(* $Id$ -*- tuareg -*-
* ----------------------------------------------------------------------
*
*)
open Fl_metatoken
open Printf
type formal_pred = [ | `Pred of string | `NegPred of string ]
type flavour = [ | `BaseDef | `Appendix ]
type pkg_definition =
{ def_var : string; def_flav : flavour; def_preds : formal_pred list;
def_value : string
}
type pkg_expr =
{ pkg_defs : pkg_definition list; pkg_children : (string * pkg_expr) list
}
exception Error of string
let string_of_preds pl =
let print = function | `Pred n -> n | `NegPred n -> "-" ^ n
in
if pl = []
then ""
else "(" ^ ((String.concat "," (List.map print pl)) ^ ")")
let scan_lexing buf =
(* transform an in_channel to a token stream; 'Space' tokens are left
* out.
*)
let (line_ref, pos0_ref, eof_found) = ((ref 1), (ref 0), (ref false))
in
fun () ->
let rec next line pos0 =
let t = Fl_meta.token buf
in
match t with
| Space -> next line pos0
| Newline -> next (line + 1) (Lexing.lexeme_end buf)
| Eof -> (eof_found := true; produce line pos0 Eof)
| _ -> produce line pos0 t
and produce line pos0 t =
(line_ref := line;
pos0_ref := pos0;
let pos = (Lexing.lexeme_start buf) - pos0 in (line, pos, t))
in
if !eof_found
then produce !line_ref !pos0_ref Eof
else next !line_ref !pos0_ref
let scan ch = scan_lexing (Lexing.from_channel ch)
let parse_lexing lexbuf =
let rec mk_set l =
match l with
| x :: l' -> if List.mem x l' then mk_set l' else x :: (mk_set l')
| [] -> [] in
let error_msg msg line col =
Printf.sprintf "%s at line %d position %d" msg line col in
let next_token = scan_lexing lexbuf in
let raise_err error_fun line col =
raise (Error (error_fun line col)) in
let get_tok test error_fun =
let (line, col, tok) = next_token ()
in
match test tok with
| None -> raise_err error_fun line col
| Some result -> result in
let get_rule rule arg error_fmt line col =
try rule arg with | Error _ -> raise_err error_fmt line col in
let rec parse_all need_rparen =
match next_token () with
| (line, col, Name "package") ->
let n =
get_tok string_tok
(error_msg "String literal expected after 'package'") in
let () =
get_tok (const_tok LParen) (error_msg "'(' expected after string") in
let subpkg =
get_rule parse_all true
(error_msg "Error in subpackage definition") line col in
let rest = parse_all need_rparen
in
{
pkg_defs = rest.pkg_defs;
pkg_children = (n, subpkg) :: rest.pkg_children;
}
| (line, col, Name n) ->
let (args, flav, value) =
get_rule parse_properties ()
(error_msg "Error in 'name = value' clause") line col in
let rest = parse_all need_rparen in (* TODO: Check args *)
let args' = List.sort compare (mk_set args) in
let def =
{
def_var = n;
def_flav = flav;
def_preds = args';
def_value = value;
}
in
{
pkg_defs = def :: rest.pkg_defs;
pkg_children = rest.pkg_children;
}
| (line, col, Eof) ->
(if need_rparen
then
raise_err
(Printf.sprintf "Unexpected end of file in line %d position %d")
line col
else ();
{ pkg_defs = []; pkg_children = []; })
| (line, col, RParen) ->
(if not need_rparen
then
raise_err
(Printf.sprintf "Unexpected end of file in line %d position %d")
line col
else ();
{ pkg_defs = []; pkg_children = []; })
| (line, col, _) ->
raise_err (error_msg "Expected 'name = value' clause") line col
and parse_properties () =
match next_token () with
| (line, col, LParen) ->
let arg1 = parse_argument () in
let args = parse_arguments () in
let flav = parse_flavour () in
let s =
get_tok string_tok (error_msg "Expected string constant after '='")
in ((arg1 :: args), flav, s)
| (line, col, Equal) ->
let s =
get_tok string_tok
(error_msg "'=' must be followed by a string constant")
in ([], `BaseDef, s)
| (line, col, PlusEqual) ->
let s =
get_tok string_tok
(error_msg "'+=' must be followed by a string constant")
in ([], `Appendix, s)
| (line, col, _) ->
raise_err (error_msg "Expected a '=' or a '(arguments,...)=' clause")
line col
and parse_arguments () =
match next_token () with
| (line, col, Comma) ->
let arg = parse_argument () in
let args = parse_arguments () in arg :: args
| (_, _, RParen) -> []
| (line, col, _) ->
raise_err (error_msg "Another predicate or a ')' expected") line col
and parse_argument () =
match next_token () with
| (line, col, Name n) -> `Pred n
| (line, col, Minus) ->
let n = get_tok name_tok (error_msg "Name expected after '-'")
in `NegPred n
| (line, col, _) ->
raise_err (error_msg "Name or -Name expected") line col
and parse_flavour () =
match next_token () with
| (line, col, Equal) -> `BaseDef
| (line, col, PlusEqual) -> `Appendix
| (line, col, _) -> raise_err (error_msg "'+' or '+=' expected") line col in
let rec check_defs p l =
match l with
| [] -> ()
| def :: l' ->
(List.iter
(fun def' ->
if
(def.def_var = def'.def_var) &&
((def.def_preds = def'.def_preds) &&
((def.def_flav = `BaseDef) && (def'.def_flav = `BaseDef)))
then
(let prefix =
if p = "" then "" else "In subpackage " ^ (p ^ ": ") in
let args = string_of_preds def.def_preds
in
raise
(Error
(prefix ^
("Double definition of '" ^
(def.def_var ^ (args ^ "'"))))))
else ())
l';
check_defs p l') in
let rec check_pkg p pkg =
(check_defs p pkg.pkg_defs;
let l = ref []
in
List.iter
(fun (n, subpkg) ->
let p' = if p = "" then n else p ^ ("." ^ n)
in
(if List.mem n !l
then
raise
(Error ("Double definition for subpackage " ^ p'))
else ();
if String.contains n '.'
then
raise
(Error
("Subpackage name must not contain '.': \"" ^
(n ^ "\"")))
else ();
check_pkg p' subpkg;
l := n :: !l))
pkg.pkg_children)
in
try let pkg = parse_all false in (check_pkg "" pkg; pkg)
with | Error "" -> raise (Error "Syntax Error")
let parse ch = parse_lexing (Lexing.from_channel ch)
let escape s = (* no Str available :-( *)
let b = Buffer.create (String.length s)
in
(for k = 0 to (String.length s) - 1 do
(match s.[k] with
| '\\' -> Buffer.add_string b "\\\\"
| '"' -> Buffer.add_string b "\\\""
| c -> Buffer.add_char b c)
done;
Buffer.contents b)
let print_def f def =
let format_pred = function | `Pred s -> s | `NegPred s -> "-" ^ s in
fprintf f "%s%s %s \"%s\"\n" def.def_var
(match def.def_preds with
| [] -> ""
| l -> "(" ^ ((String.concat "," (List.map format_pred l)) ^ ")"))
(match def.def_flav with | `BaseDef -> "=" | `Appendix -> "+=")
(escape def.def_value)
let rec print f pkg =
(List.iter (print_def f) pkg.pkg_defs;
List.iter
(fun (name, child) ->
(fprintf f "\npackage \"%s\" (\n" (escape name);
print f child;
fprintf f ")\n"))
pkg.pkg_children)
let rec remove_dups l =
(* FIXME: O(n^2) *)
match l with
x :: l' ->
if List.mem x l' then remove_dups l' else x::remove_dups l'
| [] -> []
let lookup_2 name predicate_list def =
let fulfills actual_preds formal_preds =
List.for_all
(function
| `Pred n -> List.mem n predicate_list
| `NegPred n -> not (List.mem n predicate_list))
formal_preds in
let rec search_base best_n best_value l =
match l with
| [] -> if best_n >= 0 then best_value else raise Not_found
| def :: l' ->
if
(name = def.def_var) &&
((def.def_flav = `BaseDef) &&
((fulfills predicate_list def.def_preds) &&
((List.length def.def_preds) > best_n)))
then search_base
(List.length def.def_preds)
(def.def_value, def.def_preds)
l'
else search_base best_n best_value l' in
let rec search_appdx l =
match l with
| [] -> []
| def :: l' ->
if
(name = def.def_var) &&
((def.def_flav = `Appendix) &&
(fulfills predicate_list def.def_preds))
then (def.def_value, def.def_preds) :: (search_appdx l')
else search_appdx l' in
let value_a, preds_a = search_base (-1) ("",[]) def in
let additions = search_appdx def in
let values_b = List.map fst additions in
let preds_b = List.flatten (List.map snd additions) in
let value = String.concat " " (value_a :: values_b) in
let preds = remove_dups (preds_a @ preds_b) in
(value, preds)
let lookup name predicate_list def =
fst(lookup_2 name predicate_list def)
let predicate_exists p defs =
List.exists
(fun def ->
List.exists (function | `Pred n -> n = p | `NegPred n -> n = p)
def.def_preds)
defs