diff --git a/syntax/pa_optcomp.ml b/syntax/pa_optcomp.ml index 583248f..5d511e8 100644 --- a/syntax/pa_optcomp.ml +++ b/syntax/pa_optcomp.ml @@ -38,8 +38,8 @@ module Env = Map.Make(struct type t = ident let compare = compare end) type env = value Env.t type directive = - | Dir_let of ident * Ast.expr - | Dir_default of ident * Ast.expr + | Dir_let of Ast.patt * Ast.expr + | Dir_default of Ast.patt * Ast.expr | Dir_if of Ast.expr | Dir_else | Dir_elif of Ast.expr @@ -62,9 +62,9 @@ let next_quotation_id = let r = ref 0 in fun _ -> incr r; !r -(* +-------------+ - | Environment | - +-------------+ *) +(* +-----------------------------------------------------------------+ + | Environment | + +-----------------------------------------------------------------+ *) let env = ref Env.empty let define id value = env := Env.add id value !env @@ -75,9 +75,9 @@ let _ = let dirs = ref [] let add_include_dir dir = dirs := dir :: !dirs -(* +--------------+ - | Dependencies | - +--------------+ *) +(* +-----------------------------------------------------------------+ + | Dependencies | + +-----------------------------------------------------------------+ *) module String_set = Set.Make(String) @@ -108,9 +108,9 @@ let write_depencies () = end; close_out oc -(* +----------------------------------------+ - | Value to expression/pattern conversion | - +----------------------------------------+ *) +(* +-----------------------------------------------------------------+ + | Value to expression/pattern conversion | + +-----------------------------------------------------------------+ *) let rec expr_of_value _loc = function | Bool true -> <:expr< true >> @@ -132,9 +132,83 @@ let rec patt_of_value _loc = function | Tuple [x] -> patt_of_value _loc x | Tuple l -> <:patt< $tup:Ast.paCom_of_list (List.map (patt_of_value _loc) l)$ >> -(* +-----------------------+ - | Expression evaluation | - +-----------------------+ *) +(* +-----------------------------------------------------------------+ + | Value printing | + +-----------------------------------------------------------------+ *) + +let string_of_value string_of_bool v = + let buf = Buffer.create 128 in + let rec aux = function + | Bool b -> + Buffer.add_string buf (string_of_bool b) + | Int n -> + Buffer.add_string buf (string_of_int n) + | Char ch -> + Buffer.add_char buf '\''; + Buffer.add_string buf (Char.escaped ch); + Buffer.add_char buf '\'' + | String s -> + Buffer.add_char buf '"'; + Buffer.add_string buf (String.escaped s); + Buffer.add_char buf '"' + | Tuple [] -> + Buffer.add_string buf "()" + | Tuple (x :: l) -> + Buffer.add_char buf '('; + aux x; + List.iter + (fun x -> + Buffer.add_string buf ", "; + aux x) + l; + Buffer.add_char buf ')' + in + aux v; + Buffer.contents buf + +let string_of_value_o v = + string_of_value + (function + | true -> "true" + | false -> "false") + v + +let string_of_value_r v = + string_of_value + (function + | true -> "True" + | false -> "False") + v + +let string_of_value_no_pretty v = + let buf = Buffer.create 128 in + let rec aux = function + | Bool b -> + Buffer.add_string buf (string_of_bool b) + | Int n -> + Buffer.add_string buf (string_of_int n) + | Char ch -> + Buffer.add_char buf ch + | String s -> + Buffer.add_string buf s; + | Tuple [] -> + Buffer.add_string buf "()" + | Tuple (x :: l) -> + Buffer.add_char buf '('; + aux x; + List.iter + (fun x -> + Buffer.add_string buf ", "; + aux x) + l; + Buffer.add_char buf ')' + in + aux v; + Buffer.contents buf + +(* +-----------------------------------------------------------------+ + | Expression evaluation | + +-----------------------------------------------------------------+ *) let rec type_of_value = function | Bool _ -> Tbool @@ -225,32 +299,101 @@ let rec eval env = function | <:expr< fst $x$ >> -> fst (eval_pair env x) | <:expr< snd $x$ >> -> snd (eval_pair env x) + (* Conversions *) + | <:expr@loc< to_string $x$ >> -> + String(string_of_value_no_pretty (eval env x)) + | <:expr@loc< to_int $x$ >> -> + Int + (match eval env x with + | String x -> begin + try + int_of_string x + with exn -> + Loc.raise loc exn + end + | Int x -> + x + | Char x -> + int_of_char x + | Bool _ -> + Loc.raise loc (Failure "cannot convert a boolean to an integer") + | Tuple _ -> + Loc.raise loc (Failure "cannot convert a tuple to an integer")) + | <:expr@loc< to_bool $x$ >> -> + Bool + (match eval env x with + | String x -> begin + try + bool_of_string x + with exn -> + Loc.raise loc exn + end + | Int x -> + Loc.raise loc (Failure "cannot convert an integer to a boolean") + | Char x -> + Loc.raise loc (Failure "cannot convert a character to a boolean") + | Bool x -> + x + | Tuple _ -> + Loc.raise loc (Failure "cannot convert a tuple to a boolean")) + | <:expr@loc< to_char $x$ >> -> + Char + (match eval env x with + | String x -> + if String.length x = 1 then + x.[0] + else + Loc.raise loc (Failure (Printf.sprintf "cannot convert a string of length %d to a character" (String.length x))) + | Int x -> begin + try + char_of_int x + with exn -> + Loc.raise loc exn + end + | Char x -> + x + | Bool _ -> + Loc.raise loc (Failure "cannot convert a boolean to a character") + | Tuple _ -> + Loc.raise loc (Failure "cannot convert a tuple to a character")) + + (* Pretty printing *) + | <:expr@loc< show $x$ >> -> + String(string_of_value_o (eval env x)) + (* Let-binding *) | <:expr< let $p$ = $x$ in $y$ >> -> let vx = eval env x in let env = try - bind env p vx - with - Exit -> invalid_type (Ast.loc_of_expr x) (type_of_patt p) (type_of_value vx) + bind true env p vx + with Exit -> + invalid_type (Ast.loc_of_expr x) (type_of_patt p) (type_of_value vx) in eval env y | e -> Loc.raise (Ast.loc_of_expr e) (Stream.Error "expression not supported") -and bind env patt value = match patt with - | <:patt< $lid:id$ >> -> - Env.add id value env +and bind override env patt value = match patt with + | <:patt< $lid:id$ >> + | <:patt< $uid:id$ >> -> + if override || not (Env.mem id env) then + Env.add id value env + else + env | <:patt< $tup:patts$ >> -> let patts = Ast.list_of_patt patts [] in begin match value with | Tuple values when List.length values = List.length patts -> - List.fold_left2 bind env patts values + List.fold_left2 (bind override) env patts values | _ -> raise Exit end + | <:patt< _ >> -> + env + | _ -> Loc.raise (Ast.loc_of_patt patt) (Stream.Error "pattern not supported") @@ -274,13 +417,17 @@ and eval_string env e = match eval env e with | String x -> x | v -> invalid_type (Ast.loc_of_expr e) Tstring (type_of_value v) +and eval_char env e = match eval env e with + | Char x -> x + | v -> invalid_type (Ast.loc_of_expr e) Tchar (type_of_value v) + and eval_pair env e = match eval env e with | Tuple [x; y] -> (x, y) | v -> invalid_type (Ast.loc_of_expr e) (Ttuple [Tvar "a"; Tvar "b"]) (type_of_value v) -(* +-----------------------+ - | Parsing of directives | - +-----------------------+ *) +(* +-----------------------------------------------------------------+ + | Parsing of directives | + +-----------------------------------------------------------------+ *) let rec skip_space stream = match Stream.peek stream with | Some((BLANKS _ | COMMENT _), _) -> @@ -289,12 +436,6 @@ let rec skip_space stream = match Stream.peek stream with | _ -> () -let parse_equal stream = - skip_space stream; - match Stream.next stream with - | KEYWORD "=", _ -> () - | _, loc -> Loc.raise loc (Stream.Error "'=' expected") - let rec parse_eol stream = let tok, loc = Stream.next stream in match tok with @@ -324,37 +465,44 @@ let parse_ident stream = let tok, loc = Stream.next stream in begin match tok with | LIDENT id | UIDENT id -> - id + (id, loc) | KEYWORD kwd when keyword_is_id kwd -> - kwd + (kwd, loc) | _ -> Loc.raise loc (Stream.Error "identifier expected") end -let parse_expr stream = +let parse_until entry is_stop_token stream = (* Lists of opened brackets *) let opened_brackets = ref [] in + let end_loc = ref Loc.ghost in + (* Return the next token of [stream] until all opened parentheses have been closed and a newline is reached *) let rec next_token _ = Some(match Stream.next stream, !opened_brackets with - | (NEWLINE, loc), [] -> - EOI, loc + | (tok, loc), [] when is_stop_token tok -> + end_loc := loc; + (EOI, loc) - | (KEYWORD("(" | "[" | "{" as b), _) as x, l -> + | (EOI, loc), _ -> + end_loc := loc; + (EOI, loc) + + | ((KEYWORD ("(" | "[" | "{" as b) | SYMBOL ("(" | "[" | "{" as b)), _) as x, l -> opened_brackets := b :: l; x - | (KEYWORD ")", loc) as x, "(" :: l -> + | ((KEYWORD ")" | SYMBOL ")"), loc) as x, "(" :: l -> opened_brackets := l; x - | (KEYWORD "]", loc) as x, "[" :: l -> + | ((KEYWORD "]" | SYMBOL "]"), loc) as x, "[" :: l -> opened_brackets := l; x - | (KEYWORD "}", loc) as x, "{" :: l -> + | ((KEYWORD "}" | SYMBOL "}"), loc) as x, "{" :: l -> opened_brackets := l; x @@ -362,81 +510,77 @@ let parse_expr stream = x) in - Gram.parse_tokens_before_filter Syntax.expr_eoi - (not_filtered (Stream.from next_token)) + let expr = + Gram.parse_tokens_before_filter entry + (not_filtered (Stream.from next_token)) + in + (expr, Loc.join !end_loc) + +let parse_expr stream = + parse_until Syntax.expr_eoi (fun tok -> tok = NEWLINE) stream + +let parse_patt stream = + parse_until Syntax.patt_eoi (function + | SYMBOL "=" | KEYWORD "=" -> true + | _ -> false) stream let parse_directive stream = match Stream.peek stream with - | Some(KEYWORD "#", loc) -> + | Some((KEYWORD "#" | SYMBOL "#"), loc) -> begin Stream.junk stream; - (* Move the location to the beginning of the line *) - let (file_name, - start_line, start_bol, start_off, - stop_line, stop_bol, stop_off, - ghost) = Loc.to_tuple loc in - let loc = Loc.of_tuple (file_name, - start_line, start_bol, start_bol, - start_line, start_bol, start_bol, - ghost) in + let dir, loc_dir = parse_ident stream in - begin match parse_ident stream with + match dir with | "let" -> - let id = parse_ident stream in - parse_equal stream; - let expr = parse_expr stream in - Some(Dir_let(id, expr), loc) + let patt, _ = parse_patt stream in + let expr, end_loc = parse_expr stream in + Some(Dir_let(patt, expr), Loc.merge loc end_loc) | "let_default" -> - let id = parse_ident stream in - parse_equal stream; - let expr = parse_expr stream in - Some(Dir_default(id, expr), loc) - - (* For compatibility *) - | "define" -> - let id = parse_ident stream in - let expr = parse_expr stream in - Some(Dir_let(id, expr), loc) - - (* For compatibility *) - | "default" -> - let id = parse_ident stream in - let expr = parse_expr stream in - Some(Dir_default(id, expr), loc) + let patt, _ = parse_patt stream in + let expr, end_loc = parse_expr stream in + Some(Dir_default(patt, expr), Loc.merge loc end_loc) | "if" -> - Some(Dir_if(parse_expr stream), loc) + let expr, end_loc = parse_expr stream in + Some(Dir_if expr, Loc.merge loc end_loc) | "else" -> parse_eol stream; - Some(Dir_else, loc) + Some(Dir_else, Loc.merge loc loc_dir) | "elif" -> - Some(Dir_elif(parse_expr stream), loc) + let expr, end_loc = parse_expr stream in + Some(Dir_elif expr, Loc.merge loc end_loc) | "endif" -> parse_eol stream; - Some(Dir_endif, loc) + Some(Dir_endif, Loc.merge loc loc_dir) | "include" -> - Some(Dir_include(parse_expr stream), loc) + let expr, end_loc = parse_expr stream in + Some(Dir_include expr, Loc.merge loc end_loc) | "directory" -> - Some(Dir_directory(parse_expr stream), loc) + let expr, end_loc = parse_expr stream in + Some(Dir_directory expr, Loc.merge loc end_loc) | "error" -> - Some(Dir_error(parse_expr stream), loc) + let expr, end_loc = parse_expr stream in + Some(Dir_error expr, Loc.merge loc end_loc) | "warning" -> - Some(Dir_warning(parse_expr stream), loc) + let expr, end_loc = parse_expr stream in + Some(Dir_warning expr, Loc.merge loc end_loc) | "default_quotation" -> - Some(Dir_default_quotation(parse_expr stream), loc) + let expr, end_loc = parse_expr stream in + Some(Dir_default_quotation expr, Loc.merge loc end_loc) - | dir -> - Loc.raise loc (Stream.Error (Printf.sprintf "bad directive ``%s''" dir)) - end + | _ -> + Loc.raise loc_dir (Stream.Error (Printf.sprintf "unknown directive ``%s''" dir)) + end | _ -> None @@ -447,9 +591,9 @@ let parse_command_line_define str = | <:expr< $uid:id$ = $e$ >> -> define id (eval !env e) | _ -> invalid_arg str -(* +----------------+ - | BLock skipping | - +----------------+ *) +(* +-----------------------------------------------------------------+ + | Block skipping | + +-----------------------------------------------------------------+ *) let rec skip_line stream = match Stream.next stream with @@ -507,9 +651,9 @@ and skip_else stream = | _ -> skip_else stream -(* +-----------------+ - | Token filtering | - +-----------------+ *) +(* +-----------------------------------------------------------------+ + | Token filtering | + +-----------------------------------------------------------------+ *) type context = Ctx_if | Ctx_else @@ -555,7 +699,7 @@ let really_read state = (tok, loc) (* Return the next token from a stream, interpreting directives. *) -let rec next_token state_ref = +let rec next_token lexer state_ref = let state = !state_ref in if state.bol then match parse_directive state.stream, state.stack with @@ -563,18 +707,18 @@ let rec next_token state_ref = let rec aux e = if eval_bool !env e then begin state.stack <- Ctx_if :: state.stack; - next_token state_ref + next_token lexer state_ref end else match next_endif state.stream with | Dir_else -> state.stack <- Ctx_else :: state.stack; - next_token state_ref + next_token lexer state_ref | Dir_elif e -> aux e | Dir_endif -> - next_token state_ref + next_token lexer state_ref | _ -> assert false @@ -593,25 +737,36 @@ let rec next_token state_ref = | Some(Dir_else, loc), Ctx_if :: l -> skip_else state.stream; state.stack <- l; - next_token state_ref + next_token lexer state_ref | Some(Dir_elif _, loc), Ctx_if :: l -> skip_if state.stream; state.stack <- l; - next_token state_ref + next_token lexer state_ref | Some(Dir_endif, loc), _ :: l -> state.stack <- l; - next_token state_ref + next_token lexer state_ref - | Some(Dir_let(id, e), _), _ -> - define id (eval !env e); - next_token state_ref + | Some(Dir_let(patt, expr), _), _ -> + let value = eval !env expr in + env := ( + try + bind true !env patt value; + with Exit -> + invalid_type (Ast.loc_of_expr expr) (type_of_patt patt) (type_of_value value) + ); + next_token lexer state_ref - | Some(Dir_default(id, e), _), _ -> - if not (Env.mem id !env) then - define id (eval !env e); - next_token state_ref + | Some(Dir_default(patt, expr), _), _ -> + let value = eval !env expr in + env := ( + try + bind false !env patt value; + with Exit -> + invalid_type (Ast.loc_of_expr expr) (type_of_patt patt) (type_of_value value) + ); + next_token lexer state_ref | Some(Dir_include e, _), _ -> let fname = eval_string !env e in @@ -626,7 +781,7 @@ let rec next_token state_ref = dependencies := String_set.add fname !dependencies; let ic = open_in fname in let nested_state = { - stream = Gram.Token.Filter.filter (Gram.get_filter ()) (filter (Gram.lex (Loc.mk fname) (Stream.of_channel ic))); + stream = lexer fname ic; bol = true; stack = []; on_eoi = (fun _ -> @@ -634,27 +789,27 @@ let rec next_token state_ref = eoi *) state_ref := state; close_in ic; - next_token state_ref) + next_token lexer state_ref) } in (* Replace current state with the new one *) state_ref := nested_state; - next_token state_ref + next_token lexer state_ref | Some(Dir_directory e, loc), _ -> let dir = eval_string !env e in add_include_dir dir; - next_token state_ref + next_token lexer state_ref | Some(Dir_error e, loc), _ -> Loc.raise loc (Failure (eval_string !env e)) | Some(Dir_warning e, loc), _ -> Syntax.print_warning loc (eval_string !env e); - next_token state_ref + next_token lexer state_ref | Some(Dir_default_quotation e, loc), _ -> Syntax.Quotation.default := eval_string !env e; - next_token state_ref + next_token lexer state_ref | None, _ -> really_read state @@ -662,41 +817,52 @@ let rec next_token state_ref = else really_read state -let stream_filter filter stream = +let default_lexer fname ic = + Token.Filter.filter (Gram.get_filter ()) (filter (Gram.lex (Loc.mk fname) (Stream.of_channel ic))) + +let stream_filter lexer filter stream = (* Set the source filename *) - begin match !source_filename with - | Some _ -> - () - | None -> - match Stream.peek stream with - | None -> - () - | Some(tok, loc) -> - source_filename := Some(Loc.file_name loc) + begin + match !source_filename with + | Some _ -> + () + | None -> + match Stream.peek stream with + | None -> + () + | Some(tok, loc) -> + source_filename := Some(Loc.file_name loc) end; let state_ref = ref { stream = stream; bol = true; stack = []; on_eoi = (fun x -> x) } in - filter (Stream.from (fun _ -> Some(next_token state_ref))) + filter (Stream.from (fun _ -> Some(next_token lexer state_ref))) -(* +----------------------+ - | Quotations expansion | - +----------------------+ *) +let filter ?(lexer=default_lexer) stream = stream_filter lexer (fun x -> x) stream + +(* +-----------------------------------------------------------------+ + | Quotations expansion | + +-----------------------------------------------------------------+ *) + +let get_quotation_value str = + Hashtbl.find quotations (int_of_string str) let expand f loc _ contents = try - f loc (Hashtbl.find quotations (int_of_string contents)) - with - exn -> Loc.raise loc (Failure "fatal error in optcomp!") + f loc (get_quotation_value contents) + with exn -> + Loc.raise loc (Failure "fatal error in optcomp!") -(* +--------------+ - | Registration | - +--------------+ *) +(* +-----------------------------------------------------------------+ + | Registration | + +-----------------------------------------------------------------+ *) let _ = Camlp4.Options.add "-let" (Arg.String parse_command_line_define) " Binding for a #let directive."; + Camlp4.Options.add "-I" (Arg.String add_include_dir) + " Add a directory to #include search path."; Camlp4.Options.add "-depend" (Arg.String (fun filename -> dependency_filename := Some filename)) " Write dependencies to ."; @@ -706,4 +872,4 @@ let _ = Syntax.Quotation.add "optcomp" Syntax.Quotation.DynAst.expr_tag (expand expr_of_value); Syntax.Quotation.add "optcomp" Syntax.Quotation.DynAst.patt_tag (expand patt_of_value); - Gram.Token.Filter.define_filter (Gram.get_filter ()) stream_filter + Gram.Token.Filter.define_filter (Gram.get_filter ()) (stream_filter default_lexer)