This improved version of the previous patch simplifies the resource management logic for compilation & rendering caches by using generative module functors instead of effects. There is also a bit of performance improvement, most likely due to switching these caches to imperative hash tables. Jon Sterling (1): New query infrastructure; generalised backmatter computed from queries bin/forester/main.ml | 11 +- lib/core/Base.ml | 11 +- lib/core/Eval.ml | 819 ++++++++++++-------- lib/core/Eval.mli | 7 +- lib/core/Query.ml | 91 ++- lib/core/Reporter.ml | 2 + lib/core/Sem.ml | 64 +- lib/core/Xml_tree.ml | 36 +- lib/core/dune | 2 +- lib/frontend/Analysis.ml | 151 ---- lib/frontend/Analysis.mli | 30 - lib/frontend/Config.ml | 5 +- lib/frontend/Config.mli | 1 - lib/frontend/Forest.ml | 192 ++--- lib/frontend/Forest.mli | 10 +- lib/frontend/Grammar.mly | 15 +- lib/frontend/Import_graph.ml | 37 + lib/frontend/Import_graph.mli | 13 + lib/frontend/Lexer.mll | 1 - lib/{frontend => render}/Build_latex.ml | 2 - lib/{frontend => render}/Build_latex.mli | 0 lib/render/Compile.ml | 800 ++++++++++--------- lib/render/Compile.mli | 18 +- lib/{frontend => render}/LaTeX_queue.ml | 6 +- lib/{frontend => render}/LaTeX_queue.mli | 2 +- lib/{frontend => render}/LaTeX_template.ml | 0 lib/{frontend => render}/LaTeX_template.mli | 0 lib/render/Render_effect.ml | 87 --- lib/render/Render_effect.mli | 25 - lib/render/Render_json.ml | 22 +- lib/render/Render_json.mli | 2 +- lib/render/Render_text.ml | 24 +- lib/render/Render_text.mli | 2 +- lib/render/Render_util.ml | 12 +- lib/render/Serialise_xml_tree.ml | 329 ++++---- lib/render/Serialise_xml_tree.mli | 11 +- lib/render/Xml_forester.ml | 8 +- lib/render/Xml_forester.mli | 5 - 38 files changed, 1413 insertions(+), 1440 deletions(-) delete mode 100644 lib/frontend/Analysis.ml delete mode 100644 lib/frontend/Analysis.mli create mode 100644 lib/frontend/Import_graph.ml create mode 100644 lib/frontend/Import_graph.mli rename lib/{frontend => render}/Build_latex.ml (98%) rename lib/{frontend => render}/Build_latex.mli (100%) rename lib/{frontend => render}/LaTeX_queue.ml (68%) rename lib/{frontend => render}/LaTeX_queue.mli (50%) rename lib/{frontend => render}/LaTeX_template.ml (100%) rename lib/{frontend => render}/LaTeX_template.mli (100%) delete mode 100644 lib/render/Render_effect.ml delete mode 100644 lib/render/Render_effect.mli -- 2.43.4
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~jonsterling/forester-devel/patches/53405/mbox | git am -3Learn more about email & git
From: Jon Sterling <jon@jonmsterling.com> 1. Improved querying This patch improves the querying infrastucture to be strong enough to compute the existing backmatter, as well as a few other things (like computing the hereditary contributors to a given tree). One breaking change is that I now want to support only "graph-related" queries: previously, there was `\query/meta{key}{body}`, which would do a structural equality match on the provided Forester content `body`. I think queries like this are brittle and not too useful, and should be replaced (in the future) with usage of the impending custom graph support. Ideally, most queries should be able to start by reading a small fraction of the forest from the graph and intersecting it with things --- rather than walking the entire forest again and again. 2. Generalised backmatter The backmatter is now generalised to support arbitrary queries (but this doesn't yet have a user interface).This impacts the XML format of the backmatter: previously it was something like: <backmatter> <context> ... </context> <references> ... </references> <related> ... </related> <backlinks> ... </backlinks> </backmatter> Now, the backmatter element contains arbitrary trees. For example: <backmatter> <tree ...> <frontmatter> <title>Context</title> ... </frontmatter> ... </tree> ... </backmatter> 3. Miscellaneous I have also disentangled and simplified much of the architecture. Here are some of the additional changes. + The graph analysis is computed on the fly during evaluation rather than in a second pass. + The Render_effect thing is excised, and appropriate parameters are passed to modules where needed. Resource management (caches, etc.) is simplified across the board using generative module functors. + Previously the Xml_tree representation held routes for internal links, etc. I have removed this data from the representation because this is meant to be a source of truth for plenty of output formats, not all of which will route trees to the same locations as the built-in XML output format. Therefore, routing should in fact be computed during serialisation. Simplify resource management using generative functors --- bin/forester/main.ml | 11 +- lib/core/Base.ml | 11 +- lib/core/Eval.ml | 819 ++++++++++++-------- lib/core/Eval.mli | 7 +- lib/core/Query.ml | 91 ++- lib/core/Reporter.ml | 2 + lib/core/Sem.ml | 64 +- lib/core/Xml_tree.ml | 36 +- lib/core/dune | 2 +- lib/frontend/Analysis.ml | 151 ---- lib/frontend/Analysis.mli | 30 - lib/frontend/Config.ml | 5 +- lib/frontend/Config.mli | 1 - lib/frontend/Forest.ml | 192 ++--- lib/frontend/Forest.mli | 10 +- lib/frontend/Grammar.mly | 15 +- lib/frontend/Import_graph.ml | 37 + lib/frontend/Import_graph.mli | 13 + lib/frontend/Lexer.mll | 1 - lib/{frontend => render}/Build_latex.ml | 2 - lib/{frontend => render}/Build_latex.mli | 0 lib/render/Compile.ml | 800 ++++++++++--------- lib/render/Compile.mli | 18 +- lib/{frontend => render}/LaTeX_queue.ml | 6 +- lib/{frontend => render}/LaTeX_queue.mli | 2 +- lib/{frontend => render}/LaTeX_template.ml | 0 lib/{frontend => render}/LaTeX_template.mli | 0 lib/render/Render_effect.ml | 87 --- lib/render/Render_effect.mli | 25 - lib/render/Render_json.ml | 22 +- lib/render/Render_json.mli | 2 +- lib/render/Render_text.ml | 24 +- lib/render/Render_text.mli | 2 +- lib/render/Render_util.ml | 12 +- lib/render/Serialise_xml_tree.ml | 329 ++++---- lib/render/Serialise_xml_tree.mli | 11 +- lib/render/Xml_forester.ml | 8 +- lib/render/Xml_forester.mli | 5 - 38 files changed, 1413 insertions(+), 1440 deletions(-) delete mode 100644 lib/frontend/Analysis.ml delete mode 100644 lib/frontend/Analysis.mli create mode 100644 lib/frontend/Import_graph.ml create mode 100644 lib/frontend/Import_graph.mli rename lib/{frontend => render}/Build_latex.ml (98%) rename lib/{frontend => render}/Build_latex.mli (100%) rename lib/{frontend => render}/LaTeX_queue.ml (68%) rename lib/{frontend => render}/LaTeX_queue.mli (50%) rename lib/{frontend => render}/LaTeX_template.ml (100%) rename lib/{frontend => render}/LaTeX_template.mli (100%) delete mode 100644 lib/render/Render_effect.ml delete mode 100644 lib/render/Render_effect.mli diff --git a/bin/forester/main.ml b/bin/forester/main.ml index a8c7d7d..ad72395 100644 --- a/bin/forester/main.ml @@ -15,11 +15,9 @@ let internal_config_from_config ~env (config : Forester_frontend.Config.Forest_c Forest. {env; root = config.root; - base_url = config.base_url; assets_dirs = make_dirs ~env config.assets; theme_dir = make_dir ~env config.theme; stylesheet = config.stylesheet; - max_fibers = 20; ignore_tex_cache = false; no_assets = false; no_theme = false} @@ -73,7 +71,7 @@ let new_tree ~env config_filename dest_dir prefix template random = Process.read_trees_in_dirs ~dev:true ~ignore_malformed:true input_dirs in let addrs = - Analysis.Map.bindings forest.trees + Addr_map.bindings forest.trees |> List.to_seq |> Seq.map fst |> Seq.filter_map Addr.to_user_addr @@ -126,12 +124,8 @@ let query_all ~env config_filename = Process.read_trees_in_dirs ~dev:true ~ignore_malformed:true @@ make_dirs ~env config.trees in - let internal_config = internal_config_from_config ~env config in - Forest.run_renderer ~cfg:internal_config forest @@ fun () -> forest.trees - |> Analysis.Map.to_list - |> List.map snd - |> Forester_render.Render_json.render_trees ~dev:true + |> Forester_render.Render_json.render_trees ~root:config.root ~dev:true |> Yojson.Basic.to_string |> Format.printf "%s" @@ -155,7 +149,6 @@ let init ~env () = trees = ["trees" ] # The directories in which your trees are stored assets = ["assets"] # The directories in which your assets are stored theme = "theme" # The directory in which your theme is stored -base_url = "https://www.example.com" # The base URL of your site |} in diff --git a/lib/core/Base.ml b/lib/core/Base.ml index 408dd80..cb81f53 100644 --- a/lib/core/Base.ml +++ b/lib/core/Base.ml @@ -1,4 +1,9 @@ -type addr = User_addr of string | Machine_addr of int +type addr = + | User_addr of string + (** The address of a tree that can be referenced from user text. *) + + | Machine_addr of int + (** The address of an anonymous tree.*) let pp_addr fmt = function @@ -18,6 +23,10 @@ struct | _ -> None end +module Addr_map = Map.Make (Addr) +module Addr_set = Set.Make (Addr) +module String_map = Map.Make (String) + type delim = Braces | Squares | Parens [@@deriving show] diff --git a/lib/core/Eval.ml b/lib/core/Eval.ml index 7f197f7..efd9902 100644 --- a/lib/core/Eval.ml +++ b/lib/core/Eval.ml @@ -1,338 +1,511 @@ -open Base -open Bwd open Forester_prelude +open Base -module Lex_env = Algaeff.Reader.Make (struct type t = Sem.t Env.t end) -module Dyn_env = Algaeff.Reader.Make (struct type t = Sem.t Env.t end) -module Heap = Algaeff.State.Make (struct type t = Sem.obj Env.t end) -module Emitted_trees = Algaeff.State.Make (struct type t = Sem.tree list end) -module Fm = Algaeff.State.Make (struct type t = Sem.frontmatter end) - -let get_transclusion_opts () = - let dynenv = Dyn_env.read () in - let title_override = Env.find_opt Expand.Builtins.Transclude.title_sym dynenv in - let taxon_override = - match Env.find_opt Expand.Builtins.Transclude.taxon_sym dynenv with - | Some [{value = Sem.Text text; _}] -> Some text - | _ -> None - in - let get_bool key default = - match Env.find_opt key dynenv with - | Some [{value = Sem.Text "true"; _}] -> true - | Some [{value = Sem.Text "false"; _}] -> false - | _ -> default - in - let expanded = get_bool Expand.Builtins.Transclude.expanded_sym true in - let show_heading = get_bool Expand.Builtins.Transclude.show_heading_sym true in - let toc = get_bool Expand.Builtins.Transclude.toc_sym true in - let numbered = get_bool Expand.Builtins.Transclude.numbered_sym true in - let show_metadata = get_bool Expand.Builtins.Transclude.show_metadata_sym false in - Sem.{title_override; taxon_override; toc; show_heading; expanded; numbered; show_metadata} - -let rec eval : Syn.t -> Sem.t = - function - | [] -> [] - | node :: rest -> - eval_node node rest - -and eval_node : Syn.node Range.located -> Syn.t -> Sem.t = - fun node rest -> - match node.value with - | Link {title; dest} -> - let title = Option.map eval title in - {node with value = Sem.Link (eval_addr dest, title, Identity)} :: eval rest - - | Ref dest -> - let addr = eval_addr dest in - {node with value = Sem.Ref addr} :: eval rest - - | Math (mmode, e) -> - {node with value = Sem.Math (mmode, eval e)} :: eval rest - - | Prim (p, body) -> - {node with value = Sem.Prim (p, eval_trim body)} :: eval rest - - | Xml_tag (name, attrs, body) -> - let rec process attrs = match attrs with - | [] -> [] - | (k,v) :: attrs -> - let processed = process attrs in - if List.mem_assoc k processed then begin - Reporter.emitf ?loc:node.loc Duplicate_attribute - "skipping duplicate XML attribute `%a`" pp_xml_resolved_qname k; - processed - end else - (k, eval v) :: processed - in - {node with value = Sem.Xml_tag (name, process attrs, eval body)} :: eval rest +module Q = Query - | TeX_cs cs -> - {node with value = Sem.TeX_cs cs} :: eval rest +module G = +struct + module G = Graph.Imperative.Digraph.Concrete (Addr) + include G + include Graph.Oper.I (G) - | Transclude addr -> - let opts = get_transclusion_opts () in - let addr = eval_addr addr in - {node with value = Sem.Transclude (opts, addr)} :: eval rest + let safe_succ g x = + if mem_vertex g x then succ g x else [] - | Subtree (addr, nodes) -> - let addr = - match addr with - | Some addr -> User_addr addr - | None -> Machine_addr (Oo.id (object end)) - in - let opts = get_transclusion_opts () in - let subtree = eval_tree_inner ~addr nodes in - let fm = Fm.get () in - let subtree = {subtree with fm = {subtree.fm with physical_parent = Some fm.addr; designated_parent = Some fm.addr}} in - begin - Emitted_trees.modify @@ fun trees -> - subtree :: trees - end; - {node with value = Sem.Subtree (opts, subtree)} :: eval rest - - | If_tex (x , y) -> - let x = eval x in - let y = eval y in - {node with value = Sem.If_tex (x, y)} :: eval rest - - | Query query -> - let opts = get_transclusion_opts () in - let opts = - match opts.title_override with - | None -> {opts with show_heading = false; toc = false} - | Some _ -> opts + let safe_fold_succ f g x acc = + if mem_vertex g x then + fold_succ f g x acc + else + acc + + let safe_pred g x = + if mem_vertex g x then pred g x else [] +end + +module Make () = +struct + + module Graphs = + struct + let all_addrs_ref : Addr_set.t ref = + ref Addr_set.empty + + let rel_to_graph : (Query.rel_name, G.t) Hashtbl.t = + Hashtbl.create 20 + + let rel_to_rtgraph : (Query.rel_name, G.t) Hashtbl.t = + Hashtbl.create 20 + + let get_graph rel = + match Hashtbl.find_opt rel_to_graph rel with + | None -> + let gph = G.create () in + Hashtbl.add rel_to_graph rel gph; + gph + | Some gph -> gph + + let get_rtgraph rel = + match Hashtbl.find_opt rel_to_rtgraph rel with + | None -> + let gph = G.transitive_closure ~reflexive:true @@ get_graph rel in + Hashtbl.add rel_to_rtgraph rel gph; + gph + | Some gph -> gph + + let register_addr addr = + Hashtbl.clear rel_to_rtgraph; + all_addrs_ref := Addr_set.add addr !all_addrs_ref + + let add_edge rel ~source ~target = + Hashtbl.remove rel_to_rtgraph rel; + let gph = get_graph rel in + G.add_edge gph source target + end + + module Query_engine = + struct + let query_rel pol rel addr = + let fn = + match pol with + | `Incoming -> G.safe_pred + | `Outgoing -> G.safe_succ + in + let gph = Graphs.get_graph rel in + Addr_set.of_list @@ fn gph addr + + let check_rel pol rel addr addr' = + let gph = Graphs.get_graph rel in + match pol with + | `Incoming -> G.mem_edge gph addr' addr + | `Outgoing -> G.mem_edge gph addr addr' + + let rec check_query q addr = + match q with + | Q.Tree_under root -> + G.mem_edge (Graphs.get_rtgraph `Transclusion) root addr + | Q.Rel (pol, rel, addr') -> + check_rel pol rel addr' addr + | Q.Isect qs -> check_isect qs addr + | Q.Union qs -> check_union qs addr + | Q.Complement q -> + not @@ check_query q addr + | Q.Isect_fam (q, (pol, rel)) -> + let xs = Addr_set.to_list @@ run_query q in + xs |> List.for_all @@ fun x -> + check_rel pol rel x addr + | Q.Union_fam (q, (pol, rel)) -> + let xs = Addr_set.to_list @@ run_query q in + xs |> List.exists @@ fun x -> + check_rel pol rel x addr + + and check_isect qs addr = + qs |> List.for_all @@ fun q -> + check_query q addr + + and check_union qs addr = + qs |> List.exists @@ fun q -> + check_query q addr + + + and run_query = + function + | Q.Tree_under addr -> + G.safe_fold_succ + Addr_set.add + (Graphs.get_rtgraph `Transclusion) + addr + Addr_set.empty + | Q.Rel (pol, rel, addr) -> + query_rel pol rel addr + | Q.Isect qs -> run_isect qs + | Q.Union qs -> run_union qs + | Q.Complement q -> + Addr_set.diff !Graphs.all_addrs_ref @@ run_query q + | Q.Isect_fam (q, (pol, rel)) -> + let xs = Addr_set.to_list @@ run_query q in + run_isect @@ List.map (fun x -> Q.Rel (pol, rel, x)) xs + | Q.Union_fam (q, (pol, rel)) -> + let xs = Addr_set.to_list @@ run_query q in + run_union @@ List.map (fun x -> Q.Rel (pol, rel, x)) xs + + and run_isect = + function + | [] -> !Graphs.all_addrs_ref + | q :: qs -> + run_query q |> Addr_set.filter @@ check_isect qs + + and run_union qs = + let alg q = Addr_set.union (run_query q) in + List.fold_right alg qs Addr_set.empty + + and fold_set_operation opr running = + function + | [] -> running + | q :: qs -> + let s = run_query q in + fold_set_operation opr (opr running s) qs + end + + module Lex_env = Algaeff.Reader.Make (struct type t = Sem.t Env.t end) + module Dyn_env = Algaeff.Reader.Make (struct type t = Sem.t Env.t end) + module Heap = Algaeff.State.Make (struct type t = Sem.obj Env.t end) + module Emitted_trees = Algaeff.State.Make (struct type t = Sem.tree list end) + module Fm = Algaeff.State.Make (struct type t = Sem.frontmatter end) + module Scope = Algaeff.State.Make (Addr) + + let get_transclusion_opts () = + let dynenv = Dyn_env.read () in + let title_override = Env.find_opt Expand.Builtins.Transclude.title_sym dynenv in + let taxon_override = + match Env.find_opt Expand.Builtins.Transclude.taxon_sym dynenv with + | Some [{value = Sem.Text text; _}] -> Some text + | _ -> None in - let query = Query.map eval query in - {node with value = Sem.Query (opts, query)} :: eval rest - - | Embed_tex {preamble; source} -> - {node with value = Sem.Embed_tex {preamble = eval preamble; source = eval source}} :: eval rest - - | Lam (xs, body) -> - let rec loop xs rest = - match xs, rest with - | [], rest -> eval body, rest - | x :: xs, Range.{value = Syn.Group (Braces, u); loc = loc'} :: rest -> - Lex_env.scope (Env.add x (eval u)) @@ fun () -> - loop xs rest - | x :: xs, Range.{value = Syn.Verbatim str; loc = loc'} :: rest -> - let verb = [Range.{value = Sem.Verbatim str; loc = loc'}] in - Lex_env.scope (Env.add x verb) @@ fun () -> - loop xs rest - | _ -> - Reporter.fatalf Type_error ?loc:node.loc - "expected function to be applied to `%i` additional arguments" - (List.length xs) + let get_bool key default = + match Env.find_opt key dynenv with + | Some [{value = Sem.Text "true"; _}] -> true + | Some [{value = Sem.Text "false"; _}] -> false + | _ -> default in - let body, rest = loop xs rest in - body @ eval rest - - | Object {self; methods} -> - let table = - let env = Lex_env.read () in - let add (name, body) = - let super = Symbol.fresh [] in - Sem.MethodTable.add name Sem.{body; self; super; env} + let expanded = get_bool Expand.Builtins.Transclude.expanded_sym true in + let show_heading = get_bool Expand.Builtins.Transclude.show_heading_sym true in + let toc = get_bool Expand.Builtins.Transclude.toc_sym true in + let numbered = get_bool Expand.Builtins.Transclude.numbered_sym true in + let show_metadata = get_bool Expand.Builtins.Transclude.show_metadata_sym false in + Sem.{title_override; taxon_override; toc; show_heading; expanded; numbered; show_metadata} + + let rec eval : Syn.t -> Sem.t = + function + | [] -> [] + | node :: rest -> + eval_node node rest + + and eval_node : Syn.node Range.located -> Syn.t -> Sem.t = + fun node rest -> + match node.value with + | Link {title; dest} -> + let scope = Scope.get () in + let dest = eval_addr dest in + Graphs.add_edge `Links ~source:scope ~target:dest; + let title = Option.map eval title in + {node with value = Sem.Link (dest, title, Identity)} :: eval rest + + | Ref dest -> + let scope = Scope.get () in + let dest = eval_addr dest in + Graphs.add_edge `Links ~source:scope ~target:dest; + {node with value = Sem.Ref dest} :: eval rest + + | Math (mmode, e) -> + {node with value = Sem.Math (mmode, eval e)} :: eval rest + + | Prim (p, body) -> + {node with value = Sem.Prim (p, eval_trim body)} :: eval rest + + | Xml_tag (name, attrs, body) -> + let rec process attrs = match attrs with + | [] -> [] + | (k,v) :: attrs -> + let processed = process attrs in + if List.mem_assoc k processed then begin + Reporter.emitf ?loc:node.loc Duplicate_attribute + "skipping duplicate XML attribute `%a`" pp_xml_resolved_qname k; + processed + end else + (k, eval v) :: processed in - List.fold_right add methods Sem.MethodTable.empty - in - let sym = Symbol.fresh ["obj"] in - Heap.modify @@ Env.add sym Sem.{prototype = None; methods = table}; - {node with value = Sem.Object sym} :: eval rest - - | Patch {obj; self; super; methods} -> - begin - match eval_strip obj with - | [Range.{value = Sem.Object obj_ptr; _}] -> - let table = - let env = Lex_env.read () in - let add (name, body) = - Sem.MethodTable.add name - Sem.{body; self; super; env} - in - List.fold_right add methods Sem.MethodTable.empty + {node with value = Sem.Xml_tag (name, process attrs, eval body)} :: eval rest + + | TeX_cs cs -> + {node with value = Sem.TeX_cs cs} :: eval rest + + | Transclude addr -> + let addr = eval_addr addr in + let scope = Scope.get () in + Graphs.add_edge `Transclusion ~source:scope ~target:addr; + let opts = get_transclusion_opts () in + {node with value = Sem.Transclude (opts, addr)} :: eval rest + + | Subtree (addr, nodes) -> + let addr = + match addr with + | Some addr -> User_addr addr + | None -> Machine_addr (Oo.id (object end)) + in + let scope = Scope.get () in + Graphs.add_edge `Transclusion ~source:scope ~target:addr; + let opts = get_transclusion_opts () in + let subtree = eval_tree_inner ~addr nodes in + let fm = Fm.get () in + let subtree = {subtree with fm = {subtree.fm with physical_parent = Some fm.addr; designated_parent = Some fm.addr}} in + begin + Emitted_trees.modify @@ fun trees -> + subtree :: trees + end; + {node with value = Sem.Subtree (opts, subtree)} :: eval rest + + | If_tex (x , y) -> + let x = eval x in + let y = eval y in + {node with value = Sem.If_tex (x, y)} :: eval rest + + | Query query -> + let opts = get_transclusion_opts () in + let opts = + match opts.title_override with + | None -> {opts with show_heading = false; toc = false} + | Some _ -> opts + in + let query = Query.map eval_addr query in + {node with value = Sem.Query (opts, query)} :: eval rest + + | Embed_tex {preamble; source} -> + {node with value = Sem.Embed_tex {preamble = eval preamble; source = eval source}} :: eval rest + + | Lam (xs, body) -> + let rec loop xs rest = + match xs, rest with + | [], rest -> eval body, rest + | x :: xs, Range.{value = Syn.Group (Braces, u); loc = loc'} :: rest -> + Lex_env.scope (Env.add x (eval u)) @@ fun () -> + loop xs rest + | x :: xs, Range.{value = Syn.Verbatim str; loc = loc'} :: rest -> + let verb = [Range.{value = Sem.Verbatim str; loc = loc'}] in + Lex_env.scope (Env.add x verb) @@ fun () -> + loop xs rest + | _ -> + Reporter.fatalf Type_error ?loc:node.loc + "expected function to be applied to `%i` additional arguments" + (List.length xs) + in + let body, rest = loop xs rest in + body @ eval rest + + | Object {self; methods} -> + let table = + let env = Lex_env.read () in + let add (name, body) = + let super = Symbol.fresh [] in + Sem.MethodTable.add name Sem.{body; self; super; env} in - let sym = Symbol.fresh ["obj"] in - Heap.modify @@ Env.add sym Sem.{prototype = Some obj_ptr; methods = table}; - {node with value = Sem.Object sym} :: eval rest - | xs -> - Reporter.fatalf ?loc:node.loc Type_error - "tried to patch non-object" - end - - | Call (obj, method_name) -> - begin - match eval_strip obj with - | [Range.{value = Sem.Object sym; _}] as obj_val -> - let rec call_method (obj : Sem.obj) = - let proto_val = - obj.prototype |> Option.map @@ fun ptr -> - [Range.locate_opt None @@ Sem.Object ptr] + List.fold_right add methods Sem.MethodTable.empty + in + let sym = Symbol.fresh ["obj"] in + Heap.modify @@ Env.add sym Sem.{prototype = None; methods = table}; + {node with value = Sem.Object sym} :: eval rest + + | Patch {obj; self; super; methods} -> + begin + match eval_strip obj with + | [Range.{value = Sem.Object obj_ptr; _}] -> + let table = + let env = Lex_env.read () in + let add (name, body) = + Sem.MethodTable.add name + Sem.{body; self; super; env} + in + List.fold_right add methods Sem.MethodTable.empty in - match Sem.MethodTable.find_opt method_name obj.methods with - | Some mthd -> - let env = - let env = Env.add mthd.self obj_val mthd.env in - match proto_val with - | None -> env - | Some proto_val -> - Env.add mthd.super proto_val env + let sym = Symbol.fresh ["obj"] in + Heap.modify @@ Env.add sym Sem.{prototype = Some obj_ptr; methods = table}; + {node with value = Sem.Object sym} :: eval rest + | xs -> + Reporter.fatalf ?loc:node.loc Type_error + "tried to patch non-object" + end + + | Call (obj, method_name) -> + begin + match eval_strip obj with + | [Range.{value = Sem.Object sym; _}] as obj_val -> + let rec call_method (obj : Sem.obj) = + let proto_val = + obj.prototype |> Option.map @@ fun ptr -> + [Range.locate_opt None @@ Sem.Object ptr] in - Lex_env.scope (fun _ -> env) @@ fun () -> - eval mthd.body - | None -> - match obj.prototype with - | Some proto -> - call_method @@ Env.find proto @@ Heap.get () + match Sem.MethodTable.find_opt method_name obj.methods with + | Some mthd -> + let env = + let env = Env.add mthd.self obj_val mthd.env in + match proto_val with + | None -> env + | Some proto_val -> + Env.add mthd.super proto_val env + in + Lex_env.scope (fun _ -> env) @@ fun () -> + eval mthd.body | None -> - Reporter.fatalf ?loc:node.loc Type_error - "tried to call unbound method `%s`" method_name - in - let result = call_method @@ Env.find sym @@ Heap.get () in - result @ eval rest - | xs -> - Reporter.fatalf ?loc:node.loc Type_error - "tried to call method `%s` on non-object: %a" method_name Sem.pp xs - end - - | Var x -> - begin - match Env.find_opt x @@ Lex_env.read () with - | None -> - Reporter.fatalf ?loc:node.loc Resolution_error - "could not find variable named %a" - Symbol.pp x - | Some v -> v @ eval rest - end - - | Put (k, v, body) -> - let body = - Dyn_env.scope (Env.add k @@ eval v) @@ fun () -> - eval body - in - body @ eval rest + match obj.prototype with + | Some proto -> + call_method @@ Env.find proto @@ Heap.get () + | None -> + Reporter.fatalf ?loc:node.loc Type_error + "tried to call unbound method `%s`" method_name + in + let result = call_method @@ Env.find sym @@ Heap.get () in + result @ eval rest + | xs -> + Reporter.fatalf ?loc:node.loc Type_error + "tried to call method `%s` on non-object: %a" method_name Sem.pp xs + end + + | Var x -> + begin + match Env.find_opt x @@ Lex_env.read () with + | None -> + Reporter.fatalf ?loc:node.loc Resolution_error + "could not find variable named %a" + Symbol.pp x + | Some v -> v @ eval rest + end + + | Put (k, v, body) -> + let body = + Dyn_env.scope (Env.add k @@ eval v) @@ fun () -> + eval body + in + body @ eval rest - | Default (k, v, body) -> - let body = - let upd flenv = if Env.mem k flenv then flenv else Env.add k (eval v) flenv in - Dyn_env.scope upd @@ fun () -> - eval body + | Default (k, v, body) -> + let body = + let upd flenv = if Env.mem k flenv then flenv else Env.add k (eval v) flenv in + Dyn_env.scope upd @@ fun () -> + eval body + in + body @ eval rest + + | Get key -> + begin + let env = Dyn_env.read () in + match Env.find_opt key env with + | None -> + Eio.traceln "getting %a from %a" Symbol.pp key (Env.pp Sem.pp) env; + Reporter.fatalf ?loc:node.loc Resolution_error + "could not find fluid binding named %a" + Symbol.pp key + | Some v -> v @ eval rest + end + + | Verbatim str -> + {node with value = Sem.Verbatim str} :: eval rest + + | Group _ | Text _ -> + eval_textual @@ node :: rest + + | Title title -> + let title = eval title in + Fm.modify (fun fm -> {fm with title = Some title}); + eval rest + + | Parent addr -> + Fm.modify (fun fm -> {fm with designated_parent = Some (User_addr addr)}); + eval rest + + | Meta (k, v) -> + let v = eval v in + Fm.modify (fun fm -> {fm with metas = fm.metas @ [k,v]}); + eval rest + + | Author author -> + let scope = Scope.get () in + let addr = User_addr author in + Graphs.add_edge `Authorship ~source:scope ~target:addr; + Fm.modify (fun fm -> {fm with authors = fm.authors @ [addr]}); + eval rest + + | Contributor author -> + let scope = Scope.get () in + let addr = User_addr author in + Graphs.add_edge `Contributorship ~source:scope ~target:addr; + Fm.modify (fun fm -> {fm with contributors = fm.contributors @ [addr]}); + eval rest + + | Tag tag -> + let scope = Scope.get () in + Graphs.add_edge `Tags ~source:scope ~target:(User_addr tag); + Fm.modify (fun fm -> {fm with tags = fm.tags @ [tag]}); + eval rest + + | Date date -> + begin + match Date.parse date with + | None -> + Reporter.fatalf Parse_error "Invalid date string `%s`" date + | Some date -> + Fm.modify (fun fm -> {fm with dates = fm.dates @ [date]}); + eval rest + end + + | Number num -> + Fm.modify (fun fm -> {fm with number = Some num}); + eval rest + + | Taxon taxon -> + let scope = Scope.get () in + Graphs.add_edge `Taxa ~source:scope ~target:(User_addr taxon); + Fm.modify (fun fm -> {fm with taxon = Some taxon}); + eval rest + + and eval_strip xs = Sem.strip_whitespace @@ eval xs + + and eval_trim xs = Sem.trim_whitespace @@ eval xs + + and eval_textual ?(prefix = []) : Syn.t -> Sem.t = + function + | {value = Group (d, xs); _} :: rest -> + let l, r = + match d with + | Braces -> "{", "}" + | Squares -> "[", "]" + | Parens -> "(", ")" + in + eval_textual ~prefix:(l :: prefix) @@ xs @ Asai.Range.locate_opt None (Syn.Text r) :: rest + | {value = Text x; _} :: rest -> + eval_textual ~prefix:(x :: prefix) @@ rest + | rest -> + let txt = String.concat "" @@ List.rev prefix in + Range.locate_opt None (Sem.Text txt) :: eval rest + + and eval_as_string xs = + Sem.string_of_nodes @@ eval_textual xs + + and eval_addr xs = + User_addr (eval_as_string xs) + + and eval_tree_inner ~addr (tree : Syn.tree) : Sem.tree = + Graphs.register_addr addr; + let scope = + match addr with + | User_addr _ -> addr + | _ -> Scope.get () in - body @ eval rest - - | Get key -> - begin - let env = Dyn_env.read () in - match Env.find_opt key @@ Dyn_env.read () with - | None -> - Eio.traceln "getting %a from %a" Symbol.pp key (Env.pp Sem.pp) env; - Reporter.fatalf ?loc:node.loc Resolution_error - "could not find fluid binding named %a" - Symbol.pp key - | Some v -> v @ eval rest - end - - | Verbatim str -> - {node with value = Sem.Verbatim str} :: eval rest - - | Group _ | Text _ -> - eval_textual @@ node :: rest - - | Title title -> - let title = eval title in - Fm.modify (fun fm -> {fm with title = Some title}); - eval rest - - | Parent addr -> - Fm.modify (fun fm -> {fm with designated_parent = Some (User_addr addr)}); - eval rest - - | Meta (k, v) -> - let v = eval v in - Fm.modify (fun fm -> {fm with metas = fm.metas @ [k,v]}); - eval rest - - | Author author -> - Fm.modify (fun fm -> {fm with authors = fm.authors @ [User_addr author]}); - eval rest - - | Contributor author -> - Fm.modify (fun fm -> {fm with contributors = fm.contributors @ [User_addr author]}); - eval rest - - | Tag tag -> - Fm.modify (fun fm -> {fm with tags = fm.tags @ [tag]}); - eval rest - - | Date date -> - begin - match Date.parse date with - | None -> - Reporter.fatalf Parse_error "Invalid date string `%s`" date - | Some date -> - Fm.modify (fun fm -> {fm with dates = fm.dates @ [date]}); - eval rest - end - - | Number num -> - Fm.modify (fun fm -> {fm with number = Some num}); - eval rest - - | Taxon taxon -> - begin - Fm.modify @@ fun fm -> - {fm with taxon = Some taxon} - end; - eval rest - -and eval_strip xs = Sem.strip_whitespace @@ eval xs - -and eval_trim xs = Sem.trim_whitespace @@ eval xs - -and eval_textual ?(prefix = []) : Syn.t -> Sem.t = - function - | {value = Group (d, xs); _} :: rest -> - let l, r = - match d with - | Braces -> "{", "}" - | Squares -> "[", "]" - | Parens -> "(", ")" + Scope.run ~init:scope @@ fun () -> + let outer_fm = Fm.get () in + let fm = + {(Sem.empty_frontmatter ~addr) with + source_path = outer_fm.source_path; + authors = outer_fm.authors; + dates = outer_fm.dates} in - eval_textual ~prefix:(l :: prefix) @@ xs @ Asai.Range.locate_opt None (Syn.Text r) :: rest - | {value = Text x; _} :: rest -> - eval_textual ~prefix:(x :: prefix) @@ rest - | rest -> - let txt = String.concat "" @@ List.rev prefix in - Range.locate_opt None (Sem.Text txt) :: eval rest - -and eval_as_string xs = - Sem.string_of_nodes @@ eval_textual xs - -and eval_addr xs = - User_addr (eval_as_string xs) - -and eval_tree_inner ~addr (tree : Syn.tree) : Sem.tree = - let outer_fm = Fm.get () in - let fm = - {(Sem.empty_frontmatter ~addr) with - source_path = outer_fm.source_path; - authors = outer_fm.authors; - dates = outer_fm.dates} - in - Fm.run ~init:fm @@ fun () -> - let body = eval tree in - let fm = Fm.get () in - let open Sem in - {fm; body} - - -let eval_tree ~addr ~source_path (tree : Syn.tree) : Sem.tree * Sem.tree list = - let fm = {(Sem.empty_frontmatter ~addr) with source_path} in - Fm.run ~init:fm @@ fun () -> - Emitted_trees.run ~init:[] @@ fun () -> - Heap.run ~init:Env.empty @@ fun () -> - Lex_env.run ~env:Env.empty @@ fun () -> - Dyn_env.run ~env:Env.empty @@ fun () -> - let tree = eval_tree_inner ~addr tree in - let emitted = Emitted_trees.get () in - tree, emitted + Fm.run ~init:fm @@ fun () -> + let bm = Sem.default_backmatter ~addr in (*TODO*) + let body = eval tree in + let fm = Fm.get () in + let open Sem in + {fm; body; bm} + + + let eval_tree ~addr ~source_path (tree : Syn.tree) : Sem.tree * Sem.tree list = + let fm = {(Sem.empty_frontmatter ~addr) with source_path} in + Fm.run ~init:fm @@ fun () -> + Scope.run ~init:addr @@ fun () -> + Emitted_trees.run ~init:[] @@ fun () -> + Heap.run ~init:Env.empty @@ fun () -> + Lex_env.run ~env:Env.empty @@ fun () -> + Dyn_env.run ~env:Env.empty @@ fun () -> + let tree = eval_tree_inner ~addr tree in + let emitted = Emitted_trees.get () in + tree, emitted + + let run_query = Query_engine.run_query +end diff --git a/lib/core/Eval.mli b/lib/core/Eval.mli index 34a483f..5b455b8 100644 --- a/lib/core/Eval.mli +++ b/lib/core/Eval.mli @@ -1,3 +1,8 @@ +open Forester_prelude open Base -val eval_tree : addr:addr -> source_path:string option -> Syn.tree -> Sem.tree * Sem.tree list +module Make () : +sig + val eval_tree : addr:addr -> source_path:string option -> Syn.tree -> Sem.tree * Sem.tree list + val run_query : addr Query.t -> Addr_set.t +end \ No newline at end of file diff --git a/lib/core/Query.ml b/lib/core/Query.ml index 25913dd..f7d9ed0 100644 --- a/lib/core/Query.ml +++ b/lib/core/Query.ml @@ -1,22 +1,79 @@ -type 'a t = - | Author of 'a - | Tag of 'a - | Taxon of 'a - | Meta of string * 'a - | Or of 'a t list - | And of 'a t list - | Not of 'a t - | True +open Base + +type rel_name = [`Links | `Transclusion | `Authorship | `Contributorship | `Tags | `Taxa ] +[@@deriving show] + +type rel_query = [`Incoming | `Outgoing] * rel_name +[@@deriving show] + +type 'addr t = + | Rel of [`Incoming | `Outgoing] * rel_name * 'addr + | Tree_under of 'addr + | Isect of 'addr t list + | Union of 'addr t list + | Complement of 'addr t + | Isect_fam of 'addr t * rel_query + | Union_fam of 'addr t * rel_query [@@deriving show] let rec map f = function - | Author x -> Author (f x) - | Tag x -> Tag (f x) - | Taxon x -> Taxon (f x ) - | Meta (k, v) -> Meta (k, f v) - | Or qs -> Or (List.map (map f) qs) - | And qs -> And (List.map (map f) qs) - | Not q -> Not (map f q) - | True -> True + | Rel (pol, rel, x) -> Rel (pol, rel, f x) + | Tree_under x -> Tree_under (f x) + | Isect qs -> Isect (List.map (map f) qs) + | Union qs -> Union (List.map (map f) qs) + | Isect_fam (q, rq) -> Isect_fam (map f q, rq) + | Union_fam (q, rq) -> Union_fam (map f q, rq) + | Complement q -> Complement (map f q) + + +(** A heuristic for computing an intersection of queries. *) +let rec query_cost = + function + | Tree_under _ -> 100 + | Rel _ -> 200 + | Isect qs -> + List.fold_left (fun i q -> min (query_cost q) i) 1000 qs + | Union qs -> + List.fold_left (fun i q -> max (query_cost q) i) 0 qs + | Isect_fam (q, k) -> query_cost q + | Union_fam (q, k) -> query_cost q + | Complement _ -> 900 + +let sort_by_ascending_cost qs = + qs |> List.sort @@ fun q0 q1 -> + compare (query_cost q0) (query_cost q1) + +let sort_by_descending_cost qs = + qs |> List.sort @@ fun q0 q1 -> + compare (query_cost q1) (query_cost q0) + + +let rec isect qs = + match sort_by_ascending_cost qs with + | Isect qs :: qs' -> isect @@ qs @ qs' + | qs -> Isect qs + +and union qs = + match sort_by_descending_cost qs with + | Union qs :: qs' -> union @@ qs @ qs' + | qs -> Union qs + +let rec complement = + function + | Union qs -> isect @@ List.map complement qs + | Complement q -> q + | q -> Complement q + +let rel pol rel addr = + Rel (pol, rel, addr) + +let tree_under x = Tree_under x +let isect_fam q pol rel = + Isect_fam (q, (pol, rel)) + +let union_fam q pol rel = + Union_fam (q, (pol, rel)) +let has_taxon taxon = + rel `Incoming `Taxa @@ User_addr taxon diff --git a/lib/core/Reporter.ml b/lib/core/Reporter.ml index 7c5c9c5..8d5c04b 100644 --- a/lib/core/Reporter.ml +++ b/lib/core/Reporter.ml @@ -14,6 +14,7 @@ struct | Internal_error | Configuration_error | Initialization_warning + | Routing_error | Profiling [@@deriving show] @@ -32,6 +33,7 @@ struct | Internal_error -> Bug | Configuration_error -> Error | Initialization_warning -> Warning + | Routing_error -> Error | Profiling -> Info let short_code : t -> string = diff --git a/lib/core/Sem.ml b/lib/core/Sem.ml index 18a4778..53f1682 100644 --- a/lib/core/Sem.ml +++ b/lib/core/Sem.ml @@ -17,7 +17,7 @@ type node = | Verbatim of string | Transclude of transclusion_opts * addr | Subtree of transclusion_opts * tree - | Query of transclusion_opts * t Query.t + | Query of transclusion_opts * addr Query.t | Link of addr * t option * modifier | Xml_tag of xml_resolved_qname * (xml_resolved_qname * t) list * t | TeX_cs of TeX_cs.t @@ -50,6 +50,7 @@ and env = t Env.t and tree = {fm : frontmatter; + bm : backmatter_section list; body : t} [@@deriving show] @@ -66,7 +67,10 @@ and frontmatter = designated_parent : addr option; source_path : string option; number : string option} +[@@deriving show] +and backmatter_section = + | Backmatter_section of {title: t; query : addr Query.t} type obj_method = {body : Syn.t; @@ -179,30 +183,6 @@ struct end -module Query = -struct - let rec test query (doc : tree) = - match query with - | Query.Author [Range.{value = Text addr; _}] -> - List.mem (User_addr addr) doc.fm.authors - | Query.Tag [{value = Text addr; _}] -> - List.mem addr doc.fm.tags - | Query.Meta (key, value) -> - List.mem (key, value) doc.fm.metas - | Query.Taxon [{value = Text taxon; _}] -> - doc.fm.taxon = Some taxon - | Query.Or qs -> - qs |> List.exists @@ fun q -> test q doc - | Query.And qs -> - qs |> List.for_all @@ fun q -> test q doc - | Query.Not q -> - not @@ test q doc - | Query.True -> - true - | _ -> false - -end - let empty_frontmatter ~addr = {addr; title = None; @@ -217,9 +197,41 @@ let empty_frontmatter ~addr = source_path = None; number = None} +let default_backmatter ~addr = + let make_section title query = + let title = [Range.locate_opt None @@ Text title] in + Backmatter_section {title; query} + in + [ + make_section "references" @@ + Query.isect [ + Query.union_fam (Query.tree_under addr) `Outgoing `Links; + Query.has_taxon "reference" + ]; + + make_section "context" @@ + Query.rel `Incoming `Transclusion addr; + + make_section "backlinks" @@ + Query.rel `Incoming `Links addr; + + make_section "related" @@ + Query.isect [ + Query.rel `Outgoing `Links addr; + Query.complement @@ Query.has_taxon "reference" + ]; + + make_section "contributions" @@ + Query.union [ + Query.rel `Incoming `Authorship addr; + Query.rel `Incoming `Contributorship addr + ] + ] + let empty_tree ~addr = {fm = empty_frontmatter ~addr; - body = []} + body = []; + bm = default_backmatter ~addr} let default_transclusion_opts : transclusion_opts = {title_override = None; diff --git a/lib/core/Xml_tree.ml b/lib/core/Xml_tree.ml index d5de848..33729d4 100644 --- a/lib/core/Xml_tree.ml +++ b/lib/core/Xml_tree.ml @@ -1,4 +1,5 @@ open Forester_prelude +open Base type xml_qname = { prefix : string; @@ -19,7 +20,7 @@ type 'content attribution = type date = | Date of { - href : string option; + addr : addr option; year : int; month : int option; day : int option @@ -31,8 +32,6 @@ type 'content meta = body : 'content } -type link_type = [`Local | `External] - type ('content, 'tree) content_node = | Text of string | CDATA of string @@ -44,18 +43,12 @@ type ('content, 'tree) content_node = | Prim of Prim.t * 'content | Subtree of 'tree | Ref of { - addr : string; - href : string; + addr : addr; taxon : string option; number : string option } - | Link of { - type_ : link_type; - href : string; - title : string option; - addr : string option; - content : 'content - } + | Local_link of {addr : addr; content : 'content; title : string option} + | External_link of {href : string; content : 'content; title : string option} | TeX of { display : [`Inline | `Block]; body : string @@ -71,29 +64,18 @@ type ('content, 'tree) content_node = type 'content frontmatter = { title : 'content option; - anchor : string; + anchor : string option; number : string option; taxon : string option; designated_parent : string option; metas : 'content meta list; - route : string; - addr : string; + addr : addr option; source_path : string option; dates : date list; last_changed : date option; attributions : 'content attribution list } -(* TODO: generalise *) -type 'tree backmatter_elt = - | Contributions of 'tree list - | Related of 'tree list - | Backlinks of 'tree list - | Context of 'tree list - | References of 'tree list - -type 'tree backmatter = 'tree backmatter_elt list - type tree_options = { toc : bool; numbered : bool; @@ -107,7 +89,7 @@ type 'content tree = { options : tree_options; frontmatter : 'content frontmatter; mainmatter : 'content; - backmatter : 'content tree backmatter option + backmatter : 'content tree list } (* Tie the knot *) @@ -115,4 +97,4 @@ type tree_ = Tree of content tree and content = Content of (content, tree_) content_node list let splice (Content xs) = xs -let splice_tree (Tree tree) = tree \ No newline at end of file +let splice_tree (Tree tree) = tree diff --git a/lib/core/dune b/lib/core/dune index 7804226..2c36b04 100644 --- a/lib/core/dune +++ b/lib/core/dune @@ -2,7 +2,7 @@ (name Forester_core) (preprocess (pps ppx_deriving.show)) - (libraries forester.prelude yuujinchou asai repr) + (libraries forester.prelude yuujinchou asai repr ocamlgraph) (public_name forester.core)) (env diff --git a/lib/frontend/Analysis.ml b/lib/frontend/Analysis.ml deleted file mode 100644 index 6f5c454..0000000 --- a/lib/frontend/Analysis.ml @@ -1,151 +0,0 @@ -open Forester_core - -module Map = Map.Make (Addr) -module Gph = Graph.Imperative.Digraph.Concrete (Addr) -module Topo = Graph.Topological.Make (Gph) - -module Tbl = Hashtbl.Make (Addr) - -let build_import_graph (trees : Code.tree list) = - let import_graph = Gph.create () in - - let rec analyse_tree roots (tree : Code.tree) = - let roots = Option.fold ~none:roots ~some:(fun x -> x :: roots) tree.addr in - begin - tree.addr |> Option.iter @@ fun addr -> - Gph.add_vertex import_graph @@ User_addr addr - end; - tree.code |> List.iter @@ fun node -> - match Asai.Range.(node.value) with - | Code.Import (_, dep) -> - roots |> List.iter @@ fun addr -> Gph.add_edge import_graph (User_addr dep) (User_addr addr) - | Code.Subtree (addr, code) -> - analyse_tree roots @@ Code.{tree with addr; code} - | _ -> () - in - - trees |> List.iter (analyse_tree []); - import_graph - -type analysis = - {transclusion_graph : Gph.t; - link_graph : Gph.t; - contributors : addr Tbl.t; - author_pages : addr Tbl.t; - bibliography : addr Tbl.t} - - -let new_analysis () = - let size = 100 in - {transclusion_graph = Gph.create (); - link_graph = Gph.create (); - author_pages = Tbl.create size; - contributors = Tbl.create size; - bibliography = Tbl.create size} - -let rec analyze_nodes ~analysis scope : Sem.t -> unit = - List.iter @@ fun located -> - match Range.(located.value) with - | Sem.Transclude (opts, addr) -> - analyze_transclusion_opts ~analysis scope opts; - Gph.add_edge analysis.transclusion_graph addr scope - | Sem.Subtree (opts, subtree) -> - analyze_transclusion_opts ~analysis scope opts; - Gph.add_edge analysis.transclusion_graph subtree.fm.addr scope; - begin - match subtree.fm.addr with - | Machine_addr _ -> - analyze_nodes ~analysis scope subtree.body - | _ -> () - end - | Sem.Link (addr, title, _) -> - Option.iter (analyze_nodes ~analysis scope) title; - Gph.add_edge analysis.link_graph addr scope - | Sem.Ref addr -> - Gph.add_edge analysis.link_graph addr scope - | Sem.Xml_tag (_, attrs, xs) -> - begin - attrs |> List.iter @@ fun (k, v) -> - analyze_nodes ~analysis scope v - end; - analyze_nodes ~analysis scope xs - | Sem.Math (_, x) -> - analyze_nodes ~analysis scope x - | Sem.Embed_tex {source; _} -> - analyze_nodes ~analysis scope source - | Sem.Query (opts, _) -> - analyze_transclusion_opts ~analysis scope opts - | Sem.If_tex (_, y) -> - analyze_nodes ~analysis scope y - | Sem.Prim (_, x) -> - analyze_nodes ~analysis scope x - | Sem.Object _ | Sem.TeX_cs _ | Sem.Img _ | Sem.Text _ | Sem.Verbatim _ -> - () - -and analyze_transclusion_opts ~analysis scope : Sem.transclusion_opts -> unit = - function Sem.{title_override; _} -> - title_override |> Option.iter @@ analyze_nodes ~analysis scope - -let analyze_doc ~analysis scope (doc : Sem.tree) = - analyze_nodes ~analysis scope doc.body; - doc.fm.title |> Option.iter @@ analyze_nodes ~analysis scope; - begin - doc.fm.designated_parent |> Option.iter @@ fun parent -> - if doc.fm.physical_parent = Some parent then () else - Gph.add_edge analysis.link_graph parent scope - end; - begin - doc.fm.authors |> List.iter @@ fun author -> - Tbl.add analysis.author_pages author scope - end; - begin - doc.fm.contributors |> List.iter @@ fun author -> - Tbl.add analysis.author_pages author scope - end; - begin - doc.fm.metas |> List.iter @@ fun (_, meta) -> - analyze_nodes ~analysis scope meta - end - -let merge_bibliography ~analysis ~from_addr ~to_addr = - Tbl.find_all analysis.bibliography from_addr |> List.iter @@ fun ref -> - Tbl.add analysis.bibliography to_addr ref - -let analyze_trees (trees : Sem.tree Map.t) : analysis = - let analysis = new_analysis () in - begin - trees |> Map.iter @@ fun addr doc -> - Gph.add_vertex analysis.transclusion_graph addr; - Gph.add_vertex analysis.link_graph addr; - - analyze_doc ~analysis addr doc; - let task ref = - match Map.find_opt ref trees with - | Some (ref_doc : Sem.tree) when ref_doc.fm.taxon = Some "reference" -> - Tbl.add analysis.bibliography addr ref - | _ -> () - in - Gph.iter_pred task analysis.link_graph addr; - end; - - begin - analysis.transclusion_graph |> Topo.iter @@ fun child_addr -> - - let handle_parent parent_addr = - Map.find_opt child_addr trees |> Option.iter @@ fun (parent_doc : Sem.tree) -> - match parent_doc.fm.taxon with - | Some "reference" -> () - | _ -> - begin - parent_doc.fm.authors - @ parent_doc.fm.contributors - @ Tbl.find_all analysis.contributors child_addr - |> List.iter @@ fun contributor -> - Tbl.add analysis.contributors parent_addr contributor - end; - merge_bibliography ~analysis ~from_addr:child_addr ~to_addr:parent_addr - in - Gph.iter_succ handle_parent analysis.transclusion_graph child_addr - end; - - analysis diff --git a/lib/frontend/Analysis.mli b/lib/frontend/Analysis.mli deleted file mode 100644 index 03c6f6b..0000000 --- a/lib/frontend/Analysis.mli @@ -1,30 +0,0 @@ -open Forester_core - -module Gph : sig - type t - val succ : t -> addr -> addr list - val pred : t -> addr -> addr list -end - -module Tbl : sig - type 'a t - val find_all : 'a t -> addr -> 'a list -end - -module Map : Map.S with type key = addr - -module Topo : sig - val fold : (addr -> 'a -> 'a) -> Gph.t -> 'a -> 'a -end - -val build_import_graph : Code.tree list -> Gph.t - -type analysis = - {transclusion_graph : Gph.t; - link_graph : Gph.t; - contributors : addr Tbl.t; - author_pages : addr Tbl.t; - bibliography : addr Tbl.t} - -(** Populating all the graphs and tables by inspecting evaluated trees *) -val analyze_trees : Sem.tree Map.t -> analysis diff --git a/lib/frontend/Config.ml b/lib/frontend/Config.ml index 3fac302..88eac95 100644 --- a/lib/frontend/Config.ml +++ b/lib/frontend/Config.ml @@ -6,7 +6,6 @@ struct {trees : string list; assets : string list; theme : string; - base_url : string option; root : string option; stylesheet : string} [@@deriving show] @@ -16,7 +15,6 @@ let default_forest_config : Forest_config.t = {trees = ["trees"]; assets = []; theme = "theme"; - base_url = None; root = None; stylesheet = "default.xsl"} @@ -48,7 +46,6 @@ let parse_forest_config_file filename = Option.value ~default:default_forest_config.stylesheet @@ get tbl (forest |-- key "stylesheet" |-- string) in - let base_url = get tbl (forest |-- key "base_url" |-- string) in let root = get tbl (forest |-- key "root" |-- string) in - Forest_config.{assets; trees; theme; base_url; root; stylesheet} + Forest_config.{assets; trees; theme; root; stylesheet} diff --git a/lib/frontend/Config.mli b/lib/frontend/Config.mli index a9e0114..057a067 100644 --- a/lib/frontend/Config.mli +++ b/lib/frontend/Config.mli @@ -3,7 +3,6 @@ module Forest_config : sig {trees : string list; assets : string list; theme : string; - base_url : string option; root : string option; stylesheet : string} end diff --git a/lib/frontend/Forest.ml b/lib/frontend/Forest.ml index 4b5fdb5..5ea863c 100644 --- a/lib/frontend/Forest.ml +++ b/lib/frontend/Forest.ml @@ -3,130 +3,28 @@ open Forester_prelude open Forester_core open Forester_render -module A = Analysis -module M = A.Map -module Tbl = A.Tbl -module Gph = A.Gph +module M = Addr_map type config = {env : Eio_unix.Stdenv.base; assets_dirs : Eio.Fs.dir_ty Eio.Path.t list; theme_dir : Eio.Fs.dir_ty Eio.Path.t; root : string option; - base_url : string option; stylesheet : string; ignore_tex_cache : bool; no_assets: bool; - no_theme: bool; - max_fibers : int} + no_theme: bool} type raw_forest = Code.tree list type forest = - {trees : Sem.tree Analysis.Map.t; - analysis : Analysis.analysis} + {trees : Sem.tree M.t; + run_query : addr Query.t -> Addr_set.t} module LaTeX_queue = LaTeX_queue.Make () -let run_renderer ~cfg (forest : forest) (body : unit -> 'a) : 'a = - let module S = Set.Make (Addr) in - let module H : Render_effect.Handler = - struct - let analysis = forest.analysis - - let is_root addr = - Option.map (fun x -> User_addr x) cfg.root = Some addr - - let route addr = - let ext = "xml" in - let base = - match is_root addr with - | true -> "index" - | false -> - match addr with - | User_addr addr -> addr - | Machine_addr ix -> Format.sprintf "unstable-%i" ix - in - Format.asprintf "%s.%s" base ext - - let get_doc addr = - M.find_opt addr forest.trees - - let enqueue_latex ~name ~preamble ~source = - LaTeX_queue.enqueue ~name ~preamble ~source - - let addr_peek_title scope = - Option.bind (M.find_opt scope forest.trees) Sem.Util.peek_title - - let get_sorted_trees addrs : Sem.tree list = - let find addr = - match M.find_opt addr forest.trees with - | None -> [] - | Some doc -> [doc] - in - Sem.Util.sort @@ List.concat_map find @@ S.elements addrs - - let is_user_addr = - function - | User_addr _ -> true - | _ -> false - - let get_all_links scope = - get_sorted_trees @@ S.filter is_user_addr @@ S.of_list @@ Gph.pred analysis.link_graph scope - - let backlinks scope = - get_sorted_trees @@ S.filter is_user_addr @@ S.of_list @@ Gph.succ analysis.link_graph scope - - let related scope = - get_all_links scope |> List.filter @@ fun (doc : Sem.tree) -> - doc.fm.taxon <> Some "reference" - - let bibliography scope = - get_sorted_trees @@ - S.of_list @@ A.Tbl.find_all analysis.bibliography scope - - let parents scope = - get_sorted_trees @@ S.of_list @@ Gph.succ analysis.transclusion_graph scope - - let contributions scope = - get_sorted_trees @@ S.of_list @@ Tbl.find_all analysis.author_pages scope - - let contributors scope = - try - let tree = M.find scope forest.trees in - let authors = S.of_list tree.fm.authors in - let contributors = S.union (S.of_list tree.fm.contributors) @@ S.of_list @@ Tbl.find_all analysis.contributors scope in - let proper_contributors = - contributors |> S.filter @@ fun contr -> - not @@ S.mem contr authors - in - let by_title = Compare.under addr_peek_title @@ Compare.option String.compare in - (* let compare = Compare.cascade by_title String.compare in *) - List.sort by_title @@ S.elements proper_contributors - with Not_found -> [] - - let run_query query = - get_sorted_trees @@ S.of_seq @@ Seq.map fst @@ M.to_seq @@ - M.filter (fun _ -> Sem.Query.test query) forest.trees - - let last_changed scope = - let (let*) = Option.bind in - let* tree = M.find_opt scope forest.trees in - let* source_path = tree.fm.source_path in - let env = cfg.env in - let path = Eio.Path.(Eio.Stdenv.fs env / source_path) in - let stat = Eio.Path.stat ~follow:true path in - let* mtime = Some stat.mtime in - let* ptime = Ptime.of_float_s mtime in - let (yyyy, mm, dd) = ptime |> Ptime.to_date_time |> fst in - Some (Date.{yyyy; mm = Some mm; dd = Some dd}) - end - in - let module Run = Render_effect.Run (H) in - Run.run body - - let plant_forest (trees : raw_forest) : forest = + let module Ev = Eval.Make () in let add_tree addr tree trees = if M.mem addr trees then begin @@ -146,24 +44,24 @@ let plant_forest (trees : raw_forest) : forest = List.fold_left alg M.empty trees in - let _, trees = - let import_graph = A.build_import_graph trees in + let (_, trees) = + let import_graph = Import_graph.build_import_graph trees in let task addr (units, trees) = let tree = M.find_opt addr unexpanded_trees in match tree with | None -> units, trees | Some tree -> let units, syn = Expand.expand_tree units tree in - let tree, emitted_trees = Eval.eval_tree ~addr ~source_path:tree.source_path syn in - let add trees tree = - add_tree Sem.(tree.fm.addr) tree trees + let tree, emitted_trees = Ev.eval_tree ~addr ~source_path:tree.source_path syn in + let add trees (tree : Sem.tree) = + add_tree tree.fm.addr tree trees in units, List.fold_left add trees @@ tree :: emitted_trees in - A.Topo.fold task import_graph (Expand.UnitMap.empty, M.empty) + Import_graph.Topo.fold task import_graph (Expand.UnitMap.empty, M.empty) in - {trees; analysis = A.analyze_trees trees} + {trees; run_query = Ev.run_query} let rec random_not_in keys = let attempt = Random.int (36*36*36*36 - 1) in @@ -253,25 +151,10 @@ let tags ~forest = |> Seq.filter_map (fun (addr, x) -> Addr.to_user_addr addr |> Option.map (fun s -> s, x)) -module E = Render_effect.Perform - -let render_tree ~cfg ~cwd (tree : Sem.tree) = - let addr = tree.fm.addr in - let create = `Or_truncate 0o644 in - let base_url = cfg.base_url in - begin - let path = Eio.Path.(cwd / "output" / E.route addr) in - Eio.Path.with_open_out ~create path @@ fun flow -> - Eio.Buf_write.with_flow flow @@ fun writer -> - let fmt = Eio_util.formatter_of_writer writer in - Serialise_xml_tree.pp ~stylesheet:cfg.stylesheet fmt @@ - Compile.compile_tree_top tree - end - -let render_json ~cwd docs = - let docs = Sem.Util.sort_for_index @@ List.of_seq @@ Seq.map snd @@ M.to_seq docs in +let render_json ~cfg ~cwd docs = + let root = cfg.root in Yojson.Basic.to_file "./output/forest.json" @@ - Render_json.render_trees ~dev:false docs + Render_json.render_trees ~dev:false ~root docs let is_hidden_file fname = String.starts_with ~prefix:"." fname @@ -308,16 +191,46 @@ let copy_resources ~env = if not @@ Eio_util.file_exists Eio.Path.(cwd / dest_dir / fname) then Eio_util.copy_to_dir ~cwd ~env ~source:fp ~dest_dir -let render_trees ~cfg ~forest ~render_only : unit = +let last_changed env forest scope = + let (let*) = Option.bind in + let* tree = M.find_opt scope forest.trees in + let* source_path = tree.fm.source_path in + let path = Eio.Path.(Eio.Stdenv.fs env / source_path) in + let stat = Eio.Path.stat ~follow:true path in + let* mtime = Some stat.mtime in + let* ptime = Ptime.of_float_s mtime in + let (yyyy, mm, dd) = ptime |> Ptime.to_date_time |> fst in + Some (Date.{yyyy; mm = Some mm; dd = Some dd}) + +let render_trees ~cfg ~(forest : forest) ~render_only : unit = let env = cfg.env in let cwd = Eio.Stdenv.cwd env in Eio_util.ensure_dir @@ Eio.Path.(cwd / "build"); Eio_util.ensure_dir_path cwd ["output"; "resources"]; - run_renderer ~cfg forest @@ fun () -> - Compile.run @@ fun () -> - Serialise_xml_tree.run @@ fun () -> + let module I = + struct + let root, trees, run_query, last_changed, enqueue_latex = + cfg.root, forest.trees, forest.run_query, last_changed env forest, LaTeX_queue.enqueue + end + + in + + let module C = Compile.Make (I) () in + let module Sxml = Serialise_xml_tree.Make (I) () in + + let render_tree (tree : Sem.tree) = + let addr = tree.fm.addr in + let create = `Or_truncate 0o644 in + let path = Eio.Path.(cwd / "output" / Serialise_xml_tree.route ~root:cfg.root addr) in + Eio.Path.with_open_out ~create path @@ fun flow -> + Eio.Buf_write.with_flow flow @@ fun writer -> + let fmt = Eio_util.formatter_of_writer writer in + Sxml.pp ~stylesheet:cfg.stylesheet fmt @@ + C.compile_tree tree + in + let trees = match render_only with | None -> forest.trees |> M.to_seq |> Seq.map snd |> List.of_seq @@ -328,13 +241,14 @@ let render_trees ~cfg ~forest ~render_only : unit = | None -> Reporter.fatalf Tree_not_found "Could not find tree with address `%a` when rendering forest" pp_addr addr in + trees |> Sem.Util.sort - |> List.iter (render_tree ~cfg ~cwd); - render_json ~cwd forest.trees; + |> List.iter render_tree; + render_json ~cfg ~cwd forest.trees; if not cfg.no_assets then copy_assets ~env ~assets_dirs:cfg.assets_dirs; if not cfg.no_theme then copy_theme ~env ~theme_dir:cfg.theme_dir; - let _ = LaTeX_queue.process ~env ~max_fibers:cfg.max_fibers ~ignore_tex_cache:cfg.ignore_tex_cache in + let _ = LaTeX_queue.process ~env ~ignore_tex_cache:cfg.ignore_tex_cache in copy_resources ~env diff --git a/lib/frontend/Forest.mli b/lib/frontend/Forest.mli index 191e49e..b450382 100644 --- a/lib/frontend/Forest.mli +++ b/lib/frontend/Forest.mli @@ -5,18 +5,16 @@ type config = assets_dirs : Eio.Fs.dir_ty Eio.Path.t list; theme_dir : Eio.Fs.dir_ty Eio.Path.t; root : string option; - base_url : string option; stylesheet : string; ignore_tex_cache : bool; no_assets: bool; - no_theme: bool; - max_fibers : int} + no_theme: bool} type raw_forest = Code.tree list type forest = - {trees : Sem.tree Analysis.Map.t; - analysis : Analysis.analysis} + {trees : Sem.tree Addr_map.t; + run_query : addr Query.t -> Addr_set.t} val plant_forest : raw_forest -> forest val render_trees : cfg:config -> forest:forest -> render_only:addr list option -> unit @@ -26,5 +24,3 @@ val complete : forest:forest -> string -> (string * string) Seq.t val taxa : forest:forest-> (string * string) Seq.t val tags : forest:forest -> (string * string list) Seq.t -val run_renderer : cfg:config -> forest -> ( unit -> 'a) -> 'a -val render_json : cwd:[> Eio__.Fs.dir_ty ] Eio.Path.t -> Forester_core.Sem.tree Analysis.Map.t -> unit diff --git a/lib/frontend/Grammar.mly b/lib/frontend/Grammar.mly index 348056d..5c8277f 100644 --- a/lib/frontend/Grammar.mly +++ b/lib/frontend/Grammar.mly @@ -19,7 +19,7 @@ %token OBJECT PATCH CALL %token TRANSCLUDE SUBTREE SCOPE PUT GET DEFAULT ALLOC REF %token LBRACE RBRACE LSQUARE RSQUARE LPAREN RPAREN HASH_LBRACE HASH_HASH_LBRACE -%token QUERY_AND QUERY_OR QUERY_NOT QUERY_AUTHOR QUERY_TAG QUERY_TAXON QUERY_META +%token QUERY_AND QUERY_OR QUERY_NOT QUERY_AUTHOR QUERY_TAG QUERY_TAXON %token QUERY_TREE %token EOF @@ -118,13 +118,12 @@ let ident_with_method_calls := let query0 := -| QUERY_AUTHOR; ~ = arg; <Query.Author> -| QUERY_TAG; ~ = arg; <Query.Tag> -| QUERY_TAXON; ~ = arg; <Query.Taxon> -| QUERY_AND; ~ = braces(queries); <Query.And> -| QUERY_OR; ~ = braces(queries); <Query.Or> -| QUERY_NOT; ~ = braces(query); <Query.Not> -| QUERY_META; k = txt_arg; v = arg; <Query.Meta> +| QUERY_AUTHOR; x = arg; { Query.Rel (`Incoming, `Authorship, x) } +| QUERY_TAG; x = arg; { Query.Rel (`Incoming, `Tags, x) } +| QUERY_TAXON; x = arg; { Query.Rel (`Incoming, `Taxa, x) } +| QUERY_AND; ~ = braces(queries); <Query.isect> +| QUERY_OR; ~ = braces(queries); <Query.Union> +| QUERY_NOT; ~ = braces(query); <Query.complement> let queries == ws_list(query0) diff --git a/lib/frontend/Import_graph.ml b/lib/frontend/Import_graph.ml new file mode 100644 index 0000000..c9d3dd1 --- /dev/null +++ b/lib/frontend/Import_graph.ml @@ -0,0 +1,37 @@ +open Forester_core + +module Gph = +struct + module G = Graph.Imperative.Digraph.Concrete (Addr) + include G + include Graph.Oper.I (G) + + let safe_succ g x = + if mem_vertex g x then succ g x else [] + + let safe_pred g x = + if mem_vertex g x then pred g x else [] +end + +module Topo = Graph.Topological.Make (Gph) + +let build_import_graph (trees : Code.tree list) = + let import_graph = Gph.create () in + + let rec analyse_tree roots (tree : Code.tree) = + let roots = Option.fold ~none:roots ~some:(fun x -> x :: roots) tree.addr in + begin + tree.addr |> Option.iter @@ fun addr -> + Gph.add_vertex import_graph @@ User_addr addr + end; + tree.code |> List.iter @@ fun node -> + match Asai.Range.(node.value) with + | Code.Import (_, dep) -> + roots |> List.iter @@ fun addr -> Gph.add_edge import_graph (User_addr dep) (User_addr addr) + | Code.Subtree (addr, code) -> + analyse_tree roots @@ Code.{tree with addr; code} + | _ -> () + in + + trees |> List.iter (analyse_tree []); + import_graph diff --git a/lib/frontend/Import_graph.mli b/lib/frontend/Import_graph.mli new file mode 100644 index 0000000..1b534e3 --- /dev/null +++ b/lib/frontend/Import_graph.mli @@ -0,0 +1,13 @@ +open Forester_core + +module Gph : sig + type t + val safe_succ : t -> addr -> addr list + val safe_pred : t -> addr -> addr list +end + +module Topo : sig + val fold : (addr -> 'a -> 'a) -> Gph.t -> 'a -> 'a +end + +val build_import_graph : Code.tree list -> Gph.t diff --git a/lib/frontend/Lexer.mll b/lib/frontend/Lexer.mll index 3963b02..9d0b62c 100644 --- a/lib/frontend/Lexer.mll +++ b/lib/frontend/Lexer.mll @@ -71,7 +71,6 @@ rule token = | "\\query/author" {Grammar.QUERY_AUTHOR } | "\\query/tag" {Grammar.QUERY_TAG } | "\\query/taxon" {Grammar.QUERY_TAXON } - | "\\query/meta" {Grammar.QUERY_META } | "\\query" { Grammar.QUERY_TREE } | "\\p" { Grammar.PRIM `P } | "\\em" { Grammar.PRIM `Em } diff --git a/lib/frontend/Build_latex.ml b/lib/render/Build_latex.ml similarity index 98% rename from lib/frontend/Build_latex.ml rename to lib/render/Build_latex.ml index 3dbf004..428a110 100644 --- a/lib/frontend/Build_latex.ml +++ b/lib/render/Build_latex.ml @@ -1,6 +1,5 @@ open Eio.Std open Forester_prelude -open Forester_render type 'a env = 'a constraint 'a = < cwd : Eio.Fs.dir_ty Eio.Path.t; @@ -9,7 +8,6 @@ type 'a env = 'a constraint 'a = < .. > as 'a - let tex_fp name = Format.sprintf "%s.tex" name diff --git a/lib/frontend/Build_latex.mli b/lib/render/Build_latex.mli similarity index 100% rename from lib/frontend/Build_latex.mli rename to lib/render/Build_latex.mli diff --git a/lib/render/Compile.ml b/lib/render/Compile.ml index 67a07c7..066399e 100644 --- a/lib/render/Compile.ml +++ b/lib/render/Compile.ml @@ -1,395 +1,455 @@ open Forester_prelude open Forester_core +module type I = +sig + val root : string option + val trees : Sem.tree Addr_map.t + val run_query : addr Query.t -> Addr_set.t + val last_changed : addr -> Date.t option + val enqueue_latex : name:string -> preamble:string -> source:string -> unit +end + +module S = Addr_set + module X = Xml_tree module F = Xml_forester -module E = Render_effect.Perform - -module String_map = Map.Make (String) -module Addr_map = Map.Make (Addr) module Ancestors = Algaeff.Reader.Make (struct type t = addr list end) module Current_addr = Algaeff.Reader.Make (struct type t = addr end) -module Mainmatter_cache = Algaeff.State.Make (struct type t = X.content Addr_map.t end) -module Xmlns_map = +module Make (I : I) () = struct - type t = - {prefix_to_xmlns : string String_map.t; - xmlns_to_prefixes : string list String_map.t} - let empty = - {prefix_to_xmlns = String_map.empty; - xmlns_to_prefixes = String_map.empty} + let mainmatter_cache : (addr, X.content) Hashtbl.t = + Hashtbl.create 1000 + + module Xmlns_map = + struct + type t = + {prefix_to_xmlns : string String_map.t; + xmlns_to_prefixes : string list String_map.t} + + let empty = + {prefix_to_xmlns = String_map.empty; + xmlns_to_prefixes = String_map.empty} + + let assoc ~prefix ~xmlns env = + {prefix_to_xmlns = String_map.add prefix xmlns env.prefix_to_xmlns; + xmlns_to_prefixes = String_map.add_to_list xmlns prefix env.xmlns_to_prefixes} + end + + module Xmlns_prefixes = Algaeff.Reader.Make (Xmlns_map) + + let get_xmlns_for_prefix prefix = + let env = Xmlns_prefixes.read () in + String_map.find_opt prefix env.prefix_to_xmlns + + let rec normalise_prefix ?loc ~prefix ~xmlns kont = + match xmlns with + | Some xmlns -> + begin + let open Xmlns_map in + let env = Xmlns_prefixes.read () in + let exception Shadowing in + try + begin + match + String_map.find_opt prefix env.prefix_to_xmlns, + String_map.find_opt xmlns env.xmlns_to_prefixes + with + | None, (None | Some []) -> + let env = assoc ~prefix ~xmlns env in + Xmlns_prefixes.run ~env @@ fun () -> + kont @@ ([(prefix, xmlns)], prefix) + | Some xmlns', Some prefixes -> + if xmlns' = xmlns && List.mem prefix prefixes then + kont ([], prefix) + else + raise Shadowing + | _, Some (prefix' :: _) -> + kont ([], prefix') + | Some xmlns', None -> + raise Shadowing + end + with Shadowing -> + normalise_prefix ?loc ~prefix:(prefix ^ "_") ~xmlns:(Some xmlns) kont + end + | _ -> + kont ([], prefix) - let assoc ~prefix ~xmlns env = - {prefix_to_xmlns = String_map.add prefix xmlns env.prefix_to_xmlns; - xmlns_to_prefixes = String_map.add_to_list xmlns prefix env.xmlns_to_prefixes} -end + let is_root addr = + Some addr = Option.map (fun x -> User_addr x) I.root -module Xmlns_prefixes = Algaeff.Reader.Make (Xmlns_map) - -let get_xmlns_for_prefix prefix = - let env = Xmlns_prefixes.read () in - String_map.find_opt prefix env.prefix_to_xmlns - -let rec normalise_prefix ?loc ~prefix ~xmlns kont = - match xmlns with - | Some xmlns -> - begin - let open Xmlns_map in - let env = Xmlns_prefixes.read () in - let exception Shadowing in - try - begin - match - String_map.find_opt prefix env.prefix_to_xmlns, - String_map.find_opt xmlns env.xmlns_to_prefixes - with - | None, (None | Some []) -> - let env = assoc ~prefix ~xmlns env in - Xmlns_prefixes.run ~env @@ fun () -> - kont @@ ([(prefix, xmlns)], prefix) - | Some xmlns', Some prefixes -> - if xmlns' = xmlns && List.mem prefix prefixes then - kont ([], prefix) - else - raise Shadowing - | _, Some (prefix' :: _) -> - kont ([], prefix') - | Some xmlns', None -> - raise Shadowing - end - with Shadowing -> - normalise_prefix ?loc ~prefix:(prefix ^ "_") ~xmlns:(Some xmlns) kont - end - | _ -> - kont ([], prefix) - - -let compile_date (date : Date.t) = - let date_addr = User_addr (Format.asprintf "%a" Date.pp date) in - let href = E.get_doc date_addr |> Option.map @@ fun _doc -> E.route date_addr in - let year = Date.year date in - let month = Date.month date in - let day = Date.day date in - X.Date {href; year; month; day} - -let compile_dates = List.map compile_date - -let rec compile_located (located : Sem.node Range.located) = - match located.value with - | Sem.Text txt -> - [X.Text txt] - | Sem.Verbatim txt -> - [X.CDATA txt] - | Sem.Prim (p, xs) -> - compile_prim p xs - | Sem.Math (mode, xs) -> - let body = - let module TP = Render_TeX_like.Printer in - Str.global_replace (Str.regexp "\n") " " @@ - TP.contents @@ Render_TeX_like.render ~cfg:{tex = false} xs + let addr_peek_title scope = + Option.bind (Addr_map.find_opt scope I.trees) Sem.Util.peek_title + + + let get_tree addr = + Addr_map.find_opt addr I.trees + + let get_sorted_trees addrs : Sem.tree list = + let find addr = + match get_tree addr with + | None -> [] + | Some doc -> [doc] + in + Sem.Util.sort @@ List.concat_map find @@ S.elements addrs + + let get_trees_from_query query = + get_sorted_trees @@ I.run_query query + + let contributors scope = + let by_title = + Compare.under addr_peek_title @@ + Compare.option String.compare + in + List.sort by_title @@ S.elements @@ I.run_query @@ + let q_non_ref_under = + Query.isect [ + Query.tree_under scope; + Query.complement @@ Query.has_taxon "reference" + ] in - let display = - match mode with - | Inline -> `Inline - | Display -> `Block + let q_all_contributors = + Query.union_fam + q_non_ref_under + `Outgoing + `Contributorship in - [X.TeX {display; body}] + let q_authors = Query.rel `Outgoing `Authorship scope in + Query.isect [q_all_contributors; Query.complement q_authors] - | Sem.Link (addr, title, modifier) -> - begin - match E.get_doc addr with - | Some tree -> - compile_internal_link ~title ~modifier ~addr ~dest:tree - | None -> - let url = Format.asprintf "%a" pp_addr addr in - compile_external_link ~title ~modifier ~url - end - | Sem.Ref addr -> - begin - match E.get_doc addr with - | None -> - Reporter.fatalf ?loc:located.loc Tree_not_found "could not find tree at address `%a` for reference" pp_addr addr - | Some tree -> - let href = E.route addr in - let addr = Format.asprintf "%a" pp_addr addr in - let taxon = tree.fm.taxon |> Option.map String_util.sentence_case in - let number = tree.fm.number in - [X.Ref {addr; taxon; href; number}] - end - - | Sem.Img path -> - [X.Img {src = path}] - - | Sem.If_tex (_, xs) -> - X.splice @@ compile_nodes xs - - | Sem.Xml_tag (name, attrs, xs) -> - let rec fold_attrs tag_prefix updates acc attrs = - match attrs with - | [] -> - let xmlns_attrs = - updates |> List.map @@ fun (prefix, xmlns) -> - X.{key = - X.{prefix = "xmlns"; - uname = prefix; - xmlns = None}; - value = xmlns} - in - let name = X.{prefix = tag_prefix; uname = name.uname; xmlns = get_xmlns_for_prefix tag_prefix} in - let attrs = xmlns_attrs @ List.rev acc in - let content = compile_nodes xs in - X.Xml_tag {name; attrs; content} - - | (k, v) :: attrs -> - normalise_prefix ?loc:located.loc ~prefix:k.prefix ~xmlns:k.xmlns @@ fun (updates', prefix) -> - let xml_attr = - X.{key = X.{prefix; uname = k.uname; xmlns = None}; - value = Render_text.Printer.contents @@ Render_text.render v} - in - fold_attrs tag_prefix (updates @ updates') (xml_attr :: acc) attrs + let compile_date (date : Date.t) = + let addr = + let addr = User_addr (Format.asprintf "%a" Date.pp date) in + get_tree addr |> Option.map @@ fun _doc -> addr in + let year = Date.year date in + let month = Date.month date in + let day = Date.day date in + X.Date {addr; year; month; day} + + let compile_dates = List.map compile_date + + let rec compile_located (located : Sem.node Range.located) = + match located.value with + | Sem.Text txt -> + [X.Text txt] + | Sem.Verbatim txt -> + [X.CDATA txt] + | Sem.Prim (p, xs) -> + compile_prim p xs + | Sem.Math (mode, xs) -> + let body = + let module TP = Render_TeX_like.Printer in + Str.global_replace (Str.regexp "\n") " " @@ + TP.contents @@ Render_TeX_like.render ~cfg:{tex = false} xs + in + let display = + match mode with + | Inline -> `Inline + | Display -> `Block + in + [X.TeX {display; body}] - [normalise_prefix ~prefix:name.prefix ~xmlns:name.xmlns @@ fun (updates, tag_prefix) -> - fold_attrs tag_prefix updates [] attrs] + | Sem.Link (addr, title, modifier) -> + begin + match get_tree addr with + | Some tree -> + compile_internal_link ~title ~modifier ~addr ~dest:tree + | None -> + let url = Format.asprintf "%a" pp_addr addr in + compile_external_link ~title ~modifier ~url + end - | Sem.TeX_cs name -> - Reporter.fatalf ?loc:located.loc Resolution_error - "unresolved TeX control sequence `\\%a`" TeX_cs.pp name + | Sem.Ref addr -> + begin + match get_tree addr with + | None -> + Reporter.fatalf ?loc:located.loc Tree_not_found "could not find tree at address `%a` for reference" pp_addr addr + | Some tree -> + let taxon = tree.fm.taxon |> Option.map String_util.sentence_case in + let number = tree.fm.number in + [X.Ref {addr; taxon; number}] + end + + | Sem.Img path -> + [X.Img {src = path}] + + | Sem.If_tex (_, xs) -> + X.splice @@ compile_nodes xs + + | Sem.Xml_tag (name, attrs, xs) -> + let rec fold_attrs tag_prefix updates acc attrs = + match attrs with + | [] -> + let xmlns_attrs = + updates |> List.map @@ fun (prefix, xmlns) -> + X.{key = + X.{prefix = "xmlns"; + uname = prefix; + xmlns = None}; + value = xmlns} + in + let name = X.{prefix = tag_prefix; uname = name.uname; xmlns = get_xmlns_for_prefix tag_prefix} in + let attrs = xmlns_attrs @ List.rev acc in + let content = compile_nodes xs in + X.Xml_tag {name; attrs; content} + + | (k, v) :: attrs -> + normalise_prefix ?loc:located.loc ~prefix:k.prefix ~xmlns:k.xmlns @@ fun (updates', prefix) -> + let xml_attr = + X.{key = X.{prefix; uname = k.uname; xmlns = None}; + value = Render_text.Printer.contents @@ Render_text.render ~trees:I.trees v} + in + fold_attrs tag_prefix (updates @ updates') (xml_attr :: acc) attrs + in - | Sem.Object _ -> - Reporter.fatal ?loc:located.loc Type_error - "tried to compile object closure to XML" + [normalise_prefix ~prefix:name.prefix ~xmlns:name.xmlns @@ fun (updates, tag_prefix) -> + fold_attrs tag_prefix updates [] attrs] - | Sem.Embed_tex {preamble; source} -> - let as_tex x = - Render_TeX_like.Printer.contents @@ - Render_TeX_like.render ~cfg:{tex = true} x - in - let preamble = as_tex preamble in - let source = as_tex source in - let hash = Digest.to_hex @@ Digest.string @@ preamble ^ source in - E.enqueue_latex ~name:hash ~preamble ~source; - [X.Embedded_tex {hash; preamble; source}] - - | Sem.Transclude (opts, addr) -> - begin - match E.get_doc addr with - | None -> - Reporter.fatalf ?loc:located.loc Tree_not_found "could not find tree at address `%a` for transclusion" pp_addr addr - | Some doc -> - compile_transclusion ~opts doc - end - - | Sem.Subtree (opts, subtree) -> - compile_transclusion ~opts subtree - - | Sem.Query (opts, query) -> - let trees = E.run_query query in - begin - match trees with - | [] -> - [X.Prim (`P, X.Content [X.Info "Transclusion cycle"])] - | _ -> - trees |> List.concat_map @@ fun tree -> - let opts = Sem.{expanded = false; show_heading = true; title_override = None; taxon_override = None; toc = false; numbered = false; show_metadata = true} in - compile_transclusion ~opts tree - end - -and compile_transclusion ~opts (tree : Sem.tree) = - let current = Current_addr.read () in - let update old_ancestors = current :: old_ancestors in - Ancestors.scope update @@ fun () -> - [X.Subtree (compile_tree ~opts tree)] - -and compile_title ~(opts : Sem.transclusion_opts) (fm : Sem.frontmatter) = - let ancestors = Ancestors.read () in - let title = - match opts.title_override with - | Some title -> Some title - | None -> - fm.title |> Option.map @@ - Render_util.expand_title_with_parents ~ancestors fm - in - title |> Option.map @@ fun title -> - compile_nodes @@ Sem.sentence_case title - -and compile_attributions ~contributors ~authors = - match authors, contributors with - | [], [] -> [] - | _ -> - List.map compile_author authors @ List.map compile_contributor contributors - -and compile_author author = - X.Author (compile_attribution_inner author) - -and compile_contributor author = - X.Contributor (compile_attribution_inner author) - -and compile_attribution_inner author = - let exception Untitled in - try - match E.get_doc author with - | None -> raise Untitled - | Some biotree -> - let href = E.route biotree.fm.addr in - let content = - match biotree.fm.title with - | None -> raise Untitled - | Some title -> compile_nodes title + | Sem.TeX_cs name -> + Reporter.fatalf ?loc:located.loc Resolution_error + "unresolved TeX control sequence `\\%a`" TeX_cs.pp name + + | Sem.Object _ -> + Reporter.fatal ?loc:located.loc Type_error + "tried to compile object closure to XML" + + | Sem.Embed_tex {preamble; source} -> + let as_tex x = + Render_TeX_like.Printer.contents @@ + Render_TeX_like.render ~cfg:{tex = true} x in - let title = biotree.fm.title |> Option.map Sem.string_of_nodes in - let addr = Option.some @@ Format.asprintf "%a" pp_addr author in - X.Content [X.Link {type_ = `Local; href; title; addr; content}] - with Untitled -> - let name = Format.asprintf "%a" pp_addr author in - X.Content [X.Text name] - - -and compile_meta (key, body) = - let body = compile_nodes body in - X.Meta {key; body} - -and compile_frontmatter ~opts (fm : Sem.frontmatter) = - let anchor = string_of_int @@ Oo.id (object end) in - let title = compile_title ~opts fm in - let number = fm.number in - let taxon = - Option.map String_util.sentence_case @@ - match opts.taxon_override with - | Some taxon -> Some taxon - | None -> fm.taxon - in - let route = E.route fm.addr in - let source_path = fm.source_path in - let addr = Format.asprintf "%a" pp_addr fm.addr in - let designated_parent = - fm.designated_parent |> Option.map @@ fun addr -> - Format.asprintf "%a" pp_addr addr - in - let dates = compile_dates fm.dates in - let attributions = compile_attributions ~contributors:(E.contributors fm.addr) ~authors:fm.authors in - let last_changed = E.last_changed fm.addr |> Option.map compile_date in - let metas = fm.metas |> List.map compile_meta in - X.{title; - anchor; - number; - taxon; - designated_parent; - metas; - route; - addr; - source_path; - dates; - last_changed; - attributions} - -and compile_tree ?(backmatter = false) ~opts (tree : Sem.tree) = - Current_addr.run ~env:tree.fm.addr @@ fun () -> - let ancestors = Ancestors.read () in - let options = - X.{toc = opts.toc; - numbered = opts.numbered; - show_heading = opts.show_heading; - show_metadata = opts.show_metadata; - expanded = opts.expanded; - root = E.is_root tree.fm.addr} - in - let frontmatter = compile_frontmatter ~opts tree.fm in - let mainmatter = - begin - match tree.fm.addr with - | addr when List.mem addr ancestors -> - X.Content [X.Prim (`P, X.Content [X.Info "Transclusion cycle"])] - | addr -> - let cache = Mainmatter_cache.get () in - match Addr_map.find_opt addr cache with - | Some cached -> cached + let preamble = as_tex preamble in + let source = as_tex source in + let hash = Digest.to_hex @@ Digest.string @@ preamble ^ source in + I.enqueue_latex ~name:hash ~preamble ~source; + [X.Embedded_tex {hash; preamble; source}] + + | Sem.Transclude (opts, addr) -> + begin + match get_tree addr with | None -> - let result = compile_nodes tree.body in - Mainmatter_cache.modify @@ Addr_map.add addr result; - result - end; - - in - let backmatter = - if backmatter then - Some (compile_backmatter tree.fm.addr) - else - None - in - X.Tree {options; frontmatter; mainmatter; backmatter} - -and compile_backmatter addr = - let opts = {Sem.default_transclusion_opts with numbered = false} in - let compile_trees = - List.map @@ fun tree -> - X.splice_tree @@ compile_tree ~opts tree - in - let contributions = compile_trees @@ E.contributions addr in - let context = compile_trees @@ E.parents addr in - let related = compile_trees @@ E.related addr in - let backlinks = compile_trees @@ E.backlinks addr in - let references = compile_trees @@ E.bibliography addr in - [X.Contributions contributions; - X.Context context; - X.Related related; - X.Backlinks backlinks; - X.References references] - -and compile_internal_link ~title ~modifier ~addr ~dest = - let href = E.route addr in - let ancestors = Ancestors.read () in - let dest_title = - dest.fm.title |> Option.map @@ - Render_util.expand_title_with_parents ~ancestors dest.fm - in - let content = - title - |> Option.fold ~none:dest_title ~some:Option.some - |> Option.map (Sem.apply_modifier modifier) - |> Option.value ~default:[Range.locate_opt None @@ Sem.Text "Untitled"] - |> compile_nodes - in - let title = - match dest_title with - | None -> None - | Some t -> - let title_string = - String_util.sentence_case @@ - Render_text.Printer.contents @@ - Render_text.render t + Reporter.fatalf ?loc:located.loc Tree_not_found "could not find tree at address `%a` for transclusion" pp_addr addr + | Some doc -> + compile_transclusion ~opts doc + end + + | Sem.Subtree (opts, subtree) -> + compile_transclusion ~opts subtree + + | Sem.Query (opts, query) -> + begin + match get_trees_from_query query with + | [] -> + [X.Prim (`P, X.Content [X.Info "Query returned no results"])] + | trees -> + trees |> List.concat_map @@ fun tree -> + let opts = Sem.{expanded = false; show_heading = true; title_override = None; taxon_override = None; toc = false; numbered = false; show_metadata = true} in + compile_transclusion ~opts tree + end + + and compile_transclusion ~opts (tree : Sem.tree) = + let current = Current_addr.read () in + let update old_ancestors = current :: old_ancestors in + Ancestors.scope update @@ fun () -> + [X.Subtree (compile_tree_inner ~opts tree)] + + and compile_title ~(opts : Sem.transclusion_opts) (fm : Sem.frontmatter) = + let trees = I.trees in + let ancestors = Ancestors.read () in + let title = + match opts.title_override with + | Some title -> Some title + | None -> + fm.title |> Option.map @@ + Render_util.expand_title_with_parents ~trees ~ancestors fm + in + title |> Option.map @@ fun title -> + compile_nodes @@ Sem.sentence_case title + + and compile_attributions ~contributors ~authors = + match authors, contributors with + | [], [] -> [] + | _ -> + List.map compile_author authors @ List.map compile_contributor contributors + + and compile_author author = + X.Author (compile_attribution_inner author) + + and compile_contributor author = + X.Contributor (compile_attribution_inner author) + + and compile_attribution_inner author = + let exception Untitled in + try + match get_tree author with + | None -> raise Untitled + | Some biotree -> + let content = + match biotree.fm.title with + | None -> raise Untitled + | Some title -> compile_nodes title + in + let title = biotree.fm.title |> Option.map Sem.string_of_nodes in + let addr = Option.some @@ Format.asprintf "%a" pp_addr author in + X.Content [X.Local_link {title; addr = author; content}] + with Untitled -> + let name = Format.asprintf "%a" pp_addr author in + X.Content [X.Text name] + + + and compile_meta (key, body) = + let body = compile_nodes body in + X.Meta {key; body} + + and compile_frontmatter ~opts (fm : Sem.frontmatter) = + let anchor = Option.some @@ string_of_int @@ Oo.id (object end) in + let title = compile_title ~opts fm in + let number = fm.number in + let taxon = + Option.map String_util.sentence_case @@ + match opts.taxon_override with + | Some taxon -> Some taxon + | None -> fm.taxon + in + let source_path = fm.source_path in + let addr = Option.some fm.addr in + let designated_parent = + fm.designated_parent |> Option.map @@ fun addr -> + Format.asprintf "%a" pp_addr addr + in + let dates = compile_dates fm.dates in + let contributors = contributors fm.addr in + let attributions = compile_attributions ~contributors ~authors:fm.authors in + let last_changed = I.last_changed fm.addr |> Option.map compile_date in + let metas = fm.metas |> List.map compile_meta in + X.{title; + anchor; + number; + taxon; + designated_parent; + metas; + addr; + source_path; + dates; + last_changed; + attributions} + + and compile_tree_inner ?(include_backmatter = false) ~opts (tree : Sem.tree) = + Current_addr.run ~env:tree.fm.addr @@ fun () -> + let ancestors = Ancestors.read () in + let options = + X.{toc = opts.toc; + numbered = opts.numbered; + show_heading = opts.show_heading; + show_metadata = opts.show_metadata; + expanded = opts.expanded; + root = is_root tree.fm.addr} + in + let frontmatter = compile_frontmatter ~opts tree.fm in + let mainmatter = + begin + match tree.fm.addr with + | addr when List.mem addr ancestors -> + X.Content [X.Prim (`P, X.Content [X.Info "Transclusion cycle"])] + | addr -> + match Hashtbl.find_opt mainmatter_cache addr with + | Some cached -> cached + | None -> + let result = compile_nodes tree.body in + Hashtbl.add mainmatter_cache addr result; + result + end; + in + let backmatter = + if include_backmatter && not (is_root tree.fm.addr) then + compile_backmatter tree.fm.addr tree.bm + else + [] + in + X.Tree {options; frontmatter; mainmatter; backmatter} + + and compile_backmatter addr bm = + let opts = {Sem.default_transclusion_opts with numbered = false} in + let compile_trees = + List.map @@ fun tree -> + X.splice_tree @@ compile_tree_inner ~opts tree + in + bm |> List.filter_map @@ function + | Sem.Backmatter_section {title; query} -> + let title = + Option.some @@ compile_nodes @@ + Sem.sentence_case title in - Some title_string - in - let addr = Some (Format.asprintf "%a" pp_addr addr) in - [X.Link {type_ = `Local; href; title; content; addr}] - -and compile_external_link ~title ~modifier ~url = - let href = url in - let content = - title - |> Option.map (Sem.apply_modifier modifier) - |> Option.value ~default:[Range.locate_opt None @@ Sem.Text url] - |> compile_nodes - in - [X.Link {type_ = `External; href; content; title = None; addr = None}]; - -and compile_nodes (xs : Sem.t) = X.Content (List.concat_map compile_located xs) - -and compile_prim p xs = - let content = compile_nodes xs in - [X.Prim (p, content)] - -let compile_tree_top tree = - Ancestors.run ~env:[] @@ fun () -> - let env = Xmlns_map.assoc ~prefix:F.reserved_prefix ~xmlns:F.forester_xmlns Xmlns_map.empty in - Xmlns_prefixes.run ~env @@ fun () -> - compile_tree ~backmatter:true ~opts:Sem.default_transclusion_opts tree - - -let run kont = - Mainmatter_cache.run ~init:Addr_map.empty kont + match compile_trees @@ get_trees_from_query query with + | [] -> None + | trees -> + let options = + X.{toc = false; expanded = true; numbered = false; show_heading = true; show_metadata = false; root = false} + in + let frontmatter = + X.{title; anchor = None; number = None; taxon = None; designated_parent = None; metas = []; addr = None; source_path = None; dates = []; last_changed = None; attributions = []} + in + let mainmatter = + X.Content begin + trees |> List.map @@ fun tree -> + let options = X.{tree.options with expanded = false} in + let tree = X.{tree with options} in + X.Subtree (X.Tree tree) + end + in + Option.some @@ X.{options; frontmatter; mainmatter; backmatter = []} + + and compile_internal_link ~title ~modifier ~addr ~dest = + let trees = I.trees in + let ancestors = Ancestors.read () in + let dest_title = + dest.fm.title |> Option.map @@ + Render_util.expand_title_with_parents ~trees ~ancestors dest.fm + in + let content = + title + |> Option.fold ~none:dest_title ~some:Option.some + |> Option.map (Sem.apply_modifier modifier) + |> Option.value ~default:[Range.locate_opt None @@ Sem.Text "Untitled"] + |> compile_nodes + in + let title = + match dest_title with + | None -> None + | Some t -> + let title_string = + String_util.sentence_case @@ + Render_text.Printer.contents @@ + Render_text.render ~trees t + in + Some title_string + in + [X.Local_link {title; content; addr = addr}] + + and compile_external_link ~title ~modifier ~url = + let href = url in + let content = + title + |> Option.map (Sem.apply_modifier modifier) + |> Option.value ~default:[Range.locate_opt None @@ Sem.Text url] + |> compile_nodes + in + [X.External_link {href; content; title = None}]; + + and compile_nodes (xs : Sem.t) = X.Content (List.concat_map compile_located xs) + + and compile_prim p xs = + let content = compile_nodes xs in + [X.Prim (p, content)] + + let compile_tree tree = + Ancestors.run ~env:[] @@ fun () -> + let env = Xmlns_map.assoc ~prefix:F.reserved_prefix ~xmlns:F.forester_xmlns Xmlns_map.empty in + Xmlns_prefixes.run ~env @@ fun () -> + compile_tree_inner ~include_backmatter:true ~opts:Sem.default_transclusion_opts tree +end \ No newline at end of file diff --git a/lib/render/Compile.mli b/lib/render/Compile.mli index ef8f7e5..b389a19 100644 --- a/lib/render/Compile.mli +++ b/lib/render/Compile.mli @@ -1,7 +1,17 @@ +open Forester_prelude open Forester_core -val run : (unit -> 'a) -> 'a -(** Initialises a cache for tree mainmatters in the given scope. *) -val compile_tree_top : Sem.tree -> Xml_tree.tree_ -(** Must be called in the scope of {!run}. *) +module type I = +sig + val root : string option + val trees : Sem.tree Addr_map.t + val run_query : addr Query.t -> Addr_set.t + val last_changed : addr -> Date.t option + val enqueue_latex : name:string -> preamble:string -> source:string -> unit +end + +module Make (_ : I) () : +sig + val compile_tree : Sem.tree -> Xml_tree.tree_ +end \ No newline at end of file diff --git a/lib/frontend/LaTeX_queue.ml b/lib/render/LaTeX_queue.ml similarity index 68% rename from lib/frontend/LaTeX_queue.ml rename to lib/render/LaTeX_queue.ml index 6a66e77..3525b96 100644 --- a/lib/frontend/LaTeX_queue.ml +++ b/lib/render/LaTeX_queue.ml @@ -1,7 +1,7 @@ module type S = sig val enqueue : name:string -> preamble:string -> source:string -> unit - val process : env:_ Build_latex.env -> max_fibers:int -> ignore_tex_cache : bool -> Eio.Fs.dir_ty Eio.Path.t list + val process : env:_ Build_latex.env -> ignore_tex_cache : bool -> Eio.Fs.dir_ty Eio.Path.t list end module Make () : S = @@ -12,12 +12,12 @@ struct if not @@ Hashtbl.mem svg_queue name then Hashtbl.add svg_queue name (preamble, source) - let process ~env ~max_fibers ~ignore_tex_cache : Eio.Fs.dir_ty Eio.Path.t list = + let process ~env ~ignore_tex_cache : Eio.Fs.dir_ty Eio.Path.t list = let build (name, (preamble, source)) = Build_latex.build_latex ~ignore_tex_cache ~env ~name ~source ~preamble in Hashtbl.to_seq svg_queue |> List.of_seq - |> Eio.Fiber.List.map ~max_fibers build + |> Eio.Fiber.List.map ~max_fibers:20 build |> List.concat end diff --git a/lib/frontend/LaTeX_queue.mli b/lib/render/LaTeX_queue.mli similarity index 50% rename from lib/frontend/LaTeX_queue.mli rename to lib/render/LaTeX_queue.mli index 2776274..3aaa864 100644 --- a/lib/frontend/LaTeX_queue.mli +++ b/lib/render/LaTeX_queue.mli @@ -1,7 +1,7 @@ module type S = sig val enqueue : name:string -> preamble:string -> source:string -> unit - val process : env:_ Build_latex.env -> max_fibers:int -> ignore_tex_cache : bool -> Eio.Fs.dir_ty Eio.Path.t list + val process : env:_ Build_latex.env -> ignore_tex_cache : bool -> Eio.Fs.dir_ty Eio.Path.t list end module Make () : S diff --git a/lib/frontend/LaTeX_template.ml b/lib/render/LaTeX_template.ml similarity index 100% rename from lib/frontend/LaTeX_template.ml rename to lib/render/LaTeX_template.ml diff --git a/lib/frontend/LaTeX_template.mli b/lib/render/LaTeX_template.mli similarity index 100% rename from lib/frontend/LaTeX_template.mli rename to lib/render/LaTeX_template.mli diff --git a/lib/render/Render_effect.ml b/lib/render/Render_effect.ml deleted file mode 100644 index 6573c59..0000000 --- a/lib/render/Render_effect.ml @@ -1,87 +0,0 @@ -open Forester_prelude -open Forester_core - -module type Handler = -sig - val route : addr -> string - val is_root : addr -> bool - val backlinks : addr -> Sem.tree list - val related : addr -> Sem.tree list - val bibliography : addr -> Sem.tree list - val parents : addr -> Sem.tree list - val contributors : addr -> addr list - val contributions : addr -> Sem.tree list - val enqueue_latex : name:string -> preamble:string -> source:string -> unit - val get_doc : addr -> Sem.tree option - val run_query : Sem.t Query.t -> Sem.tree list - val last_changed : addr -> Date.t option -end - -type _ Effect.t += - | Route : addr -> string Effect.t - | Is_root : addr -> bool Effect.t - | Backlinks : addr -> Sem.tree list Effect.t - | Related : addr -> Sem.tree list Effect.t - | Bibliography : addr -> Sem.tree list Effect.t - | Parents : addr -> Sem.tree list Effect.t - | Contributions : addr -> Sem.tree list Effect.t - | Contributors : addr -> addr list Effect.t - | Enqueue_latex : {name : string; preamble : string; source : string} -> unit Effect.t - | Get_doc : addr -> Sem.tree option Effect.t - | Run_query : Sem.t Query.t -> Sem.tree list Effect.t - | Last_changed : addr -> Date.t option Effect.t - -module Perform : Handler = -struct - let route addr = Effect.perform @@ Route addr - let is_root addr = Effect.perform @@ Is_root addr - let backlinks addr = Effect.perform @@ Backlinks addr - let related addr = Effect.perform @@ Related addr - let bibliography addr = Effect.perform @@ Bibliography addr - let contributions addr = Effect.perform @@ Contributions addr - let parents addr = Effect.perform @@ Parents addr - let contributors addr = Effect.perform @@ Contributors addr - let enqueue_latex ~name ~preamble ~source = Effect.perform @@ Enqueue_latex {name; preamble; source} - let get_doc addr = Effect.perform @@ Get_doc addr - let run_query query = Effect.perform @@ Run_query query - let last_changed addr = Effect.perform @@ Last_changed addr -end - -module Run (H : Handler) = -struct - let run f = - Effect.Deep.try_with f () @@ - {effc = - fun (type a) (eff : a Effect.t) -> - let resume x = - Option.some @@ fun (k : (a, _) Effect.Deep.continuation) -> - Algaeff.Fun.Deep.finally k @@ fun () -> x () - in - match eff with - | Route addr -> - resume @@ fun () -> H.route addr - | Is_root addr -> - resume @@ fun () -> H.is_root addr - | Backlinks addr -> - resume @@ fun () -> H.backlinks addr - | Related addr -> - resume @@ fun () -> H.related addr - | Bibliography addr -> - resume @@ fun () -> H.bibliography addr - | Parents addr -> - resume @@ fun () -> H.parents addr - | Contributors addr -> - resume @@ fun () -> H.contributors addr - | Contributions addr -> - resume @@ fun () -> H.contributions addr - | Enqueue_latex {name; preamble; source} -> - resume @@ fun () -> H.enqueue_latex ~name ~preamble ~source - | Get_doc addr -> - resume @@ fun () -> H.get_doc addr - | Run_query query -> - resume @@ fun () -> H.run_query query - | Last_changed addr -> - resume @@ fun () -> H.last_changed addr - | _ -> - None} -end diff --git a/lib/render/Render_effect.mli b/lib/render/Render_effect.mli deleted file mode 100644 index e4ddc5e..0000000 --- a/lib/render/Render_effect.mli @@ -1,25 +0,0 @@ -open Forester_prelude -open Forester_core - -module type Handler = -sig - val route : addr -> string - val is_root : addr -> bool - val backlinks : addr -> Sem.tree list - val related : addr -> Sem.tree list - val bibliography : addr -> Sem.tree list - val parents : addr -> Sem.tree list - val contributors : addr -> addr list - val contributions : addr -> Sem.tree list - val enqueue_latex : name:string -> preamble:string -> source:string -> unit - val get_doc : addr -> Sem.tree option - val run_query : Sem.t Query.t -> Sem.tree list - val last_changed : addr -> Date.t option -end - -module Perform : Handler - -module Run (_ : Handler) : -sig - val run : (unit -> 'a) -> 'a -end diff --git a/lib/render/Render_json.ml b/lib/render/Render_json.ml index a4663ab..71d47ec 100644 --- a/lib/render/Render_json.ml +++ b/lib/render/Render_json.ml @@ -2,20 +2,18 @@ open Forester_prelude open Forester_core open Sem -module E = Render_effect.Perform - -let render_tree ~dev (doc : Sem.tree) = +let render_tree ~root ~trees ~dev (doc : Sem.tree) = let addr = doc.fm.addr in let title = match doc.fm.title with | None -> `Null | Some title -> - let title = Render_util.expand_title_with_parents doc.fm title in + let title = Render_util.expand_title_with_parents ~trees doc.fm title in let title_string = String.trim @@ String_util.sentence_case @@ Render_text.Printer.contents @@ - Render_text.render title + Render_text.render ~trees title in `String title_string in @@ -26,13 +24,13 @@ let render_tree ~dev (doc : Sem.tree) = | Some taxon -> `String (String_util.sentence_case taxon) in let tags = `List (List.map (fun t -> `String t) doc.fm.tags) in - let route = `String (E.route addr) in + let route = `String (Serialise_xml_tree.route ~root addr) in let metas = let meta_string meta = String.trim @@ String_util.sentence_case @@ Render_text.Printer.contents @@ - Render_text.render meta + Render_text.render ~trees meta in `Assoc (List.map (fun (s, meta) -> (s, `String (meta_string meta))) @@ -60,6 +58,12 @@ let render_tree ~dev (doc : Sem.tree) = ])) | _ -> None -let render_trees ~(dev : bool) (docs : Sem.tree list) : Yojson.Basic.t = - `Assoc (List.filter_map (render_tree ~dev) docs) +let render_trees ~(dev : bool) ~root (trees : Sem.tree Addr_map.t) : Yojson.Basic.t = + `Assoc begin + Addr_map.to_seq trees + |> Seq.map snd + |> List.of_seq + |> Sem.Util.sort_for_index + |> List.filter_map (render_tree ~root ~trees ~dev) + end diff --git a/lib/render/Render_json.mli b/lib/render/Render_json.mli index 2472f2a..7b0ca80 100644 --- a/lib/render/Render_json.mli +++ b/lib/render/Render_json.mli @@ -1,4 +1,4 @@ open Forester_prelude open Forester_core -val render_trees : dev:bool -> Sem.tree list -> Yojson.Basic.t +val render_trees : dev:bool -> root:string option -> Sem.tree Addr_map.t -> Yojson.Basic.t diff --git a/lib/render/Render_text.ml b/lib/render/Render_text.ml index 8f8e988..a14b2e9 100644 --- a/lib/render/Render_text.ml +++ b/lib/render/Render_text.ml @@ -1,8 +1,6 @@ open Forester_prelude open Forester_core -module E = Render_effect.Perform - module Printer = struct module P0 = @@ -18,30 +16,30 @@ struct Format.asprintf "%a" (fun fmt _ -> printer fmt) () end -let rec render_node : Sem.node Range.located -> Printer.t = +let rec render_node ~trees : Sem.node Range.located -> Printer.t = fun node -> match node.value with | Sem.Text txt | Sem.Verbatim txt -> Printer.text txt | Sem.Math (_, xs) -> - render xs + render ~trees xs | Sem.Xml_tag (name, _, body) -> - render body + render ~trees body | Sem.Link (addr, None, modifier) -> - render @@ + render ~trees @@ Option.value ~default:[Range.locate_opt None @@ Sem.Text "Untitled"] @@ - Option.bind (E.get_doc addr) @@ fun doc -> - Option.map (Sem.apply_modifier modifier) doc.fm.title + Option.bind (Addr_map.find_opt addr trees) @@ fun (tree : Sem.tree) -> + Option.map (Sem.apply_modifier modifier) tree.fm.title | Sem.Link (addr, Some title, modifier) -> - render @@ Sem.apply_modifier modifier title + render ~trees @@ Sem.apply_modifier modifier title | Sem.If_tex (_, y) -> - render y + render ~trees y | Sem.Prim (_, x) -> - render x + render ~trees x | Sem.TeX_cs _ -> Printer.nil | _ -> Reporter.fatal ?loc:node.loc Unhandled_case "unhandled case in plain text renderer" -and render xs = - Printer.iter render_node xs +and render ~trees xs = + Printer.iter (render_node ~trees) xs diff --git a/lib/render/Render_text.mli b/lib/render/Render_text.mli index accf34e..8db99b6 100644 --- a/lib/render/Render_text.mli +++ b/lib/render/Render_text.mli @@ -8,4 +8,4 @@ sig val contents : t -> string end -val render : Sem.t -> Printer.t +val render : trees:Sem.tree Addr_map.t -> Sem.t -> Printer.t diff --git a/lib/render/Render_util.ml b/lib/render/Render_util.ml index 2dca3e5..712076c 100644 --- a/lib/render/Render_util.ml +++ b/lib/render/Render_util.ml @@ -1,16 +1,18 @@ open Forester_core -module E = Render_effect.Perform -let rec expand_title_with_parents ?(ancestors = []) (fm : Sem.frontmatter) title = +let rec expand_title_with_parents ~trees ?(ancestors = []) (fm : Sem.frontmatter) title = match fm.designated_parent with | Some parent_addr when not @@ List.mem parent_addr ancestors -> begin - match E.get_doc parent_addr with + match Addr_map.find_opt parent_addr trees with | None -> title - | Some parent -> + | Some (parent : Sem.tree) -> let chevron = [Range.locate_opt None @@ Sem.Text " › "] in - let parent_title = parent.fm.title |> Option.map @@ expand_title_with_parents parent.fm in + let parent_title = + parent.fm.title |> + Option.map @@ expand_title_with_parents ~trees parent.fm + in let parent_link = [Range.locate_opt None @@ Sem.Link (parent_addr, parent_title, Sentence_case)] diff --git a/lib/render/Serialise_xml_tree.ml b/lib/render/Serialise_xml_tree.ml index e61450a..4cb22ef 100644 --- a/lib/render/Serialise_xml_tree.ml +++ b/lib/render/Serialise_xml_tree.ml @@ -5,165 +5,176 @@ module X = Xml_tree module F = Xml_forester module P = Pure_html -module String_map = Map.Make (String) - -module Mainmatter_cache = Algaeff.State.Make (struct type t = P.node String_map.t end) - -let render_xml_qname = - function - | X.{prefix = ""; uname; _} -> uname - | X.{prefix; uname; _} -> Format.sprintf "%s:%s" prefix uname - -let render_xml_attr X.{key; value} = - P.string_attr (render_xml_qname key) "%s" value - -let render_date (X.Date date) = - F.date [ - date.href |> F.optional_ @@ F.href "%s" - ] [ - F.year [] "%i" date.year; - date.month |> F.optional @@ F.month [] "%i"; - date.day |> F.optional @@ F.day [] "%i" - ] - -let rec render_tree (X.Tree tree) = - F.tree [ - F.toc tree.options.toc; - F.numbered tree.options.numbered; - F.show_heading tree.options.show_heading; - F.show_metadata tree.options.show_metadata; - F.expanded tree.options.expanded; - F.root tree.options.root; - P.string_attr ("xmlns:" ^ F.reserved_prefix) "%s" F.forester_xmlns - ] [ - render_frontmatter tree.frontmatter; - begin - let cache = Mainmatter_cache.get () in - let key = tree.frontmatter.route in - match String_map.find_opt key cache with - | Some cached -> cached - | None -> - let result = render_mainmatter tree.mainmatter in - Mainmatter_cache.modify (String_map.add key result); - result - end; - tree.backmatter |> F.optional render_backmatter - ] - -and render_frontmatter (fm : _ X.frontmatter) = - F.frontmatter [] [ - F.anchor [] "%s" fm.anchor; - F.addr [] "%s" fm.addr; - F.route [] "%s" fm.route; - fm.title |> Option.map render_content |> F.optional @@ F.title []; - fm.taxon |> F.optional @@ F.taxon [] "%s"; - fm.source_path |> F.optional @@ F.source_path [] "%s"; - fm.dates |> List.map render_date |> F.null; - F.authors [] @@ List.map render_attribution_elt fm.attributions; - fm.number |> F.optional @@ F.number [] "%s"; - fm.designated_parent |> F.optional @@ F.parent [] "%s"; - fm.metas |> List.map render_meta |> F.null - ] - -and render_mainmatter mm = - F.mainmatter [] @@ render_content mm - -and render_backmatter (bm : _ X.backmatter) = - F.backmatter [] @@ List.map render_backmatter_elt bm - -and render_backmatter_elt = - let render_trees trees = - trees |> List.map @@ fun tree -> - render_tree @@ X.Tree tree +module type I = sig val root : string option end + +let addr_to_string addr = + Format.asprintf "%a" pp_addr addr + +let route ~root addr = + let is_root = Some addr = Option.map (fun x -> User_addr x) root in + let ext = "xml" in + let base = + match is_root with + | true -> "index" + | false -> + match addr with + | User_addr addr -> addr + | Machine_addr ix -> Format.sprintf "unstable-%i" ix in - function - | Contributions trees -> - F.contributions [] @@ render_trees trees - | Context trees -> - F.context [] @@ render_trees trees - | Related trees -> - F.related [] @@ render_trees trees - | Backlinks trees -> - F.backlinks [] @@ render_trees trees - | References trees -> - F.references [] @@ render_trees trees - -and render_meta (Meta meta) = - F.meta [F.name "%s" meta.key] @@ - render_content meta.body - -and render_attribution_elt = - function - | X.Author x -> - F.author [] @@ render_content x - | X.Contributor x -> - F.contributor [] @@ render_content x - -and render_content (X.Content xs) = - List.map render_content_node xs - - -and render_content_node = - function - | X.Text x -> - P.txt "%s" x - | X.CDATA x -> - P.txt ~raw:true "<![CDATA[%s]]>" x - | X.Prim (p, x) -> - F.prim p [] @@ render_content x - | X.Xml_tag {name; attrs; content} -> - P.std_tag - (render_xml_qname name) - (List.map render_xml_attr attrs) - (render_content content) - | X.Subtree tree -> - render_tree tree - | X.Ref ref -> - F.ref [ - F.addr_ "%s" ref.addr; - F.href "%s" ref.href; - ref.taxon |> F.optional_ @@ F.taxon_ "%s"; - ref.number |> F.optional_ @@ F.number_ "%s" + Format.asprintf "%s.%s" base ext + +module Make (I : I) () = +struct + let mainmatter_cache : (addr, P.node) Hashtbl.t = + Hashtbl.create 1000 + + let route = route ~root:I.root + + let render_xml_qname = + function + | X.{prefix = ""; uname; _} -> uname + | X.{prefix; uname; _} -> Format.sprintf "%s:%s" prefix uname + + let render_xml_attr X.{key; value} = + P.string_attr (render_xml_qname key) "%s" value + + let render_date (X.Date date) = + F.date [ + date.addr |> F.optional_ @@ fun addr -> F.href "%s" @@ route addr + ] [ + F.year [] "%i" date.year; + date.month |> F.optional @@ F.month [] "%i"; + date.day |> F.optional @@ F.day [] "%i" + ] + + let rec render_tree (X.Tree tree) = + F.tree [ + F.toc tree.options.toc; + F.numbered tree.options.numbered; + F.show_heading tree.options.show_heading; + F.show_metadata tree.options.show_metadata; + F.expanded tree.options.expanded; + F.root tree.options.root; + P.string_attr ("xmlns:" ^ F.reserved_prefix) "%s" F.forester_xmlns + ] [ + render_frontmatter tree.frontmatter; + begin + match tree.frontmatter.addr with + | None -> render_mainmatter tree.mainmatter + | Some key -> + match Hashtbl.find_opt mainmatter_cache key with + | Some cached -> cached + | None -> + let result = render_mainmatter tree.mainmatter in + Hashtbl.add mainmatter_cache key result; + result + end; + render_backmatter @@ + List.map (fun x -> X.Tree x) tree.backmatter ] - | X.Link link -> - let type_ = - match link.type_ with - | `Local -> "local" - | `External -> "external" - in - F.link [ - F.type_ "%s" type_; - F.href "%s" link.href; - link.addr |> F.optional_ @@ F.addr_ "%s"; - link.title |> F.optional_ @@ F.title_ "%s" - ] @@ render_content link.content - | X.TeX tex -> - let display = - match tex.display with - | `Inline -> "inline" - | `Block -> "block" - in - F.tex [F.display "%s" display] "<![CDATA[%s]]>" tex.body - | X.Img img -> - F.img [F.src "%s" img.src] - | X.Embedded_tex emb -> - F.embedded_tex [F.hash "%s" emb.hash] [ - F.embedded_tex_preamble [] "<![CDATA[%s]]>" emb.preamble; - F.embedded_tex_body [] "<![CDATA[%s]]>" emb.source + + and render_frontmatter (fm : _ X.frontmatter) = + F.frontmatter [] [ + fm.anchor |> F.optional @@ F.anchor [] "%s"; + begin + match fm.addr with + | None -> F.null [] + | Some addr -> + F.null [ + F.addr [] "%s" (addr_to_string addr); + F.route [] "%s" (route addr) + ] + end; + fm.title |> Option.map render_content |> F.optional @@ F.title []; + fm.taxon |> F.optional @@ F.taxon [] "%s"; + fm.source_path |> F.optional @@ F.source_path [] "%s"; + fm.dates |> List.map render_date |> F.null; + F.authors [] @@ List.map render_attribution_elt fm.attributions; + fm.number |> F.optional @@ F.number [] "%s"; + fm.designated_parent |> F.optional @@ F.parent [] "%s"; + fm.metas |> List.map render_meta |> F.null ] - | X.Info x -> - F.info [] [P.txt "%s" x] - - -let pp ?stylesheet fmt tree = - Format.fprintf fmt {|<?xml version="1.0" encoding="UTF-8"?>|}; - Format.pp_print_newline fmt (); - begin - stylesheet |> Option.iter @@ fun uri -> - Format.fprintf fmt "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>" uri - end; - Format.pp_print_newline fmt (); - P.pp_xml fmt @@ render_tree tree - -let run kont = - Mainmatter_cache.run ~init:String_map.empty kont + + and render_mainmatter mm = + F.mainmatter [] @@ render_content mm + + and render_backmatter (bm : X.tree_ list) = + F.backmatter [] @@ List.map render_tree bm + + and render_meta (Meta meta) = + F.meta [F.name "%s" meta.key] @@ + render_content meta.body + + and render_attribution_elt = + function + | X.Author x -> + F.author [] @@ render_content x + | X.Contributor x -> + F.contributor [] @@ render_content x + + and render_content (X.Content xs) = + List.map render_content_node xs + + + and render_content_node = + function + | X.Text x -> + P.txt "%s" x + | X.CDATA x -> + P.txt ~raw:true "<![CDATA[%s]]>" x + | X.Prim (p, x) -> + F.prim p [] @@ render_content x + | X.Xml_tag {name; attrs; content} -> + P.std_tag + (render_xml_qname name) + (List.map render_xml_attr attrs) + (render_content content) + | X.Subtree tree -> + render_tree tree + | X.Ref ref -> + F.ref [ + F.addr_ "%s" (addr_to_string ref.addr); + ref.taxon |> F.optional_ @@ F.taxon_ "%s"; + ref.number |> F.optional_ @@ F.number_ "%s" + ] + | X.Local_link link -> + F.link [ + F.type_ "local"; + F.href "%s" @@ route link.addr; + F.addr_ "%s" @@ addr_to_string link.addr; + link.title |> F.optional_ @@ F.title_ "%s" + ] @@ render_content link.content + + | X.External_link link -> + F.link [ + F.type_ "external"; + F.href "%s" link.href; + link.title |> F.optional_ @@ F.title_ "%s" + ] @@ render_content link.content + | X.TeX tex -> + let display = + match tex.display with + | `Inline -> "inline" + | `Block -> "block" + in + F.tex [F.display "%s" display] "<![CDATA[%s]]>" tex.body + | X.Img img -> + F.img [F.src "%s" img.src] + | X.Embedded_tex emb -> + F.embedded_tex [F.hash "%s" emb.hash] [ + F.embedded_tex_preamble [] "<![CDATA[%s]]>" emb.preamble; + F.embedded_tex_body [] "<![CDATA[%s]]>" emb.source + ] + | X.Info x -> + F.info [] [P.txt "%s" x] + + let pp ?stylesheet fmt tree = + Format.fprintf fmt {|<?xml version="1.0" encoding="UTF-8"?>|}; + Format.pp_print_newline fmt (); + begin + stylesheet |> Option.iter @@ fun uri -> + Format.fprintf fmt "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>" uri + end; + Format.pp_print_newline fmt (); + P.pp_xml fmt @@ render_tree tree +end \ No newline at end of file diff --git a/lib/render/Serialise_xml_tree.mli b/lib/render/Serialise_xml_tree.mli index 0487f56..8727348 100644 --- a/lib/render/Serialise_xml_tree.mli +++ b/lib/render/Serialise_xml_tree.mli @@ -1,7 +1,10 @@ open Forester_core -val run : (unit -> 'a) -> 'a -(** Initialises a cache for tree mainmatters in the given scope. *) +val route : root:string option -> addr -> string -val pp : ?stylesheet:string -> Format.formatter -> Xml_tree.tree_ -> unit -(** Must be called within the scope of {!run}. *) \ No newline at end of file +module type I = sig val root : string option end + +module Make (_ : I) () : +sig + val pp : ?stylesheet:string -> Format.formatter -> Xml_tree.tree_ -> unit +end \ No newline at end of file diff --git a/lib/render/Xml_forester.ml b/lib/render/Xml_forester.ml index 2d79e95..be5e3c0 100644 --- a/lib/render/Xml_forester.ml +++ b/lib/render/Xml_forester.ml @@ -43,11 +43,9 @@ let root = bool_attr "root" let frontmatter = f_std_tag "frontmatter" let mainmatter = f_std_tag "mainmatter" let backmatter = f_std_tag "backmatter" -let contributions = f_std_tag "contributions" -let context = f_std_tag "context" -let related = f_std_tag "related" -let backlinks = f_std_tag "backlinks" -let references = f_std_tag "references" + +let query_tree = f_std_tag "query-tree" + let anchor attrs = f_text_tag "anchor" attrs let taxon attrs = f_text_tag "taxon" attrs let addr attrs = f_text_tag "addr" attrs diff --git a/lib/render/Xml_forester.mli b/lib/render/Xml_forester.mli index fdc63a9..831dd85 100644 --- a/lib/render/Xml_forester.mli +++ b/lib/render/Xml_forester.mli @@ -25,11 +25,6 @@ val info : std_tag val frontmatter : std_tag val mainmatter : std_tag val backmatter : std_tag -val contributions : std_tag -val context : std_tag -val related : std_tag -val backlinks : std_tag -val references : std_tag val anchor : _ text_tag val taxon : _ text_tag -- 2.43.4