blob: 4704a157bcd29b7893943ebe0c1bbceb7e88ee2b [file] [log] [blame] [edit]
open Util
open Source
open El.Ast
open Backend_prose.Prose
open Config
(* Errors *)
type source = {file : string; s : string; mutable i : int}
let eos src = src.i = String.length src.s
let get src = src.s.[src.i]
let str src j = String.sub src.s j (src.i - j)
let advn src i = src.i <- src.i + i
let adv src = advn src 1
let left src = String.length src.s - src.i
let col src =
let j = ref src.i in
while !j > 0 && src.s.[!j - 1] <> '\n' do decr j done;
src.i - !j
let pos src =
let line = ref 1 in
let col = ref 1 in
for j = 0 to src.i - 1 do
if src.s.[j] = '\n' then (incr line; col := 1) else incr col
done;
Source.{file = src.file; line = !line; column = !col}
let region src = let pos = pos src in {left = pos; right = pos}
let error src msg = Error.error (region src) "splicing" msg
let try_with_error src i f x =
try f x with Error.Error (at, msg) ->
(* Translate relative positions *)
let pos = pos {src with i} in
let shift {line; column; _} =
{ file = src.file; line = line + pos.line - 1;
column = if line = 1 then column + pos.column - 1 else column} in
let at' = {left = shift at.left; right = shift at.right} in
Printexc.print_backtrace stdout;
raise (Error.Error (at', msg))
(* Environment *)
module Map = Map.Make(String)
type use = int ref
type syntax = {sdef : El.Ast.def; sfragments : (string * El.Ast.def * use) list}
type grammar = {gdef : El.Ast.def; gfragments : (string * El.Ast.def * use) list}
type relation = {rdef : El.Ast.def; rules : (string * El.Ast.def * use) list}
type definition = {fdef : El.Ast.def; clauses : El.Ast.def list; use : use}
type rule_prose = {ralgo : Backend_prose.Prose.def; use : use}
type definition_prose = {falgo : Backend_prose.Prose.def; use : use}
type env =
{ elab : Frontend.Elab.env;
config : Config.t;
latex : Backend_latex.Render.env;
prose : Backend_prose.Render.env;
mutable syn : syntax Map.t;
mutable gram : grammar Map.t;
mutable rel : relation Map.t;
mutable def : definition Map.t;
mutable rule_prose : rule_prose Map.t;
mutable def_prose : definition_prose Map.t;
}
let env_def env def =
match def.it with
| TypD (id1, id2, _, _, _) ->
if not (Map.mem id1.it env.syn) then
env.syn <- Map.add id1.it {sdef = def; sfragments = []} env.syn;
let syntax = Map.find id1.it env.syn in
let sfragments = syntax.sfragments @ [(id2.it, def, ref 0)] in
env.syn <- Map.add id1.it {syntax with sfragments} env.syn
| GramD (id1, id2, _, _, _, _) ->
if not (Map.mem id1.it env.gram) then
env.gram <- Map.add id1.it {gdef = def; gfragments = []} env.gram;
let grammar = Map.find id1.it env.gram in
let gfragments = grammar.gfragments @ [(id2.it, def, ref 0)] in
env.gram <- Map.add id1.it {grammar with gfragments} env.gram
| RelD (id, _, _) ->
env.rel <- Map.add id.it {rdef = def; rules = []} env.rel
| RuleD (id1, id2, _, _) ->
let relation = Map.find id1.it env.rel in
let rules = relation.rules @ [(id2.it, def, ref 0)] in
env.rel <- Map.add id1.it {relation with rules} env.rel
| DecD (id, _, _, _) ->
env.def <- Map.add id.it {fdef = def; clauses = []; use = ref 0} env.def
| DefD (id, _, _, _) ->
let definition = Map.find id.it env.def in
let clauses = definition.clauses @ [def] in
env.def <- Map.add id.it {definition with clauses} env.def
| FamD _ | VarD _ | SepD | HintD _ ->
()
let env_prose env prose =
match prose with
| RuleD (anchor, _, _) -> (* TODO *)
env.rule_prose <- Map.add anchor {ralgo = prose; use = ref 0} env.rule_prose
| AlgoD ({ it = Al.Ast.RuleA (_, anchor, _, _); _ }) ->
env.rule_prose <- Map.add anchor {ralgo = prose; use = ref 0} env.rule_prose
| AlgoD ({ it = Al.Ast.FuncA (id, _, _); _}) ->
env.def_prose <- Map.add id {falgo = prose; use = ref 0} env.def_prose
let env (config : config) pdsts odsts elab el pr : env =
let latex = Backend_latex.Render.env config.latex el in
let prose = Backend_prose.Render.env config.prose pdsts odsts latex in
let env =
{ elab; config; latex; prose;
syn = Map.empty;
gram = Map.empty;
rel = Map.empty;
def = Map.empty;
rule_prose = Map.empty;
def_prose = Map.empty;
}
in
List.iter (env_def env) el;
List.iter (env_prose env) pr;
env
let warn_use use space id1 id2 =
if !use <> 1 then
let id = if id2 = "" then id1 else id1 ^ "/" ^ id2 in
let msg = if !use = 0 then "never spliced" else "spliced more than once" in
prerr_endline ("warning: " ^ space ^ " `" ^ id ^ "` was " ^ msg)
let warn_math env =
Map.iter (fun id1 {sfragments; _} ->
List.iter (fun (id2, _, use) -> warn_use use "syntax" id1 id2) sfragments
) env.syn;
Map.iter (fun id1 {gfragments; _} ->
List.iter (fun (id2, _, use) -> warn_use use "grammar" id1 id2) gfragments
) env.gram;
Map.iter (fun id1 {rules; _} ->
List.iter (fun (id2, _, use) -> warn_use use "rule" id1 id2) rules
) env.rel;
Map.iter (fun id1 ({use; _} : definition) ->
warn_use use "definition" id1 ""
) env.def
let warn_prose env =
Map.iter (fun id1 ({use; _} : rule_prose) ->
warn_use use "rule prose" id1 ""
) env.rule_prose;
Map.iter (fun id1 ({use; _} : definition_prose) ->
warn_use use "definition prose" id1 ""
) env.def_prose
let find_nosub space src id1 id2 =
if id2 <> "" then
error src ("unknown " ^ space ^ " identifier `" ^ id1 ^ "/" ^ id2 ^ "`")
let match_full re s =
Str.string_match re s 0 && Str.match_end () = String.length s
let find_entries space src id1 id2 entries =
let id2' = if id2 = "" then "*" else id2 in
let re = Str.(regexp (global_replace (regexp "\\*\\|\\?") (".\\0") id2')) in
let defs = List.filter (fun (id, _, _) -> match_full re id) entries in
if defs = [] then
error src ("unknown " ^ space ^ " identifier `" ^ id1 ^ "/" ^ id2' ^ "`");
List.map (fun (_, def, use) -> incr use; def) defs
(* let find_entry space src id1 id2 entries = *)
(* match find_entries space src id1 id2 entries with *)
(* | [def] -> def *)
(* | defs -> *)
(* Printf.eprintf "warning: %s `%s/%s` has multiple definitions\n%!" space id1 id2; *)
(* List.hd defs *)
(* TODO(2, rossberg): this should be an error, once the last hard-coded prose rule is gone
error src ("duplicate " ^ space ^ " identifier `" ^ id1 ^ "/" ^ id2 ^ "`")
*)
let ungroup = List.map (fun x -> [x])
let find_syntax env src id1 id2 =
match Map.find_opt id1 env.syn with
| None -> error src ("unknown syntax identifier `" ^ id1 ^ "`")
| Some syntax ->
let defs = find_entries "syntax" src id1 id2 syntax.sfragments in
if id2 = "" then [defs] else ungroup defs
let find_grammar env src id1 id2 =
match Map.find_opt id1 env.gram with
| None -> error src ("unknown grammar identifier `" ^ id1 ^ "`")
| Some grammar ->
let defs = find_entries "grammar" src id1 id2 grammar.gfragments in
if id2 = "" then [defs] else ungroup defs
let find_relation env src id1 id2 =
find_nosub "relation" src id1 id2;
match Map.find_opt id1 env.rel with
| None -> error src ("unknown relation identifier `" ^ id1 ^ "`")
| Some relation -> [[relation.rdef]]
let find_rule env src id1 id2 =
match Map.find_opt id1 env.rel with
| None -> error src ("unknown relation identifier `" ^ id1 ^ "`")
| Some relation -> ungroup (find_entries "rule" src id1 id2 relation.rules)
let find_def env src id1 id2 =
find_nosub "definition" src id1 id2;
match Map.find_opt id1 env.def with
| None -> error src ("unknown definition identifier `" ^ id1 ^ "`")
| Some definition ->
if definition.clauses = [] then
error src ("definition `" ^ id1 ^ "` has no clauses");
incr definition.use; [definition.clauses]
let find_rule_prose env src id1 id2 =
let id = if id2 <> "" then id1 ^ "/" ^ id2 else id1 in
match Map.find_opt id env.rule_prose with
| None -> error src ("unknown prose relation identifier `" ^ id ^ "`")
| Some rule -> incr rule.use; rule.ralgo
let find_def_prose env src id1 id2 =
find_nosub "definition" src id1 id2;
match Map.find_opt id1 env.def_prose with
| None -> error src ("unknown prose definition identifier `" ^ id1 ^ "`")
| Some definition -> incr definition.use; definition.falgo
(* Parsing *)
let len = String.length
let rec parse_space src =
if not (eos src) && (get src = ' ' || get src = '\t' || get src = '\n') then
(adv src; parse_space src)
let rec try_string' s i s' j : bool =
j = len s' || s.[i] = s'.[j] && try_string' s (i + 1) s' (j + 1)
let try_string src s : bool =
left src >= len s && try_string' src.s src.i s 0 && (advn src (len s); true)
let try_anchor_start src anchor : bool =
try_string src (anchor ^ "{")
let rec parse_to_colon src =
if eos src then
error src "colon `:` expected"
else if get src <> ':' then
(adv src; parse_to_colon src)
let rec parse_to_anchor_end i0 depth src =
if eos src then
error {src with i = i0} "unclosed anchor"
else if get src = '{' then
(adv src; parse_to_anchor_end i0 (depth + 1) src)
else if get src <> '}' then
(adv src; parse_to_anchor_end i0 depth src)
else if depth > 0 then
(adv src; parse_to_anchor_end i0 (depth - 1) src)
let rec parse_id' src =
if not (eos src) then
match get src with
| 'A'..'Z' | 'a'..'z' | '0'..'9' | '_' | '\'' | '`' | '-' | '*' | '.' ->
(adv src; parse_id' src)
| _ -> ()
let parse_id src space : string =
let j = src.i in
parse_id' src;
if j = src.i then
error {src with i = j} ("expected " ^ space ^ " identifier or `}`");
str src j
let parse_id_id env src space1 space2 find =
let j = src.i in
let id1 = parse_id src space1 in
let id2 =
if space2 <> "" && try_string src "/" then parse_id src space2 else ""
in find env {src with i = j} id1 id2
let rec parse_id_id_list env src space1 space2 find : El.Ast.def list list =
parse_space src;
if try_string src "}" then [] else
let defs1 = parse_id_id env src space1 space2 find in
let defs2 = parse_id_id_list env src space1 space2 find in
defs1 @ defs2
let rec parse_group_list env src space1 space2 find : El.Ast.def list list =
parse_space src;
if try_string src "}" then [] else
let groups =
if try_string src "{" then
[List.concat (parse_id_id_list env src space1 space2 find)]
else
parse_id_id env src space1 space2 find
in
groups @ parse_group_list env src space1 space2 find
let try_def_anchor env src r sort space1 space2 find : bool =
let b = try_string src (sort ^ ":") in
if b then
( let groups = parse_group_list env src space1 space2 find in
let defs = List.tl (List.concat_map ((@) [SepD $ no_region]) groups) in
if not (String.ends_with ~suffix:"-ignore" sort) then
let decorated = String.ends_with ~suffix:"+" sort in
let unmacrofied = String.ends_with ~suffix:"-" sort in
let env' = env.latex
|> Backend_latex.Render.without_macros unmacrofied
|> Backend_latex.Render.with_syntax_decoration decorated
|> Backend_latex.Render.with_grammar_decoration decorated
|> Backend_latex.Render.with_rule_decoration decorated
in
r := Backend_latex.Render.render_defs env' defs
);
b
let try_relid src : id option =
let i = src.i in
parse_space src;
let id = try parse_id src "relation" with Error.Error _ -> " " in
if id.[0] <> Char.lowercase_ascii id.[0] then
let pos = pos {src with i} in
let left = {file = src.file; line = pos.line; column = pos.column} in
let right = {left with column = left.column + String.length id} in
Some (id $ {left; right})
else
(advn src (i - src.i); None)
let run_parser find_end parser src =
let i = src.i in
find_end src;
let s = str src i in
adv src;
try_with_error src i parser s
let parse_typ src : typ =
run_parser parse_to_colon Frontend.Parse.parse_typ src
let parse_exp src i0 : exp =
run_parser (parse_to_anchor_end i0 0) Frontend.Parse.parse_exp src
let parse_sym src i0 : sym =
run_parser (parse_to_anchor_end i0 0) Frontend.Parse.parse_sym src
let elab_exp src i elaborator env exp typ =
try_with_error src i (elaborator env.elab exp) typ
let render_exp src i renderer env exp =
try_with_error src i (renderer env) exp
let render_sym = render_exp
let try_exp_anchor env src r : bool =
let i0 = src.i in
if try_string src "-:" then (
let exp = parse_exp src (i0 - 2) in
let env' = Backend_latex.Render.without_macros true env.latex in
r := render_exp src (i0 - 2) Backend_latex.Render.render_exp env' exp;
true
)
else if try_string src ":" then (
let exp = parse_exp src (i0 - 2) in
r := render_exp src (i0 - 2) Backend_latex.Render.render_exp env.latex exp;
true
)
else
match try_relid src with
| Some id when try_string src ":" ->
let i = src.i in
let exp = parse_exp src (i0 - 2) in
let _ = elab_exp src i Frontend.Elab.elab_rel env exp id in
r := render_exp src i Backend_latex.Render.render_exp env.latex exp;
true
| Some _ -> advn src (i0 - src.i); false
| None ->
match parse_typ src with
| typ ->
let i = src.i in
let exp = parse_exp src (i0 - 2) in
let _ = elab_exp src i Frontend.Elab.elab_exp env exp typ in
r := render_exp src i Backend_latex.Render.render_exp env.latex exp;
true
| exception Error.Error _ -> advn src (i0 - src.i); false
let try_sym_anchor env src r sort : bool =
let i0 = src.i in
let b = try_string src (sort ^ ":") in
if b then (
let sym = parse_sym src (i0 - 2) in
r := render_sym src (i0 - 2) Backend_latex.Render.render_sym env.latex sym;
);
b
let try_prose_anchor env src r sort space1 space2 find : bool =
let b = try_string src (sort ^ ":") in
if b then (
parse_space src;
let algo = parse_id_id env src space1 space2 find in
parse_space src;
if not (try_string src "}") then
error src "closing bracket `}` expected";
if not (String.ends_with ~suffix:"-ignore" sort) then
r := Backend_prose.Render.render_def env.prose algo
);
b
(* Splicing *)
let splice_anchor env src splice_pos anchor buf =
let open Backend_latex in
let config = {(Render.config env.latex) with Config.display = anchor.newline} in
let env' = {env with latex = Render.env_with_config env.latex config} in
parse_space src;
let r = ref "" in
let prose = ref true in
ignore (
try_prose_anchor env' src r "rule-prose" "prose relation" "rule" find_rule_prose ||
try_prose_anchor env' src r "definition-prose" "prose definition" "" find_def_prose ||
try_prose_anchor env' src r "rule-prose-ignore" "prose relation" "rule" find_rule_prose ||
try_prose_anchor env' src r "definition-prose-ignore" "prose definition" "" find_def_prose ||
(prose := false; false) ||
try_def_anchor env' src r "syntax" "syntax" "fragment" find_syntax ||
try_def_anchor env' src r "syntax+" "syntax" "fragment" find_syntax ||
try_def_anchor env' src r "syntax-" "syntax" "fragment" find_syntax ||
try_def_anchor env' src r "grammar" "grammar" "fragment" find_grammar ||
try_def_anchor env' src r "grammar-" "grammar" "fragment" find_grammar ||
try_def_anchor env' src r "relation" "relation" "" find_relation ||
try_def_anchor env' src r "relation-" "relation" "" find_relation ||
try_def_anchor env' src r "rule" "relation" "rule" find_rule ||
try_def_anchor env' src r "rule+" "relation" "rule" find_rule ||
try_def_anchor env' src r "rule-" "relation" "rule" find_rule ||
try_def_anchor env' src r "definition" "definition" "" find_def ||
try_def_anchor env' src r "definition-" "definition" "" find_def ||
try_def_anchor env' src r "syntax-ignore" "syntax" "fragment" find_syntax ||
try_def_anchor env' src r "grammar-ignore" "grammar" "fragment" find_grammar ||
try_def_anchor env' src r "relation-ignore" "relation" "" find_relation ||
try_def_anchor env' src r "rule-ignore" "relation" "rule" find_rule ||
try_def_anchor env' src r "definition-ignore" "definition" "" find_def ||
try_sym_anchor env' src r "grammar-case" ||
try_exp_anchor env' src r ||
error src "unknown anchor sort";
);
if !r <> "" then
( let s = if !prose then !r else anchor.prefix ^ !r ^ anchor.suffix in
let indent = if !prose then "" else anchor.indent in
let nl =
if not anchor.newline then " " else
"\n" ^ String.make (col {src with i = splice_pos}) ' ' ^ indent
in
let s' = if nl = "\n" then s else Str.(global_replace (regexp "\n") nl s) in
Buffer.add_string buf s'
)
let rec try_anchors env src buf = function
| [] -> false
| anchor::anchors ->
let i = src.i in
if try_anchor_start src anchor.token then
(splice_anchor env src i anchor buf; true)
else
try_anchors env src buf anchors
let rec splice_all env src buf =
if not (eos src) then (
if not (try_anchors env src buf env.config.anchors) then
(Buffer.add_char buf (get src); adv src);
splice_all env src buf
)
(* Entry points *)
let splice_string env file s : string =
let buf = Buffer.create (String.length s) in
splice_all env {file; s; i = 0} buf;
Buffer.contents buf
let splice_file ?(dry = false) env infile outfile =
let ic = In_channel.open_text infile in
let s =
Fun.protect (fun () -> In_channel.input_all ic)
~finally:(fun () -> In_channel.close ic)
in
let s' = splice_string env infile s in
if not dry then
if outfile = "" then print_endline s' else
let oc = Out_channel.open_text outfile in
Fun.protect (fun () -> Out_channel.output_string oc s')
~finally:(fun () -> Out_channel.close oc)