| open Util |
| open Source |
| open El.Ast |
| open El.Convert |
| open Xl |
| open Config |
| |
| |
| (* Errors *) |
| |
| let error at msg = Error.error at "latex generation" msg |
| |
| |
| (* Helpers *) |
| |
| let concat = String.concat |
| let suffix s f x = f x ^ s |
| let space f x = " " ^ f x ^ " " |
| |
| let rec has_nl = function |
| | [] -> false |
| | Nl::_ -> true |
| | _::xs -> has_nl xs |
| |
| let rec concat_map_nl sep br f = function |
| | [] -> "" |
| | [Elem x] -> f x |
| | (Elem x)::xs -> f x ^ sep ^ concat_map_nl sep br f xs |
| | Nl::xs -> br ^ concat_map_nl sep br f xs |
| |
| let rec altern_map_nl sep br f = function |
| | [] -> "" |
| | [Elem x] -> f x |
| | (Elem x)::Nl::xs -> f x ^ br ^ altern_map_nl sep br f xs |
| | (Elem x)::xs -> f x ^ sep ^ altern_map_nl sep br f xs |
| | Nl::xs -> br ^ altern_map_nl sep br f xs |
| |
| let strip_nl = function |
| | Nl::xs -> xs |
| | xs -> xs |
| |
| |
| let split_ticks id = |
| let i = ref (String.length id) in |
| while !i > 0 && id.[!i - 1] = '\'' do decr i done; |
| String.sub id 0 !i, String.sub id !i (String.length id - !i) |
| |
| let all_sub id = String.for_all ((=) '_') id |
| let ends_sub id = id <> "" && id.[String.length id - 1] = '_' |
| let count_sub id = |
| let n = ref 0 in |
| while !n < String.length id && id.[String.length id - !n - 1] = '_' do incr n done; |
| !n |
| let split_sub id = |
| let n = count_sub id in |
| String.sub id 0 (String.length id - n), String.make n '_' |
| let chop_sub id = fst (split_sub id) |
| |
| let rec ends_sub_exp e = |
| match e.it with |
| | VarE (id, []) -> ends_sub id.it |
| | AtomE atom -> atom.it <> Atom.Atom "_" && ends_sub (Atom.to_string atom) |
| | FuseE (_e1, e2) -> ends_sub_exp e2 |
| | _ -> false |
| |
| let as_arith_exp e = |
| match e.it with |
| | ArithE e1 -> e1 |
| | _ -> e |
| |
| let as_paren_exp e = |
| match e.it with |
| | ParenE e1 -> e1 |
| | _ -> e |
| |
| let as_tup_exp e = |
| match e.it with |
| | TupE es -> es |
| | ParenE e1 -> [e1] |
| | _ -> [e] |
| |
| let as_seq_exp e = |
| match e.it with |
| | SeqE es -> es |
| | _ -> [e] |
| |
| let rec fuse_exp e deep = |
| match e.it with |
| | ParenE e1 when deep -> ParenE (fuse_exp e1 false) $ e.at |
| | IterE (e1, iter) -> IterE (fuse_exp e1 deep, iter) $ e.at |
| | SeqE (e1::es) -> List.fold_left (fun e1 e2 -> FuseE (e1, e2) $ e.at) e1 es |
| | _ -> e |
| |
| let as_tup_arg a = |
| match !(a.it) with |
| | ExpA {it = TupE es; _} -> List.map (fun e -> ref (ExpA e) $ e.at) es |
| | _ -> [a] |
| |
| |
| (* Environment *) |
| |
| module Set = Set.Make(String) |
| module Map = Map.Make(String) |
| |
| let map_cons x y map = |
| map := Map.update x |
| (function None -> Some [y] | Some ys' -> Some (y::ys')) !map |
| |
| let map_append x ys map = |
| map := Map.update x |
| (function None -> Some ys | Some ys' -> Some (ys' @ ys)) !map |
| |
| |
| type hints = exp list Map.t |
| type env = |
| { config : config; |
| vars : Set.t ref; |
| atoms : atom list Map.t ref; |
| show_typ : hints ref; |
| show_gram : hints ref; |
| show_var : hints ref; |
| show_rel : hints ref; |
| show_def : hints ref; |
| show_atom : hints ref; |
| macro_typ : hints ref; |
| macro_gram : hints ref; |
| macro_var : hints ref; |
| macro_rel : hints ref; |
| macro_def : hints ref; |
| macro_atom : hints ref; |
| desc_typ : hints ref; |
| desc_gram : hints ref; |
| deco_typ : bool; |
| deco_gram : bool; |
| deco_rule : bool; |
| name_rel : hints ref; |
| tab_rel : hints ref; |
| } |
| |
| let new_env config = |
| { config; |
| vars = ref Set.empty; |
| atoms = ref Map.empty; |
| show_typ = ref Map.empty; |
| show_gram = ref Map.empty; |
| show_var = ref Map.empty; |
| show_rel = ref Map.empty; |
| show_def = ref Map.empty; |
| show_atom = ref Map.empty; |
| macro_typ = ref Map.empty; |
| macro_gram = ref Map.empty; |
| macro_var = ref Map.empty; |
| macro_rel = ref Map.empty; |
| macro_def = ref Map.empty; |
| macro_atom = ref Map.empty; |
| desc_typ = ref Map.empty; |
| desc_gram = ref Map.empty; |
| deco_typ = false; |
| deco_gram = false; |
| deco_rule = false; |
| name_rel = ref Map.empty; |
| tab_rel = ref Map.empty; |
| } |
| |
| let config env : Config.t = |
| env.config |
| |
| let env_with_config env config : env = |
| {env with config} |
| |
| let with_syntax_decoration b env = {env with deco_typ = b} |
| let with_grammar_decoration b env = {env with deco_gram = b} |
| let with_rule_decoration b env = {env with deco_rule = b} |
| |
| let without_macros b env = |
| if not b then env else |
| env_with_config env {env.config with macros_for_ids = false} |
| |
| let local_env env = |
| { env with |
| macro_typ = ref !(env.macro_typ); |
| macro_var = ref !(env.macro_var); |
| macro_def = ref !(env.macro_def); |
| macro_rel = ref !(env.macro_rel); |
| macro_gram = ref !(env.macro_gram); |
| macro_atom = ref !(env.macro_atom); |
| } |
| |
| |
| let typed_id' atom def = def ^ ":" ^ (Atom.name atom) |
| let typed_id atom = typed_id' atom atom.note.Atom.def $ atom.at |
| let untyped_id' id' = |
| let i = String.rindex id' ':' in |
| String.sub id' (i + 1) (String.length id' - i - 1) |
| let untyped_id id = untyped_id' id.it $ id.at |
| |
| |
| (* Collecting hints to populate the environment *) |
| |
| let is_atom_typ = function |
| | {it = AtomT _; _} -> true |
| | _ -> false |
| |
| let env_macro map id = |
| map := Map.add id.it [TextE "%" $ id.at] !map |
| |
| let env_hints name map id hints = |
| List.iter (fun {hintid; hintexp} -> |
| if hintid.it = name then map_cons id.it hintexp map |
| ) hints |
| |
| let env_hintdef env hd = |
| match hd.it with |
| | AtomH (id1, atom, hints) -> |
| let id = typed_id {atom with note = {atom.note with def = id1.it}} in |
| env_hints "macro" env.macro_atom id hints; |
| env_hints "show" env.show_atom id hints |
| | TypH (id1, id2, hints) -> |
| let id = if id2.it = "" then id1 else (id1.it ^ "/" ^ id2.it) $ id2.at in |
| env_hints "desc" env.desc_typ id hints; |
| env_hints "macro" env.macro_typ id1 hints; |
| env_hints "macro" env.macro_var (El.Convert.strip_var_sub id1) hints; |
| env_hints "show" env.show_typ id1 hints; |
| env_hints "show" env.show_var id1 hints |
| | GramH (id1, id2, hints) -> |
| let id = if id2.it = "" then id1 else (id1.it ^ "/" ^ id2.it) $ id2.at in |
| env_hints "desc" env.desc_gram id hints; |
| env_hints "macro" env.macro_gram id hints; |
| env_hints "show" env.show_gram id1 hints |
| | RelH (id, hints) -> |
| env_hints "macro" env.macro_rel id hints; |
| env_hints "show" env.show_rel id hints; |
| env_hints "name" env.name_rel id hints; |
| env_hints "tabular" env.tab_rel id hints |
| | RuleH (_id1, _id2, _hints) -> |
| () |
| | VarH (id, hints) -> |
| env_hints "macro" env.macro_var id hints; |
| env_hints "show" env.show_var id hints |
| | DecH (id, hints) -> |
| env_hints "macro" env.macro_def id hints; |
| env_hints "show" env.show_def id hints |
| |
| let env_atom env tid atom hints = |
| env_hintdef env (AtomH (tid, atom, hints) $ atom.at) |
| |
| let env_atom_show env tid atom hints = |
| env_atom env tid atom |
| (List.filter (fun {hintid; _} -> hintid.it = "show") hints) |
| |
| let env_typ env tid t hints = |
| let hints' = List.filter (fun {hintid; _} -> hintid.it = "macro") hints in |
| let module EnvIter = |
| El.Iter.Make( |
| struct |
| include El.Iter.Skip |
| let visit_atom atom = |
| map_cons tid.it atom env.atoms; |
| env_atom env tid atom hints' |
| end |
| ) |
| in EnvIter.typ t |
| |
| let env_typfield env tid (atom, (t, _prems), hints) = |
| map_cons tid.it atom env.atoms; |
| env_atom env tid atom hints; |
| env_typ env tid t hints |
| |
| let env_typcase env tid (atom, (t, _prems), hints) = |
| env_atom_show env tid atom hints; |
| env_typ env tid t hints |
| |
| let env_typcon env tid ((t, _prems), hints) = |
| env_typ env tid t hints; |
| match t.it with |
| | AtomT atom |
| | InfixT (_, atom, _) |
| | BrackT (atom, _, _) -> |
| env_atom_show env tid atom hints |
| | SeqT ts -> |
| (match List.find_opt is_atom_typ ts with |
| | Some {it = AtomT atom; _} -> env_atom_show env tid atom hints |
| | _ -> () |
| ) |
| | _ -> () |
| |
| let env_typdef env tid t : typ list option = |
| map_append tid.it [] env.atoms; |
| match t.it with |
| | VarT (id, _) -> |
| map_append tid.it (Map.find id.it !(env.atoms)) env.atoms; |
| Some [t] |
| | StrT (dots1, ts, tfs, _) -> |
| iter_nl_list (env_typfield env tid) tfs; |
| iter_nl_list (fun t -> |
| match t.it with |
| | VarT (id, _) -> |
| map_append tid.it (Map.find id.it !(env.atoms)) env.atoms |
| | _ -> () |
| ) ts; |
| if dots1 = Dots && ts = [] then None else Some (filter_nl ts) |
| | CaseT (dots1, ts, tcs, _) -> |
| iter_nl_list (env_typcase env tid) tcs; |
| iter_nl_list (fun t -> |
| match t.it with |
| | VarT (id, _) -> |
| map_append tid.it (Map.find id.it !(env.atoms)) env.atoms |
| | _ -> () |
| ) ts; |
| if dots1 = Dots && ts = [] then None else Some (filter_nl ts) |
| | ConT tc -> |
| env_typcon env tid tc; |
| Some [] |
| | _ -> |
| Some [] |
| |
| let env_def env d : (id * typ list) list = |
| match d.it with |
| | FamD (id, _ps, hints) -> |
| env.vars := Set.add id.it !(env.vars); |
| env_macro env.macro_typ id; |
| env_macro env.macro_var (El.Convert.strip_var_sub id); |
| env_hintdef env (TypH (id, "" $ id.at, hints) $ d.at); |
| env_hintdef env (VarH (id, hints) $ d.at); |
| [] |
| | TypD (id1, id2, _args, t, hints) -> |
| env.vars := Set.add id1.it !(env.vars); |
| if not (Map.mem id1.it !(env.macro_typ)) then env_macro env.macro_typ id1; |
| if not (Map.mem id1.it !(env.macro_var)) then env_macro env.macro_var id1; |
| env_hintdef env (TypH (id1, id2, hints) $ d.at); |
| env_hintdef env (VarH (id1, hints) $ d.at); |
| (match env_typdef env id1 t with |
| | None -> [] |
| | Some ts -> [(id1, ts)] |
| ) |
| | GramD (id1, id2, _ps, _t, _gram, hints) -> |
| env_macro env.macro_gram id1; |
| env_hintdef env (GramH (id1, id2, hints) $ d.at); |
| [] |
| | RelD (id, _ps, t, hints) -> |
| env_hintdef env (RelH (id, hints) $ d.at); |
| env_typcon env id ((t, []), hints); |
| [] |
| | VarD (id, _t, hints) -> |
| env.vars := Set.add id.it !(env.vars); |
| env_hintdef env (VarH (id, hints) $ d.at); |
| [] |
| | DecD (id, _as, _e, hints) -> |
| env_macro env.macro_def id; |
| env_hintdef env (DecH (id, hints) $ d.at); |
| [] |
| | HintD hd -> |
| env_hintdef env hd; |
| [] |
| | RuleD _ | DefD _ | SepD -> [] |
| |
| let env_hints_inherit env map tid tid' = |
| List.iter (fun atom -> |
| Option.iter (fun es -> |
| (* |
| if atom.it = Atom.Atom "REF.STRUCT_ADDR" && map == env.macro_atom then |
| Printf.printf "[inherit] %s = %s => %s\n%!" |
| (typed_id' atom tid.it) (typed_id' atom tid'.it) (String.concat " | " (List.map El.Print.string_of_exp es)); |
| *) |
| map_append (typed_id' atom tid.it) es map |
| ) (Map.find_opt (typed_id' atom tid'.it) !map) |
| ) (Map.find tid'.it !(env.atoms)) |
| |
| let env config script : env = |
| let env = new_env config in |
| let inherits = List.concat_map (env_def env) script in |
| (* |
| Printf.printf "\n[env before inheritance]\n%s\n%!" |
| (String.concat "\n" (List.map (fun (id, atoms) -> id ^ ": " ^ String.concat " " (List.map El.Print.string_of_atom atoms)) (Map.bindings !(env.atoms)))); |
| Printf.printf "\n[env show_atom]\n%s\n%!" |
| (String.concat "\n" (List.map (fun (id, exps) -> id ^ " => " ^ String.concat " | " (List.map El.Print.string_of_exp exps)) (Map.bindings !(env.show_atom)))); |
| Printf.printf "\n[env macro_atom]\n%s\n%!" |
| (String.concat "\n" (List.map (fun (id, exps) -> id ^ " => " ^ String.concat " | " (List.map El.Print.string_of_exp exps)) (Map.bindings !(env.macro_atom)))); |
| Printf.printf "\n[env inheritance]\n"; |
| *) |
| (* Stand-alone hints can occur in a different order than their associated |
| * definitions, but to handle inherited type atoms correctly, they need to be |
| * processed in order of their type definitions. Hence, inherited types are |
| * recorded and processed in order. *) |
| List.iter (fun (tid, ts) -> |
| List.iter (fun t -> |
| match t.it with |
| | VarT (id, _) -> |
| env_hints_inherit env env.macro_atom tid id; |
| env_hints_inherit env env.show_atom tid id |
| | _ -> () |
| ) ts |
| ) inherits; |
| (* Add default atom hints last *) |
| List.iter (fun (tid, _ts) -> |
| let e = |
| match Map.find_opt tid.it !(env.macro_typ) with |
| | Some ({it = SeqE (_::e::_); _}::_) -> e |
| | _ -> TextE "%" $ no_region |
| in |
| List.iter (fun atom -> |
| let t_id = typed_id' atom tid.it in |
| if not (Map.mem t_id !(env.macro_atom)) then |
| (* |
| if atom.it = Atom.Atom "REF.STRUCT_ADDR" then |
| begin |
| (Printf.printf "[default] %s => %s\n%!" |
| t_id (El.Print.string_of_exp e); |
| *) |
| map_append t_id [e] env.macro_atom; |
| (* TODO(4, rossberg): do something for having macros on untyped splices *) |
| (* map_cons (typed_id' atom "") e env.macro_atom (* for defaulting *) *) |
| ) (Map.find tid.it !(env.atoms)); |
| ) inherits; |
| (* |
| Printf.printf "\n[env show_atom]\n%s\n%!" |
| (String.concat "\n" (List.map (fun (id, exps) -> id ^ " => " ^ String.concat " | " (List.map El.Print.string_of_exp exps)) (Map.bindings !(env.show_atom)))); |
| Printf.printf "\n[env macro_atom]\n%s\n%!" |
| (String.concat "\n" (List.map (fun (id, exps) -> id ^ " => " ^ String.concat " | " (List.map El.Print.string_of_exp exps)) (Map.bindings !(env.macro_atom)))); |
| *) |
| env |
| |
| |
| (* Rendering tables *) |
| |
| type col = Col of string | Br of [`Wide | `Narrow] |
| type row = Row of col list | Sep |
| type table = row list |
| |
| let rec string_of_row sep br = function |
| | [] -> "" |
| | (Col s)::(Br _)::cols -> s ^ br ^ string_of_row sep br cols |
| | (Col s)::cols -> s ^ sep ^ string_of_row sep br cols |
| | (Br _)::cols -> br ^ string_of_row sep br cols |
| |
| let rec string_of_table vsep vbr hsep hbr = function |
| | [] -> "" |
| | (Row cols)::[] -> string_of_row hsep hbr cols |
| | (Row cols)::Sep::rows -> string_of_row hsep hbr cols ^ vbr ^ string_of_table vsep vbr hsep hbr rows |
| | (Row cols)::rows -> string_of_row hsep hbr cols ^ vsep ^ string_of_table vsep vbr hsep hbr rows |
| | Sep::rows -> vbr ^ string_of_table vsep vbr hsep hbr rows |
| |
| let rec map_cols f = function |
| | [] -> [] |
| | (Br w)::cols -> Br w :: map_cols f cols |
| | (Col s)::cols -> Col (f s) :: map_cols f cols |
| |
| let rec map_rows f = function |
| | [] -> [] |
| | Sep::rows -> Sep :: map_rows f rows |
| | (Row cols)::rows -> Row (f cols) :: map_rows f rows |
| |
| let prefix_row (pre : col list) = function |
| | (Row cols)::rows -> (Row (pre @ cols))::rows |
| | rows -> (Row pre)::rows |
| |
| let rec prefix_rows (pre_hd : col list) (pre_tl : col list) = function |
| | [] -> [Row pre_hd] |
| | Sep::rows -> Sep :: prefix_rows pre_hd pre_tl rows |
| | (Row cols)::rows -> Row (pre_hd @ cols) :: map_rows (fun row -> pre_tl @ row) rows |
| |
| let prefix_rows_hd pre tab = |
| let empty = map_cols (fun _ -> "") pre in |
| prefix_rows pre empty tab |
| |
| let rec merge_cols sep = function |
| | [] -> [] |
| | (Col s1)::(Col s2)::cols -> merge_cols sep (Col (s1 ^ sep ^ s2) :: cols) |
| | col::cols -> col :: merge_cols sep cols |
| |
| let concat_cols sep s = function |
| | (Col s')::cols -> Col (s ^ sep ^ s') :: cols |
| | cols -> Col s :: cols |
| |
| let concat_rows sep s = function |
| | (Row cols)::rows -> Row (concat_cols sep s cols) :: rows |
| | rows -> Row (concat_cols sep s []) :: rows |
| |
| let rec concat_cols_table sep cols tab = |
| match cols with |
| | [] -> tab |
| | [Col s] -> concat_rows sep s tab |
| | col::cols' -> prefix_row [col] (concat_cols_table sep cols' tab) |
| |
| let rec concat_table sep tab1 tab2 = |
| match tab1 with |
| | [] -> tab2 |
| | [Row cols] -> concat_cols_table sep cols tab2 |
| | row::tab1' -> row :: concat_table sep tab1' tab2 |
| |
| let rec render_cols env ((fmt, indent_wide, indent_narrow) as cfg) i = function |
| | [] -> "" |
| | (Br w)::cols -> |
| let width = List.length fmt in |
| let indent = if w = `Wide then indent_wide else indent_narrow in |
| let fmt' = "l" :: List.map (fun _ -> "@{}l") (List.tl cols) in |
| let n = width - i in |
| (* Add spare &'s at the end of the current line to work around Latex |
| * strangeness (it will calculate the formula's width incorrectly, |
| * leading to bogus placement). *) |
| String.make (max 0 (n - 1)) '&' ^ " \\\\\n" ^ |
| String.make indent '&' ^ " " ^ |
| "\\multicolumn{" ^ string_of_int (width - indent) ^ "}{@{}l@{}}{\\quad\n" ^ |
| (if n <= 1 then |
| render_cols env (fmt', 0, 0) 0 cols |
| else |
| render_table env "" fmt' 0 0 [Row cols] |
| ) ^ |
| "\n}" |
| | (Col s)::[] -> s |
| | (Col "")::cols -> "& " ^ render_cols env cfg (i + 1) cols |
| | (Col s)::cols -> s ^ " & " ^ render_cols env cfg (i + 1) cols |
| |
| and render_rows env cfg = function |
| | [] -> "" |
| | Sep::rows -> "{} \\\\[-2ex]\n" ^ render_rows env cfg rows |
| | (Row r)::Sep::rows -> |
| render_cols env cfg 0 r ^ " \\\\[0.8ex]\n" ^ |
| render_rows env cfg rows |
| | (Row r)::rows -> |
| render_cols env cfg 0 r ^ " \\\\\n" ^ |
| render_rows env cfg rows |
| |
| and render_table env sp fmt indent_wide indent_narrow (tab : table) = |
| let fmt' = List.hd fmt :: |
| List.map (fun s -> if s.[0] = '@' then s else sp ^ s) (List.tl fmt) in |
| "\\begin{array}[t]{@{}" ^ String.concat "" fmt' ^ "@{}}\n" ^ |
| render_rows env (fmt, indent_wide, indent_narrow) tab ^ |
| "\\end{array}" |
| |
| |
| let rec render_sep_defs f = function |
| | [] -> [] |
| | {it = SepD; _}::ds -> Sep :: render_sep_defs f ds |
| | d::ds -> f d @ render_sep_defs f ds |
| |
| (* |
| * TODO(4, rossberg): Reconcile this; perhaps get rid of NL lists altogether. |
| * The NLs in different NL lists are currently interpreted differently: |
| * - comma nl list (str): comma-nl => NL instead of WS |
| * - bar nl list (alt): nl-bar => NL instead of WS |
| * - dash nl list (prem): dash-dash => Sep instead of NL |
| * - sym seq: nl-nl-nl => Sep instead of NL or WS |
| * - sym alt: nl-bar => NL instead of WS |
| *) |
| let rec render_nl_list env (sep : [`V | `H] * string) (f : env -> 'a -> row list) : |
| 'a nl_list -> table = |
| function |
| | [] -> [] |
| | [Elem x] -> f env x |
| | (Elem x)::Nl::xs when env.config.display -> |
| f env x @ render_nl_list env sep f (Nl::xs) |
| | (Elem x)::Nl::xs -> render_nl_list env sep f ((Elem x)::xs) |
| | (Elem x)::xs -> |
| let env' = {env with config = {env.config with display = false}} in |
| let rows1 = f env' x in |
| let rows2 = render_nl_list env sep f xs in |
| if fst sep = `V then rows1 @ rows2 |
| else concat_table " " rows1 (concat_rows " " (snd sep) rows2) |
| | Nl::xs when env.config.display -> |
| let rows = render_nl_list env sep f xs in |
| if fst sep = `V then Sep :: rows else rows |
| | Nl::xs -> render_nl_list env sep f xs |
| |
| |
| (* Show expansions *) |
| |
| exception Arity_mismatch |
| |
| let render_exp_fwd = ref (fun _ -> assert false) |
| let render_arg_fwd = ref (fun _ -> assert false) |
| let render_args_fwd = ref (fun _ -> assert false) |
| |
| (* Show hints are expressions, so expansion produces an expression that is |
| * potentially converted to another syntactic class afterwards. |
| *) |
| type ctxt = |
| { macro : hints ref; (* macro map for the target class of ids *) |
| templ : string list option; (* current macro template *) |
| args : arg list; (* epansion arguments *) |
| next : int ref; (* argument counter *) |
| max : int ref; (* highest used argument (to detect arity) *) |
| } |
| |
| let macro_template env macro name = |
| if not env.config.macros_for_ids then None else |
| match Map.find_opt name !macro with |
| | Some ({it = (TextE s | SeqE ({it = TextE s; _}::_)); _}::_) -> |
| Some (String.split_on_char '%' s) |
| | _ -> |
| (* |
| Printf.printf "[macro not found %s]\n%!" name; |
| Printf.printf "%s\n%!" (String.concat " " (List.map fst (Map.bindings !macro))); |
| *) |
| None |
| |
| let expand_name templ name = |
| let name', sub = split_sub name in |
| String.concat name' (Option.get templ) ^ sub |
| |
| let nonmacro_atom atom = |
| let open Atom in |
| match atom.it with |
| | Atom s -> s = "_" |
| | Dot |
| | Comma | Semicolon |
| | Colon | Equal | Less | Greater |
| | Quest | Plus | Star |
| | LParen | RParen |
| | LBrack | RBrack |
| | LBrace | RBrace -> true |
| | _ -> false |
| |
| let expand_atom env (ctxt : ctxt) atom = |
| (* |
| Printf.eprintf "[expand_atom %s @ %s] def=%s templ=%s\n%!" |
| (Atom.to_string atom) (Source.string_of_region atom.at) atom.note.Atom.def |
| (match templ with None -> "none" | Some xs -> String.concat "%" xs); |
| *) |
| (* When expanding with macro template, then pre-macrofy atom name, |
| * since we will have lost the template context later on. *) |
| if ctxt.templ = None || nonmacro_atom atom then atom else |
| let name' = expand_name ctxt.templ (Atom.name atom) in |
| let atom' = {atom with it = Atom.Atom name'} in |
| (* Record result as identity expansion, HACK: unless it exists already *) |
| if not (Map.mem name' !(env.macro_atom)) then env_macro env.macro_atom (typed_id atom'); |
| atom' |
| |
| let expand_id _env (ctxt : ctxt) id = |
| (* When expanding with macro template, then pre-macrofy id name, |
| * since we will have lost the template context later on. *) |
| if ctxt.templ = None || all_sub id.it then id else |
| let id' = {id with it = expand_name ctxt.templ id.it} in |
| (* |
| Printf.eprintf "[expand_id %s] templ=%s macro=%b => %s macro=%b\n%!" |
| id.it |
| (match templ with None -> "none" | Some xs -> String.concat "%" xs) |
| (Map.mem id.it !(ctxt.macro)) id'.it (Map.mem id'.it !(ctxt.macro)); |
| *) |
| (* Record result as identity expansion, HACK: unless it exists already *) |
| if not (Map.mem id'.it !(ctxt.macro)) then env_macro ctxt.macro id'; |
| id' |
| |
| let rec expand_iter env ctxt iter = |
| match iter with |
| | Opt | List | List1 -> iter |
| | ListN (e, id_opt) -> ListN (expand_exp env ctxt e, id_opt) |
| |
| and expand_exp env ctxt e = |
| (* Involves side effects, be careful to order recursive calls! *) |
| (match e.it with |
| | AtomE atom -> |
| let atom' = expand_atom env ctxt atom in |
| AtomE atom' |
| | BoolE _ | NumE _ | TextE _ | EpsE -> |
| e.it |
| | VarE (id, args) -> |
| let id' = expand_id env ctxt id in |
| let args' = List.map (expand_arg env ctxt) args in |
| VarE (id', args') |
| | CvtE (e1, nt) -> |
| let e1' = expand_exp env ctxt e1 in |
| CvtE (e1', nt) |
| | UnE (op, e1) -> |
| let e1' = expand_exp env ctxt e1 in |
| UnE (op, e1') |
| | BinE (e1, op, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| BinE (e1', op, e2') |
| | CmpE (e1, op, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| CmpE (e1', op, e2') |
| | SeqE es -> |
| let es' = List.map (expand_exp env ctxt) es in |
| SeqE es' |
| | ListE es -> |
| let es' = List.map (expand_exp env ctxt) es in |
| ListE es' |
| | IdxE (e1, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| IdxE (e1', e2') |
| | SliceE (e1, e2, e3) -> |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| let e3' = expand_exp env ctxt e3 in |
| SliceE (e1', e2', e3') |
| | UpdE (e1, p, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let p' = expand_path env ctxt p in |
| let e2' = expand_exp env ctxt e2 in |
| UpdE (e1', p', e2') |
| | ExtE (e1, p, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let p' = expand_path env ctxt p in |
| let e2' = expand_exp env ctxt e2 in |
| ExtE (e1', p', e2') |
| | StrE efs -> |
| let efs' = map_nl_list (expand_expfield env ctxt) efs in |
| StrE efs' |
| | DotE (e1, atom) -> |
| let e1' = expand_exp env ctxt e1 in |
| let atom' = expand_atom env ctxt atom in |
| DotE (e1', atom') |
| | CommaE (e1, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| CommaE (e1', e2') |
| | CatE (e1, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| CatE (e1', e2') |
| | MemE (e1, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| MemE (e1', e2') |
| | LenE e1 -> |
| let e1' = expand_exp env ctxt e1 in |
| LenE e1' |
| | SizeE id -> |
| SizeE id |
| | ParenE e1 -> |
| let e1' = expand_exp env ctxt e1 in |
| ParenE e1' |
| | TupE es -> |
| let es' = List.map (expand_exp env ctxt) es in |
| TupE es' |
| | InfixE (e1, atom, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let atom' = expand_atom env ctxt atom in |
| let e2' = expand_exp env ctxt e2 in |
| InfixE (e1', atom', e2') |
| | BrackE (l, e1, r) -> |
| let l' = expand_atom env ctxt l in |
| let e1' = expand_exp env ctxt e1 in |
| let r' = expand_atom env ctxt r in |
| BrackE (l', e1', r') |
| | CallE (id, args) -> |
| let id' = expand_id env {ctxt with macro = env.macro_def} id in |
| let args' = List.map (expand_arg env ctxt) args in |
| CallE (id', args') |
| | IterE (e1, iter) -> |
| let e1' = expand_exp env ctxt e1 in |
| let iter' = expand_iter env ctxt iter in |
| IterE (e1', iter') |
| | TypE (e1, t) -> |
| let e1' = expand_exp env ctxt e1 in |
| TypE (e1', t) |
| | ArithE e1 -> |
| let e1' = expand_exp env ctxt e1 in |
| ArithE e1' |
| | HoleE (`Num i) -> |
| (match List.nth_opt ctxt.args i with |
| | None -> raise Arity_mismatch |
| | Some arg -> |
| ctxt.next := i + 1; |
| ctxt.max := max !(ctxt.max) i; |
| match !(arg.it) with |
| | ExpA eJ -> ArithE eJ |
| | _ -> CallE ("" $ e.at, [arg]) |
| ) |
| | HoleE `Next -> |
| (expand_exp env ctxt (HoleE (`Num !(ctxt.next)) $ e.at)).it |
| | HoleE `Rest -> |
| let args = |
| try Lib.List.drop !(ctxt.next) ctxt.args |
| with Failure _ -> raise Arity_mismatch |
| in |
| ctxt.next := List.length ctxt.args; |
| ctxt.max := max !(ctxt.max) (!(ctxt.next) - 1); |
| SeqE (List.map (function |
| | {it = {contents = ExpA e}; _} -> |
| (* Guard exp arguments against being reinterpreted as sym |
| * when expanding a grammar id, by wrapping in ArithE. *) |
| Source.(ArithE e $ e.at) |
| | a -> CallE ("" $ a.at, [a]) $ a.at |
| ) args) |
| | HoleE `None -> |
| HoleE `None |
| | FuseE (e1, e2) -> |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| FuseE (e1', e2') |
| | UnparenE e1 -> |
| let e1' = expand_exp env ctxt e1 in |
| UnparenE e1' |
| | LatexE s -> |
| LatexE s |
| ) $ e.at |
| |
| and expand_expfield env ctxt (atom, e) = |
| let atom' = expand_atom env ctxt atom in |
| let e' = expand_exp env ctxt e in |
| (atom', e') |
| |
| and expand_path env ctxt p = |
| (match p.it with |
| | RootP -> RootP |
| | IdxP (p1, e1) -> |
| let p1' = expand_path env ctxt p1 in |
| let e1' = expand_exp env ctxt e1 in |
| IdxP (p1', e1') |
| | SliceP (p1, e1, e2) -> |
| let p1' = expand_path env ctxt p1 in |
| let e1' = expand_exp env ctxt e1 in |
| let e2' = expand_exp env ctxt e2 in |
| SliceP (p1', e1', e2') |
| | DotP (p1, atom) -> |
| let p1' = expand_path env ctxt p1 in |
| let atom' = expand_atom env ctxt atom in |
| DotP (p1', atom') |
| ) $ p.at |
| |
| and expand_arg env ctxt a = |
| ref (match !(a.it) with |
| | ExpA e -> ExpA (expand_exp env ctxt e) |
| | a' -> a' |
| ) $ a.at |
| |
| |
| (* Attempt to expand the application `id(args)`, using the hints `show`, |
| * and the function `render` for rendering the resulting expression. |
| * If no hint can be found, fall back to the default of rendering `f`. |
| *) |
| let rec render_expand render env (show : hints ref) macro id args possibly_flattened f : string * arg list = |
| match Map.find_opt id.it !show with |
| | None -> |
| (* |
| (if env.config.macros_for_ids then ( |
| Printf.printf "[expand not found %s]\n%!" id.it; |
| Printf.printf "%s\n%!" (String.concat " " (List.map fst (Map.bindings !show))) |
| )); |
| *) |
| f (), [] |
| | Some showexps -> |
| let templ = macro_template env macro id.it in |
| let rec attempt = function |
| | [] -> f (), [] |
| | showexp::showexps' -> |
| try |
| (* |
| (if env.config.macros_for_ids then |
| let m = match templ with None -> "-" | Some l -> String.concat "%" l in |
| Printf.printf "[expand attempt %s %s] %s\n%!" id.it m (El.Print.string_of_exp showexp) |
| ); |
| *) |
| let env' = local_env env in |
| let ctxt = {macro; templ; args; next = ref 1; max = ref 0} in |
| let e = expand_exp env' ctxt showexp in |
| if possibly_flattened && !(ctxt.max) < List.length args - 1 then |
| (* Not all args used, retry with unflattened tail *) |
| let args1, args2 = Lib.List.split !(ctxt.max) args in |
| let at = Source.over_region (List.map Source.at args2) in |
| render_expand render env show macro id |
| (args1 @ [ref (ExpA (SeqE (List.map exp_of_arg args2) $ at)) $ at]) |
| true f |
| else |
| let args' = Lib.List.drop (!(ctxt.max) + 1) args in |
| (* Avoid cyclic expansion *) |
| show := Map.remove id.it !show; |
| Fun.protect (fun () -> render env' e, args') |
| ~finally:(fun () -> show := Map.add id.it showexps !show) |
| with Arity_mismatch -> attempt showexps' |
| (* HACK: Ignore arity mismatches, such that overloading notation works, |
| * e.g., using CONST for both instruction and relation. *) |
| in attempt showexps |
| |
| (* Render the application `id(args)`, using the hints `show`, |
| * and the function `render_id`, `render_exp` for rendering the id and |
| * possible show expansion results, respectively. |
| *) |
| let render_apply render_id render_exp env show macro id args = |
| (* Pre-render id here, since we cannot distinguish it from other id classes later. *) |
| let arg0 = arg_of_exp (LatexE (render_id env id) $ id.at) in |
| render_expand render_exp env show macro id (arg0::args) false |
| (fun () -> |
| let n = count_sub id.it in |
| if n > 0 && n <= List.length args then |
| (* Handle subscripting *) |
| let subs, args' = Lib.List.split n args in |
| "{" ^ render_id env id ^ |
| "}_{" ^ |
| String.concat ", " (List.map (!render_arg_fwd env) (List.flatten (List.map as_tup_arg subs))) ^ |
| "}" ^ !render_args_fwd env args' |
| else |
| render_id env id ^ !render_args_fwd env args |
| ) |> fst |
| |
| |
| (* Identifiers *) |
| |
| let is_digit = Lib.Char.is_digit_ascii |
| let is_upper = Lib.Char.is_uppercase_ascii |
| let lower = String.lowercase_ascii |
| |
| let dash_id = Str.(global_replace (regexp "-") "{-}") |
| let quote_id = Str.(global_replace (regexp "_+") "\\_") |
| let shrink_id = Str.(global_replace (regexp "[0-9]+") "{\\\\scriptstyle \\0}") |
| let rekerni_id = Str.(global_replace (regexp "[.]") "{.}") |
| let rekernl_id = Str.(global_replace (regexp "\\([a-z]\\)[{]") "\\1{\\kern-0.1em") |
| let rekernr_id = Str.(global_replace (regexp "[}]\\([a-z{]\\)") "\\kern-0.1em}\\1") |
| let macrofy_id = Str.(global_replace (regexp "[_.]") "") |
| |
| let id_style = function |
| | `Var -> "\\mathit" |
| | `Func -> "\\mathrm" |
| | `Atom -> "\\mathsf" |
| | `Token -> "\\mathtt" |
| |
| let render_id' env style id templ = |
| El.Debug.(log "render.id" |
| (fun _ -> fmt "%s %s" id |
| ""(*mapping (fun xs -> string_of_int (List.length xs)) !(env.macro_atom)*)) |
| (fun s -> s) |
| ) @@ fun _ -> |
| assert (templ = None || env.config.macros_for_ids); |
| if templ <> None && not (all_sub id) then |
| "\\" ^ macrofy_id (expand_name templ id) |
| else |
| (* |
| if env.config.macros_for_ids && String.length id > 2 && (style = `Var || style = `Func) then |
| Printf.eprintf "[id w/o macro] %s%s\n%!" (if style = `Func then "$" else "") id; |
| *) |
| let id' = quote_id id in |
| let id'' = |
| (* TODO(3, rossberg): provide a way to selectively shrink uppercase vars, esp after # *) |
| match style with |
| | `Var | `Func -> rekernl_id (rekernr_id (shrink_id id')) |
| | `Atom -> rekerni_id (shrink_id (lower id')) |
| | `Token -> |
| (* HACK for now: if first char is upper, remove *) |
| if String.length id' > 1 && is_upper id'.[0] |
| then String.sub id' 1 (String.length id' - 1) |
| else id' |
| in |
| if style = `Var && String.length id'' = 1 && Lib.Char.is_letter_ascii id''.[0] |
| then id'' |
| else id_style style ^ "{" ^ id'' ^ "}" |
| |
| let rec render_id_sub style show macro env first at = function |
| | [] -> "" |
| | ""::ss -> |
| (* Ignore leading underscores *) |
| render_id_sub style show macro env first at ss |
| | s::ss when style = `Var && not first && is_upper s.[0] -> |
| (* In vars, underscore renders subscripts; subscripts may be atoms *) |
| render_id_sub `Atom show (ref Map.empty) env first at (lower s :: ss) |
| | s1::""::[] -> |
| (* Keep trailing underscores for macros (and chop later) *) |
| render_id_sub style show macro env first at [s1 ^ "_"] |
| | s1::""::s2::ss -> |
| (* Record double underscores as regular underscore characters *) |
| render_id_sub style show macro env first at ((s1 ^ "__" ^ s2)::ss) |
| | s1::s2::ss when style = `Atom && is_upper s2.[0] -> |
| (* Record single underscores in atoms as regular characters as well *) |
| render_id_sub `Atom show (ref Map.empty) env first at ((s1 ^ "_" ^ lower s2)::ss) |
| | s::ss -> |
| (* All other underscores are rendered as subscripts *) |
| let s1, sub = split_sub s in (* previous steps might have appended underscore *) |
| let s2, ticks = split_ticks s1 in |
| let s3 = s2 ^ sub in |
| let s4 = |
| if String.for_all is_digit s3 then s3 else |
| if not first then render_id' env style s2 None else |
| render_expand !render_exp_fwd env show macro |
| (s3 $ at) [ref (ExpA (VarE (s3 $ at, []) $ at)) $ at] false |
| (fun () -> render_id' env style s2 (macro_template env macro s3)) |> fst |
| in |
| let s5 = s4 ^ ticks in |
| (if String.length s5 = 1 then s5 else "{" ^ s5 ^ "}") ^ |
| match ss with |
| | [] -> "" |
| | [_] -> "_" ^ render_id_sub `Var env.show_var (ref Map.empty) env false at ss |
| | _ -> "_{" ^ render_id_sub `Var env.show_var (ref Map.empty) env false at ss ^ "}" |
| |
| and render_id style show macro env id = |
| match id.it with |
| | "bool" -> "\\mathbb{B}" |
| | "nat" -> "\\mathbb{N}" |
| | "int" -> "\\mathbb{Z}" |
| | "rat" -> "\\mathbb{Q}" |
| | "real" -> "\\mathbb{R}" |
| | "text" -> "\\mathbb{T}" |
| | _ -> |
| render_id_sub style show macro env true id.at (String.split_on_char '_' id.it) |
| |
| let render_typid env id = render_id `Var env.show_typ env.macro_typ env id |
| let render_varid env id = render_id `Var env.show_var env.macro_var env id |
| let render_defid env id = render_id `Func env.show_def env.macro_def env id |
| let render_gramid env id = render_id `Token env.show_gram env.macro_gram env id |
| |
| let render_ruleid env id1 id2 = |
| let id1' = |
| match Map.find_opt id1.it !(env.name_rel) with |
| | None -> id1.it |
| | Some [] -> assert false |
| | Some ({it = TextE s; _}::_) -> s |
| | Some ({at; _}::_) -> |
| error at "malformed `name` hint for relation" |
| in |
| let id2' = if id2.it = "" then "" else "-" ^ id2.it in |
| "\\textsc{\\scriptsize " ^ dash_id (quote_id (id1' ^ id2')) ^ "}" |
| |
| let render_rule_deco env pre id1 id2 post = |
| if not env.deco_rule then "" else |
| pre ^ "{[" ^ render_ruleid env id1 id2 ^ "]}" ^ post |
| |
| |
| let render_atom env atom = |
| El.Debug.(log_if "render.atom" (atom.it = Atom.Atom "CALL_INDIRECT") |
| (fun _ -> fmt "%s/%s %s" (el_atom atom) atom.note.def |
| (string_of_int (List.length (List.flatten (Option.to_list (Map.find_opt (typed_id atom).it !(env.macro_atom)))))) |
| (*mapping (fun xs -> string_of_int (List.length xs)) !(env.macro_atom)*)) |
| (fun s -> s) |
| ) @@ fun _ -> |
| let open Atom in |
| let id = typed_id atom in |
| let arg = arg_of_exp (AtomE atom $ atom.at) in |
| render_expand !render_exp_fwd env env.show_atom env.macro_atom id [arg] false |
| (fun () -> |
| (* |
| if env.config.macros_for_ids then |
| Printf.eprintf "[render_atom %s @ %s] id=%s def=%s macros: %s (%s)\n%!" |
| (Atom.to_string atom) (Source.string_of_region atom.at) id.it atom.note.Atom.def |
| (String.concat "%" (List.concat (Option.to_list (macro_template env env.macro_atom id.it)))) |
| ""(*(String.concat " " (List.map fst (Map.bindings !(env.macro_atom))))*); |
| *) |
| (* |
| if env.config.macros_for_ids && atom.note.def = "" then |
| error atom.at |
| ("cannot infer type of notation `" ^ El.Print.string_of_atom atom ^ "`"); |
| *) |
| match atom.it with |
| | Atom "_" -> render_id' env `Atom "_" None |
| | Atom s when s.[0] = '_' -> "" |
| (* Always keep punctuation as non-macros *) |
| | Dot -> "{.}" |
| | Comma -> "," |
| | Semicolon -> ";" |
| | Colon | ColonSub -> ":" |
| | Equal | EqualSub -> "=" |
| | Less -> "<" |
| | Greater -> ">" |
| | Quest -> "{}^?" |
| | Star -> "{}^\\ast" |
| | Iter -> "{}^+" |
| | Plus -> "+" |
| | Minus -> "-" |
| | PlusMinus -> "\\pm" |
| | MinusPlus -> "\\mp" |
| | Slash -> "/" |
| | Not -> "\\neg" |
| | And -> "\\land" |
| | Or -> "\\lor" |
| | LParen -> "(" |
| | RParen -> ")" |
| | LBrack -> "{}[" |
| | RBrack -> "]" |
| | LBrace -> "\\{" |
| | RBrace -> "\\}" |
| | _ -> |
| assert (not (nonmacro_atom atom)); |
| match macro_template env env.macro_atom id.it with |
| | Some _ as templ when env.config.macros_for_ids (*&& atom.note.def <> ""*) -> |
| render_id' env `Atom (chop_sub (untyped_id id).it) templ |
| | _ -> |
| match atom.it with |
| | Atom s -> render_id' env `Atom (chop_sub s) None |
| | Dot2 -> ".." |
| | Dot3 -> "\\ldots" |
| | Assign -> ":=" |
| | NotEqual -> "\\neq" |
| | LessEqual-> "\\leq" |
| | GreaterEqual -> "\\geq" |
| | Equiv | EquivSub -> "\\equiv" |
| | Approx | ApproxSub -> "\\approx" |
| | Arrow | ArrowSub -> "\\rightarrow" |
| | Arrow2 | Arrow2Sub -> "\\Rightarrow" |
| | Sub -> "\\leq" |
| | Sup -> "\\geq" |
| | SqArrow | SqArrowSub -> "\\hookrightarrow" |
| | SqArrowStar | SqArrowStarSub -> "\\hookrightarrow^\\ast" |
| | Cat -> "\\oplus" |
| | Bar -> "\\mid" |
| | BigAnd -> "\\bigwedge" |
| | BigOr -> "\\bigvee" |
| | BigAdd -> "\\Sigma" |
| | BigMul -> "\\Pi" |
| | BigCat -> "\\bigoplus" |
| | Turnstile | TurnstileSub -> "\\vdash" |
| | Tilesturn | TilesturnSub -> "\\dashv" |
| | _ -> "\\" ^ Atom.name atom |
| ) |> fst |
| |
| |
| let render_text s = |
| let buf = Buffer.create (String.length s + 32) in |
| Buffer.add_string buf "\\mbox{‘\\texttt{"; |
| for i = 0 to String.length s - 1 do |
| match s.[i] with |
| | '\'' -> Buffer.add_string buf "\\kern0.03em{'}\\kern0.03em" (* TODO: not typeset in TT *) |
| | '"' -> Buffer.add_string buf "\\kern-0.02em{'}\\kern-0.05em{'}\\kern-0.02em" (* TODO: not typeset in TT *) |
| | '#' -> Buffer.add_string buf "\\#" |
| | '$' -> Buffer.add_string buf "\\$" |
| | '%' -> Buffer.add_string buf "\\%" |
| | '&' -> Buffer.add_string buf "\\&" |
| | '_' -> Buffer.add_string buf "\\_" |
| | '=' -> Buffer.add_string buf "{=}" |
| | '<' -> Buffer.add_string buf "{<}" |
| | '>' -> Buffer.add_string buf "{>}" |
| | '-' -> Buffer.add_string buf "{-}" |
| | '(' -> Buffer.add_string buf "{(}" |
| | ')' -> Buffer.add_string buf "{)}" |
| | '{' -> Buffer.add_string buf "{\\{}" |
| | '}' -> Buffer.add_string buf "{\\}}" |
| | '[' -> Buffer.add_string buf "{[}" |
| | ']' -> Buffer.add_string buf "{]}" |
| | '\\' -> Buffer.add_string buf "\\(\\mathtt{\\backslash}\\)" (* TODO: not typeset in TT *) |
| | '^' -> Buffer.add_string buf "\\(\\mathtt{\\hat{~~}}\\)" |
| | '`' -> Buffer.add_string buf "\\(\\mathtt{\\grave{~~}}\\)" |
| | '~' -> Buffer.add_string buf "\\(\\mathtt{\\tilde{~~}}\\)" |
| | c -> Buffer.add_char buf c |
| done; |
| Buffer.add_string buf "}’}"; |
| Buffer.contents buf |
| |
| |
| (* Operators *) |
| |
| let render_unop = function |
| | `NotOp -> "\\neg" |
| | `PlusOp -> "+" |
| | `MinusOp -> "-" |
| | `PlusMinusOp -> "\\pm" |
| | `MinusPlusOp -> "\\mp" |
| |
| let render_binop = function |
| | `AndOp -> "\\land" |
| | `OrOp -> "\\lor" |
| | `ImplOp -> "\\Rightarrow" |
| | `EquivOp -> "\\Leftrightarrow" |
| | `AddOp -> "+" |
| | `SubOp -> "-" |
| | `MulOp -> "\\cdot" |
| | `DivOp -> "/" |
| | `ModOp -> "\\mathbin{\\mathrm{mod}}" |
| | `PowOp -> assert false |
| |
| let render_cmpop = function |
| | `EqOp -> "=" |
| | `NeOp -> "\\neq" |
| | `LtOp -> "<" |
| | `GtOp -> ">" |
| | `LeOp -> "\\leq" |
| | `GeOp -> "\\geq" |
| |
| let render_dots = function |
| | Dots -> [Row [Col "\\dots"]] |
| | NoDots -> [] |
| |
| |
| (* Iteration *) |
| |
| let rec render_iter env = function |
| | Opt -> "^?" |
| | List -> "^\\ast" |
| | List1 -> "^{+}" |
| | ListN ({it = ParenE e; _}, None) | ListN (e, None) -> |
| "^{" ^ render_exp env e ^ "}" |
| | ListN (e, Some id) -> |
| "^{" ^ render_varid env id ^ "<" ^ render_exp env e ^ "}" |
| |
| |
| (* Types *) |
| |
| and render_typ env t : string = |
| render_exp env (exp_of_typ t) |
| |
| and render_nottyp env t : table = |
| (* |
| Printf.eprintf "[render_nottyp %s] %s\n%!" |
| (string_of_region t.at) (El.Print.string_of_typ t); |
| *) |
| match t.it with |
| | StrT (dots1, ts, tfs, dots2) -> |
| let render env = function |
| | `Dots -> render_dots Dots |
| | `Typ t -> render_nottyp env t |
| | `TypField tf -> render_typfield env tf |
| in |
| [Row [Col ( |
| "\\{ " ^ |
| render_table env "@{}" ["l"; "l"] 0 0 |
| (concat_table "" (render_nl_list env (`H, ", ") render ( |
| (match dots1 with Dots -> [Elem `Dots] | NoDots -> []) @ |
| map_nl_list (fun t -> `Typ t) ts @ |
| map_nl_list (fun tf -> `TypField tf) tfs @ |
| (match dots2 with Dots -> [Elem `Dots] | NoDots -> []) |
| )) [Row [Col " \\}"]]) |
| )]] |
| | CaseT (dots1, ts, tcs, dots2) -> |
| let render env = function |
| | `Dots -> render_dots Dots |
| | `Typ t -> render_nottyp env t |
| | `TypCase tc -> render_typcase env tc |
| in |
| let rhss = |
| render_nl_list env (`H, "~~|~~") render ( |
| (match dots1 with Dots -> [Elem `Dots] | NoDots -> []) @ |
| map_nl_list (fun t -> `Typ t) ts @ |
| map_nl_list (fun tc -> `TypCase tc) tcs @ |
| (match dots2 with Dots -> [Elem `Dots] | NoDots -> []) |
| ) |
| in |
| if env.config.display then |
| rhss |
| else |
| [Row [Col (string_of_table " ~~|~~ " " ~~|~~ " "" "" rhss)]] |
| | ConT tcon -> |
| render_typcon env tcon |
| | RangeT tes -> |
| render_nl_list env (`H, "~~|~~") render_typenum tes |
| | NumT `NatT -> |
| [Row [Col "0 ~~|~~ 1 ~~|~~ 2 ~~|~~ \\dots"]] |
| | NumT `IntT -> |
| [Row [Col "\\dots ~~|~~ {-2} ~~|~~ {-1} ~~|~~ 0 ~~|~~ 1 ~~|~~ 2 ~~|~~ \\dots"]] |
| | _ -> |
| [Row [Col (render_typ env t)]] |
| |
| |
| and render_typfield env (atom, (t, prems), _hints) = |
| prefix_rows_hd |
| [Col (render_fieldname env atom ^ "~" ^ render_typ env t)] |
| (render_conditions env prems) |
| |
| and render_typcase env (_atom, (t, prems), _hints) : row list = |
| prefix_rows_hd [Col (render_typ env t)] (render_conditions env prems) |
| |
| and render_typcon env ((t, prems), _hints) : row list = |
| prefix_rows_hd [Col (render_typ env t)] (render_conditions env prems) |
| |
| and render_typenum env (e, eo) : row list = |
| let r = |
| match eo with |
| | None -> "" |
| | Some e2 -> " ~~|~~ \\ldots ~~|~~ " ^ render_exp env e2 |
| in |
| [Row [Col (render_exp env e ^ r)]] |
| |
| |
| (* Expressions *) |
| |
| and is_atom_exp_with_show env e = |
| match e.it with |
| | AtomE atom when Map.mem (typed_id atom).it !(env.show_atom) -> true |
| | _ -> false |
| |
| and flatten_fuse_exp_rev e = |
| match e.it with |
| | FuseE (e1, e2) -> e2 :: flatten_fuse_exp_rev e1 |
| | _ -> [e] |
| |
| and render_exp env e = |
| (* |
| Printf.eprintf "[render_exp %s] %s\n%!" |
| (string_of_region e.at) (El.Print.string_of_exp e); |
| *) |
| match e.it with |
| | VarE (id, args) -> |
| render_apply render_varid render_exp env env.show_typ env.macro_typ id args |
| | BoolE b -> |
| render_atom env (Atom.(Atom (string_of_bool b) $$ e.at % info "bool")) |
| | NumE (`DecOp, `Nat n) -> Z.to_string n |
| | NumE (`HexOp, `Nat n) -> |
| let fmt = |
| if n < Z.of_int 0x100 then "%02X" else |
| if n < Z.of_int 0x10000 then "%04X" else |
| "%X" |
| in "\\mathtt{0x" ^ Z.format fmt n ^ "}" |
| | NumE (`CharOp, `Nat n) -> |
| let fmt = |
| if n < Z.of_int 0x100 then "%02X" else |
| if n < Z.of_int 0x10000 then "%04X" else |
| "%X" |
| in "\\mathrm{U{+}" ^ Z.format fmt n ^ "}" |
| | NumE (`AtomOp, `Nat n) -> |
| let atom = {it = Atom.Atom (Z.to_string n); at = e.at; note = Atom.info "nat"} in |
| render_atom (without_macros true env) atom |
| | NumE _ -> assert false |
| | TextE t -> render_text t |
| | CvtE (e1, _) -> render_exp env e1 |
| | UnE (`NotOp, {it = MemE (e1, e2); _}) -> |
| render_exp env e1 ^ " \\notin " ^ render_exp env e2 |
| | UnE (op, e2) -> "{" ^ render_unop op ^ render_exp env e2 ^ "}" |
| | BinE (e1, `PowOp, ({it = ParenE e2; _ } | e2)) -> |
| "{" ^ render_exp env e1 ^ "^{" ^ render_exp env e2 ^ "}}" |
| | BinE (({it = NumE (`DecOp, `Nat _); _} as e1), `MulOp, |
| ({it = VarE _ | CallE (_, []) | ParenE _; _ } as e2)) -> |
| render_exp env e1 ^ " \\, " ^ render_exp env e2 |
| | BinE (e1, op, e2) -> |
| render_exp env e1 ^ space render_binop op ^ render_exp env e2 |
| | CmpE (e1, op, e2) -> |
| render_exp env e1 ^ space render_cmpop op ^ render_exp env e2 |
| | EpsE -> "\\epsilon" |
| | AtomE atom -> render_atom env atom |
| | SeqE es -> |
| (match List.find_opt (is_atom_exp_with_show env) es with |
| | Some {it = AtomE atom; _} -> |
| let args = List.map arg_of_exp es in |
| (match render_expand render_exp env env.show_atom env.macro_atom |
| (typed_id atom) args true (fun () -> render_exp_seq env e.at es) |
| with |
| | _, args' when List.length args' + 1 = List.length args -> |
| (* HACK for nullary contructors *) |
| (* TODO(4, rossberg): handle inner constructors more generally *) |
| render_exp_seq env e.at es |
| | s, _ -> s |
| ) |
| | _ -> render_exp_seq env e.at es |
| ) |
| | ListE es -> "{}[" ^ render_exp_seq env e.at es ^ "]" |
| | IdxE (e1, e2) -> render_exp env e1 ^ "{}[" ^ render_exp env e2 ^ "]" |
| | SliceE (e1, e2, e3) -> |
| render_exp env e1 ^ |
| "{}[" ^ render_exp env e2 ^ " : " ^ render_exp env e3 ^ "]" |
| | UpdE (e1, p, e2) -> |
| render_exp env e1 ^ |
| "{}[" ^ render_path env p ^ " = " ^ render_exp env e2 ^ "]" |
| | ExtE (e1, p, e2) -> |
| render_exp env e1 ^ |
| "{}[" ^ render_path env p ^ " \\mathrel{{=}{\\oplus}} " ^ render_exp env e2 ^ "]" |
| | StrE efs -> |
| if not (List.mem Nl efs) then |
| let sep = if env.config.display then ",\\;\\allowbreak " else ",\\; " in |
| "\\{ " ^ |
| concat_map_nl sep "\\\\\n " (render_expfield env) efs ^ " \\}" |
| else |
| "\\{ " ^ |
| "\\begin{array}[t]{@{}l@{}}\n" ^ |
| concat_map_nl ",\\; " "\\\\\n " (render_expfield env) efs ^ " \\}" ^ |
| "\\end{array}" |
| | DotE (e1, atom) -> render_exp env e1 ^ "{.}" ^ render_fieldname env atom |
| | CommaE (e1, e2) -> render_exp env e1 ^ ", " ^ render_exp env e2 |
| | CatE (e1, e2) -> render_exp env e1 ^ " \\oplus " ^ render_exp env e2 |
| | MemE (e1, e2) -> render_exp env e1 ^ " \\in " ^ render_exp env e2 |
| | LenE e1 -> "{|" ^ render_exp env e1 ^ "|}" |
| | SizeE id -> "||" ^ render_gramid env id ^ "||" |
| | ParenE ({it = SeqE [{it = AtomE atom; _}; _]; _} as e1) |
| when render_atom env atom = "" -> |
| render_exp env e1 |
| | ParenE e1 -> "(" ^ render_exp env e1 ^ ")" |
| | TupE es -> "(" ^ render_exps ", " env es ^ ")" |
| | InfixE (e1, atom, e2) -> |
| let id = typed_id atom in |
| let ea = AtomE atom $ atom.at in |
| let args = List.map arg_of_exp ([e1; ea] @ as_seq_exp e2) in |
| render_expand render_exp env env.show_atom env.macro_atom id args false |
| (fun () -> |
| (* Handle subscripting and unary uses *) |
| (match Atom.is_sub atom, (as_arith_exp e1).it with |
| | false, SeqE [] -> "{" ^ render_atom env atom ^ "}\\, " |
| | true, SeqE [] -> "{" ^ render_atom env atom ^ "}_" |
| | false, _ -> render_exp env e1 ^ space (render_atom env) atom |
| | true, _ -> render_exp env e1 ^ " " ^ render_atom env atom ^ "_" |
| ) ^ |
| (match Atom.is_sub atom, e2.it with |
| | true, SeqE (e21::e22::es2) -> |
| "{" ^ render_exps "," env (as_tup_exp e21) ^ "} " ^ render_exp_seq env e2.at (e22::es2) |
| | true, _ -> "{" ^ render_exps "," env (as_tup_exp e2) ^ "} {}" |
| | false, _ -> render_exp env e2 |
| ) |
| ) |> fst |
| | BrackE (l, e1, r) -> |
| let id = typed_id l in |
| let el = AtomE l $ l.at in |
| let er = AtomE r $ r.at in |
| let args = List.map arg_of_exp ([el] @ as_seq_exp e1 @ [er]) in |
| render_expand render_exp env env.show_atom env.macro_atom id args true |
| (fun () -> render_atom env l ^ space (render_exp env) e1 ^ render_atom env r) |> fst |
| | CallE (id, [arg]) when id.it = "" -> (* expansion result only *) |
| render_arg env arg |
| | CallE (id, args) when id.it = "" -> (* expansion result only *) |
| render_args env args |
| | CallE (id, args) -> |
| render_apply render_defid render_exp env env.show_def env.macro_def id args |
| | IterE (e1, iter) -> "{" ^ render_exp env e1 ^ render_iter env iter ^ "}" |
| | TypE ({it = VarE ({it = "_"; _}, []); _}, t) -> |
| (* HACK for rendering shorthand parameters that have been turned into args |
| * with arg_of_param, for use in render_apply. *) |
| render_typ env t |
| | TypE (e1, _) | ArithE e1 -> render_exp env e1 |
| | FuseE (e1, e2) -> |
| (* TODO(2, rossberg): HACK for printing t.LOADn_sx (replace with invisible parens) *) |
| let e2' = as_paren_exp (fuse_exp e2 true) in |
| let es = e2' :: flatten_fuse_exp_rev e1 in |
| String.concat "" (List.map (fun e -> "{" ^ render_exp env e ^ "}") (List.rev es)) |
| | UnparenE {it = ArithE e1; _} -> render_exp env (UnparenE e1 $ e.at) |
| | UnparenE ({it = ParenE e1; _} | e1) -> render_exp env e1 |
| | HoleE `None -> "" |
| | HoleE _ -> error e.at "misplaced hole" |
| | LatexE s -> s |
| |
| and render_exps sep env es = |
| concat sep (List.filter ((<>) "") (List.map (render_exp env) es)) |
| |
| and render_exp_seq env at = function |
| | [] -> "" |
| | es when env.config.display && (List.hd es).at.left.line < (Lib.List.last es).at.right.line -> |
| "\\begin{array}[t]{@{}l@{}} " ^ render_exp_seq' env at es ^ " \\end{array}" |
| | es -> render_exp_seq' env at es |
| |
| and render_exp_seq' env at = function |
| | [] -> "" |
| | e1::e2::es when ends_sub_exp e1 -> |
| (* Handle subscripting *) |
| let s1 = |
| "{" ^ render_exp env e1 ^ "}_{" ^ |
| render_exps "," env (as_tup_exp e2) ^ "}" |
| in |
| let s2 = render_exp_seq' env at es in |
| if s1 <> "" && s2 <> "" then s1 ^ "\\," ^ s2 else s1 ^ s2 |
| | e1::e2::es when is_atom_exp_with_show env e2 && es <> [] -> |
| render_exp_seq' env at [e1; SeqE (e2::es) $ at] |
| | e1::e2::es when env.config.display && e1.at.right.line < e2.at.left.line -> |
| let s1 = render_exp env e1 in |
| let s2 = render_exp_seq' env at (e2::es) in |
| s1 ^ " \\\\\n " ^ s2 |
| | e1::es -> |
| let s1 = render_exp env e1 in |
| let s2 = render_exp_seq' env at es in |
| if s1 <> "" && s2 <> "" then s1 ^ "~" ^ s2 else s1 ^ s2 |
| |
| and render_expfield env (atom, e) = |
| render_fieldname env atom ^ "~" ^ render_exp env e |
| |
| and render_path env p = |
| match p.it with |
| | RootP -> "" |
| | IdxP (p1, e) -> render_path env p1 ^ "{}[" ^ render_exp env e ^ "]" |
| | SliceP (p1, e1, e2) -> |
| render_path env p1 ^ "{}[" ^ render_exp env e1 ^ " : " ^ render_exp env e2 ^ "]" |
| | DotP (p1, atom) -> |
| render_path env p1 ^ "{.}" ^ render_fieldname env atom |
| |
| and render_fieldname env atom = |
| El.Debug.(log "render.fieldname" |
| (fun _ -> fmt "%s %s" (el_atom atom) |
| (mapping (fun xs -> string_of_int (List.length xs)) !(env.show_atom))) |
| (fun s -> s) |
| ) @@ fun _ -> |
| render_atom env atom |
| |
| |
| (* Premises *) |
| |
| and render_prem env prem = |
| match prem.it with |
| | VarPr _ -> assert false |
| | RulePr (_id, _ps, e) -> render_exp env e |
| | IfPr e -> render_exp env e |
| | ElsePr -> error prem.at "misplaced `otherwise` premise" |
| | IterPr ({it = IterPr _; _} as prem', iter) -> |
| "{" ^ render_prem env prem' ^ "}" ^ render_iter env iter |
| | IterPr (prem', iter) -> |
| "(" ^ render_prem env prem' ^ ")" ^ render_iter env iter |
| |
| and word s = "\\mbox{" ^ s ^ "}" |
| |
| and render_conditions env prems : row list = |
| let prems' = filter_nl_list (function {it = VarPr _; _} -> false | _ -> true) prems in |
| if prems' = [] then [] else |
| let br, prems'' = |
| match prems' with |
| | Nl::Nl::prems'' -> |
| (* If premises start with double empty line, break and align below LHS. *) |
| [Br `Wide], prems'' |
| | Nl::prems'' -> |
| (* If premises start with an empty line, break and align below RHS. *) |
| [Br `Narrow], prems'' |
| | _ -> [], prems' |
| in |
| let prems''', pre = |
| match prems'' with |
| | [Elem {it = ElsePr; _}] -> [], word "otherwise" |
| | (Elem {it = ElsePr; _})::prems''' -> prems''', word "otherwise, if" |
| | _ -> prems'', word "if" |
| in |
| prefix_row br ( |
| match prems''' with |
| | [] -> [Row [Col ("\\quad " ^ pre)]] |
| | [Elem prem] -> [Row [Col ("\\quad " ^ pre ^ "~ " ^ render_prem env prem)]] |
| | _ -> |
| [Row [Col |
| ("\\quad\n" ^ render_table env "" ["l"] 0 0 |
| (map_rows (merge_cols "~ ") |
| (prefix_rows [Col pre] [Col "{\\land}"] |
| (render_nl_list env (`V, " \\and ") render_condition prems''') |
| ) |
| ) |
| ) |
| ]] |
| ) |
| |
| and render_condition env prem = |
| [Row [Col (render_prem env prem)]] |
| |
| |
| (* Grammars *) |
| |
| and render_exp_as_sym env e = |
| render_sym env (sym_of_exp e) |
| |
| and render_sym env g : string = |
| (* |
| Printf.eprintf "[render_sym %s] %s\n%!" |
| (string_of_region g.at) (El.Print.string_of_sym g); |
| *) |
| match g.it with |
| | VarG (id, args) -> |
| render_apply render_gramid render_exp_as_sym |
| env env.show_gram env.macro_gram id args |
| | NumG (`DecOp, n) -> Z.to_string n |
| | NumG (`HexOp, n) -> |
| let fmt = |
| if n < Z.of_int 0x100 then "%02X" else |
| if n < Z.of_int 0x10000 then "%04X" else |
| "%X" |
| in "\\mathtt{0x" ^ Z.format fmt n ^ "}" |
| | NumG (`CharOp, n) -> |
| let fmt = |
| if n < Z.of_int 0x100 then "%02X" else |
| if n < Z.of_int 0x10000 then "%04X" else |
| "%X" |
| in "\\mathrm{U{+}" ^ Z.format fmt n ^ "}" |
| | NumG (`AtomOp, n) -> "\\mathtt{" ^ Z.to_string n ^ "}" |
| | TextG t -> render_text t |
| | EpsG -> "\\epsilon" |
| | SeqG gs -> render_sym_seq env gs |
| | AltG gs -> render_syms " ~~|~~ " env gs |
| | RangeG (g1, g2) -> |
| render_sym env g1 ^ " ~~|~~ \\ldots ~~|~~ " ^ render_sym env g2 |
| | ParenG g1 -> "(" ^ render_sym env g1 ^ ")" |
| | TupG gs -> "(" ^ concat ", " (List.map (render_sym env) gs) ^ ")" |
| | IterG (g1, iter) -> "{" ^ render_sym env g1 ^ render_iter env iter ^ "}" |
| | ArithG e -> render_exp env e |
| | AttrG ({it = VarE (id, []); _}, g1) when id.it = "<implicit-prod-result>" -> |
| render_sym env g1 |
| | AttrG (e, g1) -> render_exp env e ^ "{:}" ^ render_sym env g1 |
| | FuseG (g1, g2) -> |
| "{" ^ render_sym env g1 ^ "}" ^ "{" ^ render_sym env g2 ^ "}" |
| | UnparenG ({it = ParenG g1; _} | g1) -> render_sym env g1 |
| |
| and render_syms sep env gs = |
| if env.config.display && List.exists ((=) Nl) gs then |
| "\\begin{array}[t]{@{}l@{}} " ^ |
| altern_map_nl sep " \\\\\n " (render_sym env) gs ^ |
| "\\end{array}" |
| else |
| altern_map_nl sep " " (render_sym env) gs |
| |
| and render_sym_seq env gs = |
| match El.Convert.filter_nl gs with |
| | [] -> "" |
| | gs' when env.config.display && (List.hd gs').at.left.line < (Lib.List.last gs').at.right.line |
| || List.exists ((=) Nl) gs -> |
| "\\begin{array}[t]{@{}l@{}} " ^ render_sym_seq' env gs ^ " \\end{array}" |
| | _ -> render_sym_seq' env gs |
| |
| and render_sym_seq' env = function |
| | [] -> "" |
| | (Elem g1)::(Elem g2)::gs when env.config.display && g1.at.right.line < g2.at.left.line -> |
| let s1 = render_sym env g1 in |
| let s2 = render_sym_seq' env (Elem g2::gs) in |
| s1 ^ " \\\\\n " ^ s2 |
| | (Elem g1)::gs -> |
| let s1 = render_sym env g1 in |
| let s2 = render_sym_seq' env gs in |
| if s1 <> "" && s2 <> "" then s1 ^ "~~" ^ s2 else s1 ^ s2 |
| | Nl::gs -> |
| let s = render_sym_seq' env gs in |
| " \\\\[0.8ex]\n " ^ s |
| |
| and render_prod env prod : row list = |
| match prod.it with |
| | SynthP (g, e, prems) -> |
| (match e.it, prems with |
| | (TupE [] | ParenE {it = SeqE []; _}), [] -> |
| [Row [Col (render_sym env g)]] |
| | VarE (id, []), _ when id.it = "<implicit-prod-result>" -> |
| prefix_rows_hd |
| [Col (render_sym env g)] |
| (render_conditions env prems) |
| | _ when not env.config.display -> |
| prefix_rows_hd |
| [Col (render_sym env g ^ " ~\\Rightarrow~ " ^ render_exp env e)] |
| (render_conditions env prems) |
| | _ -> |
| prefix_rows_hd |
| ( Col (render_sym env g) :: |
| Col "\\quad\\Rightarrow\\quad{}" :: |
| if g.at.right.line = e.at.left.line then |
| Col (render_exp env e) :: [] |
| else |
| Br `Narrow :: Col (render_exp env e) :: [] |
| ) |
| (render_conditions env prems) |
| ) |
| | RangeP (g1, e1, g2, e2) -> |
| let at = Source.after_region e1.at in |
| if g1.at.right.line = e2.at.left.line then |
| prefix_rows_hd |
| [Col ( |
| render_sym env g1 ^ " ~\\Rightarrow~ " ^ render_exp env e1 ^ |
| " ~~|~~ \\ldots ~~|~~ " ^ |
| render_sym env g2 ^ " ~\\Rightarrow~ " ^ render_exp env e2 |
| )] |
| [] |
| else |
| render_prod env (SynthP (g1, e1, []) $ g1.at) @ |
| render_prod env (SynthP (ArithG (AtomE Atom.(Dot3 $$ at % info "") $ at) $ at, |
| VarE ("<implicit-prod-result>" $ at, []) $ at, []) $ at) @ |
| render_prod env (SynthP (g2, e2, []) $ g2.at) |
| | EquivP (g1, g2, prems) -> |
| (match prems with |
| | _ when not env.config.display -> |
| prefix_rows_hd |
| [Col (render_sym env g1 ^ " ~\\equiv~ " ^ render_sym env g2)] |
| (render_conditions env prems) |
| | _ -> |
| prefix_rows_hd |
| ( Col (render_sym env g1) :: |
| Col "\\quad\\equiv\\quad{}" :: |
| if g1.at.right.line = g2.at.left.line then |
| Col (render_sym env g2) :: [] |
| else |
| Br `Narrow :: Col (render_sym env g2) :: [] |
| ) |
| (render_conditions env prems) |
| ) |
| |
| and render_gram env gram : table = |
| let (dots1, prods, _dots2) = gram.it in |
| let singleline = |
| List.length prods > 1 && gram.at.left.line = gram.at.right.line || |
| List.exists (function (Elem {it = RangeP _; _}) -> true | _ -> false) prods |
| in |
| let render env = function |
| | `Dots -> render_dots Dots |
| | `Prod p -> |
| let env' = |
| if singleline |
| then env_with_config env {env.config with display = false} |
| else env |
| in render_prod env' p |
| in |
| let rhss = |
| render_nl_list env (`H, "~~|~~") render ( |
| (match dots1 with Dots -> [Elem `Dots] | NoDots -> []) @ |
| map_nl_list (fun p -> `Prod p) prods @ |
| [] (* (match dots2 with Dots -> [Elem `Dots] | NoDots -> []) *) |
| ) |
| in |
| if env.config.display then |
| rhss |
| else |
| [Row [Col (string_of_table " ~~|~~ " " ~~|~~ " "" "" rhss)]] |
| |
| |
| (* Definitions *) |
| |
| and render_arg env arg = |
| match !(arg.it) with |
| | ExpA e -> render_exp env e |
| | TypA t -> render_typ env t |
| | GramA g -> render_sym env g |
| | DefA id -> render_defid env id |
| |
| and render_args env args = |
| match List.map (render_arg env) args with |
| | [] -> "" |
| | ss -> "(" ^ concat ", " ss ^ ")" |
| |
| let render_param env p = |
| match p.it with |
| | ExpP (id, t) -> if id.it = "_" then render_typ env t else render_varid env id |
| | TypP id -> render_typid env id |
| | GramP (id, _ps, _t) -> render_gramid env id |
| | DefP (id, _ps, _t) -> render_defid env id |
| |
| let _render_params env = function |
| | [] -> "" |
| | ps -> "(" ^ concat ", " (List.map (render_param env) ps) ^ ")" |
| |
| let () = render_exp_fwd := render_exp |
| let () = render_arg_fwd := render_arg |
| let () = render_args_fwd := render_args |
| |
| |
| let merge_typ t1 t2 = |
| match t1.it, t2.it with |
| | CaseT (dots1, ids1, cases1, _), CaseT (_, ids2, cases2, dots2) -> |
| CaseT (dots1, ids1 @ strip_nl ids2, cases1 @ strip_nl cases2, dots2) $ t1.at |
| | _, _ -> assert false |
| |
| let merge_gram gram1 gram2 = |
| match gram1.it, gram2.it with |
| | (dots1, prods1, _), (_, prods2, dots2) -> |
| (dots1, prods1 @ strip_nl prods2, dots2) $ gram1.at |
| |
| let rec merge_typdefs = function |
| | [] -> [] |
| | {it = TypD (id1, _, as1, t1, _); at; _}:: |
| {it = TypD (id2, _, as2, t2, _); _}::ds |
| when id1.it = id2.it && El.Eq.(eq_list eq_arg as1 as2) -> |
| let d' = TypD (id1, "" $ no_region, as1, merge_typ t1 t2, []) $ at in |
| merge_typdefs (d'::ds) |
| | d::ds -> |
| d :: merge_typdefs ds |
| |
| let rec merge_gramdefs = function |
| | [] -> [] |
| | {it = GramD (id1, _, ps, t, gram1, _); at; _}:: |
| {it = GramD (id2, _, _ps, _t, gram2, _); _}::ds when id1.it = id2.it -> |
| let d' = GramD (id1, "" $ no_region, ps, t, merge_gram gram1 gram2, []) $ at in |
| merge_gramdefs (d'::ds) |
| | d::ds -> |
| d :: merge_gramdefs ds |
| |
| let string_of_desc = function |
| | Some ({it = TextE s; _}::_) -> Some s |
| | Some ({at; _}::_) -> error at "malformed description hint" |
| | _ -> None |
| |
| let render_typdeco env id = |
| match env.deco_typ, string_of_desc (Map.find_opt id.it !(env.desc_typ)) with |
| | true, Some s -> "\\mbox{(" ^ s ^ ")}" |
| | _ -> "" |
| |
| let render_typdef env d = |
| match d.it with |
| | TypD (id1, _id2, args, t, _hints) -> |
| let lhs = |
| Col (render_typdeco env id1) :: |
| Col (render_apply render_typid render_exp |
| env env.show_typ env.macro_typ id1 args) :: [] |
| in |
| let rhss = render_nottyp env t in |
| prefix_rows_hd lhs (prefix_rows [Col "::="] [Col "|"] rhss) |
| | _ -> assert false |
| |
| let render_gramdeco env id = |
| match env.deco_gram, string_of_desc (Map.find_opt id.it !(env.desc_gram)) with |
| | true, Some s -> "\\mbox{(" ^ s ^ ")}" |
| | _ -> "" |
| |
| let render_gramdef env d : row list = |
| match d.it with |
| | GramD (id1, _id2, ps, _t, gram, _hints) -> |
| let args = List.map arg_of_param ps in |
| let lhs = |
| Col (render_gramdeco env id1) :: |
| Col (render_apply render_gramid render_exp_as_sym |
| env env.show_gram env.macro_gram id1 args) :: [] |
| in |
| let rhss = render_gram env gram in |
| prefix_rows_hd lhs (prefix_rows [Col "::="] [Col "|"] rhss) |
| | _ -> assert false |
| |
| let render_ruledef_infer env d = |
| match d.it with |
| | RuleD (id1, _ps, id2, e, prems, _hints) -> |
| let prems' = filter_nl_list (function {it = VarPr _; _} -> false | _ -> true) prems in |
| "\\frac{\n" ^ |
| (if has_nl prems then "\\begin{array}{@{}c@{}}\n" else "") ^ |
| altern_map_nl " \\qquad\n" " \\\\\n" (suffix "\n" (render_prem env)) prems' ^ |
| (if has_nl prems then "\\end{array}\n" else "") ^ |
| "}{\n" ^ |
| render_exp env e ^ "\n" ^ |
| "}" ^ |
| render_rule_deco env " \\, " id1 id2 "" |
| | _ -> failwith "render_ruledef_infer" |
| |
| let render_ruledef env d : row list = |
| match d.it with |
| | RuleD (id1, _ps, id2, e, prems, _hints) -> |
| let e1, op, e2 = |
| match e.it with |
| | InfixE (e1, op, ({it = SeqE (e21::es22); _} as e2)) when Atom.is_sub op -> |
| let e1' = SeqE [] $ Source.no_region in |
| e1, {e with it = InfixE (e1', op, {e2 with it = SeqE [e21]})}, {e2 with it = SeqE es22} |
| | InfixE (e1, op, e2) -> e1, {e with it = AtomE op}, e2 |
| | _ -> error e.at "unrecognized format for tabular rule, infix operator expected" |
| in |
| prefix_rows_hd |
| ( Col (render_rule_deco env "" id1 id2 " \\quad") :: |
| Col (render_exp env e1) :: |
| Col (render_exp env op) :: |
| if e1.at.right.line = e2.at.left.line then |
| Col (render_exp env e2) :: [] |
| else |
| Br `Wide :: Col (render_exp env e2) :: [] |
| ) |
| (render_conditions env prems) |
| | _ -> failwith "render_ruledef" |
| |
| let render_funcdef env d : row list = |
| match d.it with |
| | DefD (id1, args, e, prems) -> |
| prefix_rows_hd |
| ( Col (render_exp env (CallE (id1, args) $ d.at)) :: |
| Col "=" :: |
| if id1.at.right.line = e.at.left.line then |
| Col (render_exp env e) :: [] |
| else |
| Br `Wide :: Col (render_exp env e) :: [] |
| ) |
| (render_conditions env prems) |
| | _ -> failwith "render_funcdef" |
| |
| let rec render_sep_defs' sep br f = function |
| | [] -> "" |
| | {it = SepD; _}::ds -> "{} \\\\[-2ex]\n" ^ render_sep_defs' sep br f ds |
| | d::{it = SepD; _}::ds -> f d ^ br ^ render_sep_defs' sep br f ds |
| | d::ds -> f d ^ sep ^ render_sep_defs' sep br f ds |
| |
| |
| let rec render_defs env = function |
| | [] -> "" |
| | d::ds' as ds -> |
| let sp = if env.config.display then "" else "@{~}" in |
| match d.it with |
| | TypD _ -> |
| (* Columns: decorator & lhs & ::=/| & rhs & premise *) |
| let ds' = merge_typdefs ds in |
| if List.length ds' > 1 && not env.config.display then |
| error d.at "cannot render multiple syntax types in line"; |
| let sp_deco = if env.deco_typ then sp else "@{}" in |
| render_table env sp ["l"; sp_deco ^ "r"; "r"; "l"; "@{}l"] 1 3 |
| (render_sep_defs (render_typdef env) ds') |
| | GramD _ -> |
| (* Columns: decorator & lhs & ::=/| & rhs & => & attr & premise *) |
| let ds' = merge_gramdefs ds in |
| if List.length ds' > 1 && not env.config.display then |
| error d.at "cannot render multiple grammars in line"; |
| let sp_deco = if env.deco_gram then sp else "@{}" in |
| render_table env sp ["l"; sp_deco ^ "r"; "r"; "l"; "@{}l"; "@{}l"; "@{}l"] 1 3 |
| (render_sep_defs (render_gramdef env) ds') |
| | RelD (_, _ps, t, _) -> |
| "\\boxed{" ^ render_typ env t ^ "}" ^ |
| (if ds' = [] then "" else " \\; " ^ render_defs env ds') |
| | RuleD (id1, _, _, _, _, _) -> |
| if Map.mem id1.it !(env.tab_rel) then |
| (* Columns: decorator & lhs & op & rhs & premise *) |
| let sp_deco = if env.deco_rule then sp else "@{}" in |
| render_table env sp ["l"; sp_deco ^ "r"; "c"; "l"; "@{}l"] 1 3 |
| (render_sep_defs (render_ruledef env) ds) |
| else |
| "\\begin{array}{@{}c@{}}\\displaystyle\n" ^ |
| render_sep_defs' "\n\\qquad\n" "\n\\\\[3ex]\\displaystyle\n" |
| (render_ruledef_infer env) ds ^ |
| "\\end{array}" |
| | DefD _ -> |
| (* Columns: lhs & = & rhs & premise *) |
| render_table env sp ["l"; "c"; "l"; "@{}l"] 0 2 |
| (render_sep_defs (render_funcdef env) ds) |
| | SepD -> |
| " \\\\\n" ^ |
| render_defs env ds' |
| | FamD _ | VarD _ | DecD _ | HintD _ -> |
| failwith "render_defs" |
| |
| let render_def env d = render_defs env [d] |
| |
| |
| (* Scripts *) |
| |
| let rec split_typdefs typdefs = function |
| | [] -> List.rev typdefs, [] |
| | d::ds -> |
| match d.it with |
| | TypD _ -> split_typdefs (d::typdefs) ds |
| | _ -> List.rev typdefs, d::ds |
| |
| let rec split_gramdefs gramdefs = function |
| | [] -> List.rev gramdefs, [] |
| | d::ds -> |
| match d.it with |
| | GramD _ -> split_gramdefs (d::gramdefs) ds |
| | _ -> List.rev gramdefs, d::ds |
| |
| let rec split_tabdefs id tabdefs = function |
| | [] -> List.rev tabdefs, [] |
| | d::ds -> |
| match d.it with |
| | RuleD (id1, _, _, _, _, _) when id1.it = id -> |
| split_tabdefs id (d::tabdefs) ds |
| | _ -> List.rev tabdefs, d::ds |
| |
| let rec split_funcdefs id funcdefs = function |
| | [] -> List.rev funcdefs, [] |
| | d::ds -> |
| match d.it with |
| | DefD (id1, _, _, _) when id1.it = id -> split_funcdefs id (d::funcdefs) ds |
| | _ -> List.rev funcdefs, d::ds |
| |
| let rec render_script env = function |
| | [] -> "" |
| | d::ds -> |
| match d.it with |
| | TypD _ -> |
| let typdefs, ds' = split_typdefs [d] ds in |
| "$$\n" ^ render_defs env typdefs ^ "\n$$\n\n" ^ |
| render_script env ds' |
| | GramD _ -> |
| let gramdefs, ds' = split_gramdefs [d] ds in |
| "$$\n" ^ render_defs env gramdefs ^ "\n$$\n\n" ^ |
| render_script env ds' |
| | RelD _ -> |
| "$" ^ render_def env d ^ "$\n\n" ^ |
| render_script env ds |
| | RuleD (id1, _, _, _, _, _) -> |
| if Map.mem id1.it !(env.tab_rel) then |
| let tabdefs, ds' = split_tabdefs id1.it [d] ds in |
| "$$\n" ^ render_defs env tabdefs ^ "\n$$\n\n" ^ |
| render_script env ds' |
| else |
| "$$\n" ^ render_def env d ^ "\n$$\n\n" ^ |
| render_script env ds |
| | DefD (id, _, _, _) -> |
| let funcdefs, ds' = split_funcdefs id.it [d] ds in |
| "$$\n" ^ render_defs env funcdefs ^ "\n$$\n\n" ^ |
| render_script env ds' |
| | SepD -> |
| "\\vspace{1ex}\n\n" ^ |
| render_script env ds |
| | FamD _ | VarD _ | DecD _ | HintD _ -> |
| render_script env ds |