diff --git a/lib/compiler/Expand.ml b/lib/compiler/Expand.ml index fa372c0..e0cb279 100644 --- a/lib/compiler/Expand.ml +++ b/lib/compiler/Expand.ml @@ -302,13 +302,13 @@ let builtin_xml_namespaces = |> Seq.map @@ fun (prefix, xmlns) -> (["xmlns"; prefix], (Syn.Xmlns {prefix; xmlns}, None)) -let builtins = +let math_builtins = Seq.append Tex_builtins.words Tex_builtins.symbols + +let prose_builtins = Seq.concat @@ List.to_seq [ builtin_xml_namespaces; - Tex_builtins.words; - Tex_builtins.symbols; begin let open Builtins.Transclude in List.to_seq @@ -382,6 +382,8 @@ let builtins = end; ] +let builtins = Seq.append math_builtins prose_builtins + let initial_visible_trie : (Syn.resolver_data, Range.t option) Trie.t = Yuujinchou.Trie.of_seq builtins diff --git a/lib/compiler/Expand.mli b/lib/compiler/Expand.mli index 50db06f..eeeb146 100644 --- a/lib/compiler/Expand.mli +++ b/lib/compiler/Expand.mli @@ -9,6 +9,9 @@ module Unit_map = URI.Map val initial_visible_trie : (Syn.resolver_data, Range.t option) Trie.t +val math_builtins : (Trie.path * (Syn.resolver_data * Range.t option)) Seq.t +val prose_builtins : (Trie.path * (Syn.resolver_data * Range.t option)) Seq.t + module Builtins : sig module Transclude : sig val expanded_sym : Symbol.t diff --git a/lib/language_server/Analysis.ml b/lib/language_server/Analysis.ml index 75aac69..9c63847 100644 --- a/lib/language_server/Analysis.ml +++ b/lib/language_server/Analysis.ml @@ -8,6 +8,8 @@ open Forester_compiler open Forester_core +(* I think this module is mostly junk? *) + module Item = struct type t = Path of Trie.path | Addr of string @@ -108,97 +110,12 @@ let rec node_at : type a. | None -> Some n end -let get_enclosing_code_group ~position tree = - let rec go ~position nodes = - match - List.find_opt (fun Range.{range; _} -> contains ~position range) nodes - with - | None -> None - | Some ({value; range} as node) -> ( - match value with - | Code.Group (delim, t) -> begin - match go ~position t with - | None -> Some Range.{value = (delim, t); range} - | Some t -> Some t - end - | _ -> (go ~position) (Code.children node)) - in - match Tree.code tree with - | None -> None - | Some code -> go ~position @@ Tree.Parsed.nodes code - -let get_enclosing_syn_group ~position tree = - let rec go ~position nodes = - match - List.find_opt (fun Range.{range; _} -> contains ~position range) nodes - with - | None -> None - | Some ({value; range} as node) -> ( - match value with - | Syn.Group (delim, children) -> begin - match go ~position children with - | None -> Some Range.{value = (delim, children); range} - | Some t -> Some t - end - | _ -> go ~position (Syn.children node)) - in - match Tree.syn tree with - | None -> None - | Some syn -> go ~position syn.tree.nodes - -let enclosing_group_start ~position - ~(enclosing_group : - position:L.Position.t -> Tree.t -> (delim * 'a) Range.located option) - (tree : Tree.t) = - match enclosing_group ~position tree with - | None -> position - | Some {range; value = _} -> Lsp_shims.lsp_pos_of_range range - -let find_with_prev ~position = - let rec go prev = function - | [] -> None - | x :: xs -> - if contains ~position Range.(x.range) then Some (prev, x) - else go (Some x) xs - in - go None - -module Context = struct - (* Kind of like a zipper where you can only go backwards? *) - type 'a t = Prev of 'a * 'a | Parent of 'a | Top of 'a option -end - -let parent_or_prev_at : type a. - position:L.Position.t -> - children:(a Range.located -> a Range.located list) -> - a Range.located list -> - a Range.located Context.t option = - fun ~position ~children code -> - let rec go ~top nodes = - match find_with_prev ~position nodes with - | None -> if top then Some (Context.Top None) else None - | Some (prev, node) -> ( - match go ~top:false (children node) with - | Some inner -> Some inner - | None -> ( - match prev with - | Some prev -> Some (Context.Prev (prev, node)) - | None -> - Some (if top then Context.Top (Some node) else Context.Parent node))) - in - go ~top:true code - -let parent_or_prev_at_code ~position = - parent_or_prev_at ~position ~children:Code.children - -let parent_or_prev_at_syn ~position = - parent_or_prev_at ~position ~children:Syn.children - let node_at_code ~position = node_at ~position ~children:Code.children let node_at_syn ~position = node_at ~position ~children:Syn.children -let get_visible ~forest ~position code = - Sc.run ~init_visible:Expand.initial_visible_trie @@ fun () -> +let get_visible ?(init_visible = Expand.initial_visible_trie) ~forest ~position + code = + Sc.run ~init_visible @@ fun () -> let open Effect.Deep in match_with (Expand.expand_eff ~forest) @@ -241,17 +158,3 @@ let word_at ~position (doc : Lsp.Text_document.t) = words; None with Found str -> Some str) - -let word_before ~position (doc : Lsp.Text_document.t) = - let L.Position.{line; character} = position in - let line = - List.nth_opt (String.split_on_char '\n' (Lsp.Text_document.text doc)) line - in - match line with - | None -> None - | Some line -> ( - try - let until_cursor = String.sub line 0 character in - let words = List.rev @@ String.split_on_char ' ' until_cursor in - Some (List.hd words) - with _ -> None) diff --git a/lib/language_server/Completion.ml b/lib/language_server/Completion.ml index 2668b50..25a4c4e 100644 --- a/lib/language_server/Completion.ml +++ b/lib/language_server/Completion.ml @@ -16,170 +16,6 @@ open struct let ( let* ) = Option.bind end -type completion = Addrs | New_addr | Assets | Visible | Date [@@deriving show] - -module S = Set.Make (struct - type t = completion - - let compare = compare -end) - -type completion_kind = { - text: string -> completion option; - code: Code.node Range.located Analysis.Context.t -> completion option; - syn: Syn.node Range.located Analysis.Context.t -> completion option; -} - -let subtree_completion : completion_kind = - let text word_before = - if Str.(string_match (regexp {|.*subtree.*|}) word_before 0) then - Some New_addr - else None - in - let code (context : _ Analysis.Context.t) = - match context with - | Prev (Range.{value = Code.Subtree (_, _); _}, _) - | Prev (_, Range.{value = Code.Subtree (_, _); _}) - | Parent {value = Code.Subtree (_, _); _} -> - Some New_addr - | Parent _ | Prev (_, _) | Top _ -> None - in - let syn (context : Syn.node Range.located Analysis.Context.t) = - match context with - | Top (Some {value = Subtree _; _}) -> Some New_addr - | Prev (_, {value = Subtree _; _}) -> Some New_addr - | _ -> None - in - {text; code; syn} - -let asset_completion : completion_kind = - let text word_before = - if Str.(string_match (regexp {|.*route-asset.*|}) word_before 0) then - Some Assets - else None - in - let code (context : _ Analysis.Context.t) = - match context with - | Prev (Range.{value = Code.Ident ["route-asset"]; _}, _) - | Prev (_, Range.{value = Code.Ident ["route-asset"]; _}) -> - Some Assets - | Prev _ | Parent _ | Top _ -> None - in - let syn (context : Syn.node Range.located Analysis.Context.t) = - match context with - | Top (Some {value = Route_asset; _}) -> Some Assets - | Prev (_, {value = Route_asset; _}) -> Some Assets - | _ -> None - in - {text; code; syn} - -let date_completion : completion_kind = - let text word_before = - if Str.(string_match (regexp {|.*date.*|}) word_before 0) then Some Date - else None - in - let code (context : _ Analysis.Context.t) = - match context with - | Prev (Range.{value = Code.Ident ["date"]; _}, _) - | Prev (_, Range.{value = Code.Ident ["date"]; _}) -> - Some Date - | Prev _ | Parent _ | Top _ -> None - in - let syn (_context : Syn.node Range.located Analysis.Context.t) = None in - {text; code; syn} - -let uri_completion : completion_kind = - let text word_before = - if - Str.(string_match (regexp {|.*]($|}) word_before 0) - || Str.(string_match (regexp {|.*\[\[?$|}) word_before 0) - || Str.(string_match (regexp {|.*transclude.*|}) word_before 0) - then Some Addrs - else None - in - let code (_context : _ Analysis.Context.t) = None in - let syn (_context : Syn.node Range.located Analysis.Context.t) = None in - {text; code; syn} - -let new_uri_completion : completion_kind = - let text context = - if Str.(string_match (regexp {|.*subtree\[.*|}) context 0) then - Some New_addr - else None - in - let code context = - Option.map (Fun.const New_addr) (uri_completion.code context) - in - let syn context = - Option.map (Fun.const New_addr) (uri_completion.syn context) - in - {text; code; syn} - -let visible_completion : completion_kind = - let text _ = None in - let code (context : _ Analysis.Context.t) = - match context with Top _ | Parent _ -> Some Visible | Prev (_, _) -> None - in - let syn (context : Syn.node Range.located Analysis.Context.t) = - match context with Top _ | Parent _ -> Some Visible | Prev (_, _) -> None - in - {text; code; syn} - -let completion_types (t : Tree.t) ~position = - let completions : completion_kind list = - [ - uri_completion; - asset_completion; - subtree_completion; - new_uri_completion; - date_completion; - visible_completion; - ] - in - let code_opt = Tree.code t in - let syn_opt = Tree.syn t in - let doc_opt = Tree.document t in - let text_context = Option.bind doc_opt @@ Analysis.word_before ~position in - Logs.debug ~src:Log.Src.lsp (fun m -> - m "text context before cursor: %a" - Format.(pp_print_option pp_print_string) - text_context); - let code_context = - let enclosing_group = Analysis.get_enclosing_code_group in - let position = - Analysis.enclosing_group_start ~enclosing_group ~position t - in - let@ code = Option.bind code_opt in - Analysis.parent_or_prev_at_code ~position @@ Tree.Parsed.nodes code - in - let syn_context = - let enclosing_group = Analysis.get_enclosing_syn_group in - let position = - Analysis.enclosing_group_start ~enclosing_group ~position t - in - let@ syn = Option.bind syn_opt in - Analysis.parent_or_prev_at_syn ~position syn.tree.nodes - in - completions - |> List.fold_left - begin fun acc completion_kind -> - (* let module Kind = (val completion_kind : CompletionKind) in *) - let compl = - List.fold_left - begin fun acc a -> - match a with None -> acc | Some compl -> S.add compl acc - end - S.empty - [ - Option.bind text_context completion_kind.text; - Option.bind code_context completion_kind.code; - Option.bind syn_context completion_kind.syn; - ] - in - S.union compl acc - end - S.empty - let kind : Syn.node -> L.CompletionItemKind.t option = function | Fun (_, _) -> Some Function | Text _ | Verbatim _ -> Some Text @@ -251,6 +87,8 @@ let make ()) | Xmlns _ -> None +let builtin_completions builtins = List.of_seq builtins |> List.filter_map make + (* These are useful completion items that are handled during parsing, not during expansion and are thus not "builtins"*) @@ -347,18 +185,92 @@ let new_addr_completions ~(forest : State.t) : L.CompletionItem.t list = (); ] -let visible_completions ~(forest : State.t) ~(position : L.Position.t) : - Tree.parsed -> L.CompletionItem.t list = - fun nodes -> - Analysis.get_visible ~position ~forest nodes +let math_visible_trie = Trie.of_seq Expand.math_builtins +let prose_visible_trie = Trie.of_seq Expand.prose_builtins + +let visible_completions ~(forest : State.t) ~(position : L.Position.t) + (nodes : Tree.parsed) : L.CompletionItem.t list = + Analysis.get_visible ~init_visible:prose_visible_trie ~position ~forest nodes |> Trie.to_seq |> List.of_seq |> List.filter_map make |> List.append syntax_completions +let math_completions ~(forest : State.t) ~(position : L.Position.t) + (nodes : Tree.parsed) : L.CompletionItem.t list = + Analysis.get_visible ~init_visible:math_visible_trie ~position ~forest nodes + |> Trie.to_seq |> List.of_seq |> List.filter_map make + let date_completions () : L.CompletionItem.t list = let now = Human_datetime.now () in let now_string = Format.asprintf "%a" Human_datetime.pp now in [L.CompletionItem.create ~label:now_string ~insertText:now_string ()] +type rule = { + applies: Completion_context.t -> bool; + items: + forest:State.t -> + position:L.Position.t -> + code:Tree.parsed option -> + L.CompletionItem.t list; +} + +let subtree_address_rule : rule = + { + applies = (fun ctx -> Completion_context.purpose ctx = Subtree_address); + items = (fun ~forest ~position:_ ~code:_ -> new_addr_completions ~forest); + } + +let reference_address_rule : rule = + { + applies = (fun ctx -> Completion_context.purpose ctx = Reference_address); + items = + (fun ~forest ~position:_ ~code:_ -> + addr_completions () @ new_addr_completions ~forest); + } + +let asset_rule : rule = + { + applies = (fun ctx -> Completion_context.purpose ctx = Asset); + items = + (fun ~forest ~position:_ ~code:_ -> + asset_completions ~config:forest.config); + } + +let date_rule : rule = + { + applies = (fun ctx -> Completion_context.purpose ctx = Date); + items = (fun ~forest:_ ~position:_ ~code:_ -> date_completions ()); + } + +let math_rule : rule = + { + applies = Completion_context.in_math; + items = + (fun ~forest ~position ~code -> + match code with + | Some nodes -> math_completions ~forest ~position nodes + | None -> builtin_completions Expand.math_builtins); + } + +let visible_rule : rule = + { + applies = (fun ctx -> Completion_context.purpose ctx = Body); + items = + (fun ~forest ~position ~code -> + match code with + | Some nodes -> visible_completions ~forest ~position nodes + | None -> builtin_completions Expand.prose_builtins @ syntax_completions); + } + +let rules = + [ + subtree_address_rule; + reference_address_rule; + asset_rule; + date_rule; + math_rule; + visible_rule; + ] + let compute ({context; position; textDocument = {uri}; _} : L.CompletionParams.t) : _ = Logs.debug ~src:Log.Src.lsp (fun m -> @@ -369,18 +281,14 @@ let compute | None -> None in let forest, uri = Util.forest_and_uri uri in - let config = forest.config in let* tree = forest.={uri} in - let* code = Tree.code tree in - let completion_types = completion_types ~position tree in + let* document = Tree.document tree in + let ctx = Completion_context.classify ~document ~position in + let code = Option.map Tree.Parsed.nodes (Tree.code tree) in let items = - let@ completion = List.concat_map @~ S.to_list completion_types in - match completion with - | Addrs -> addr_completions () - | New_addr -> new_addr_completions ~forest - | Assets -> asset_completions ~config - | Visible -> visible_completions ~forest ~position @@ Tree.Parsed.nodes code - | Date -> date_completions () + rules + |> List.filter (fun rule -> rule.applies ctx) + |> List.concat_map (fun rule -> rule.items ~forest ~position ~code) in Logs.debug ~src:Log.Src.lsp (fun m -> m "found %d completion items" (List.length items)); diff --git a/lib/language_server/Completion_context.ml b/lib/language_server/Completion_context.ml new file mode 100644 index 0000000..90b5a83 --- /dev/null +++ b/lib/language_server/Completion_context.ml @@ -0,0 +1,160 @@ +(* + * SPDX-FileCopyrightText: 2024 The Forester Project Contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + *) + +open Forester_core +open Forester_parser + +open struct + module L = Lsp.Types +end + +type delim = Brace | Square | Paren | Math_inline | Math_display + +type purpose = + | Body + | Subtree_address + | Reference_address + | Asset + | Date + | Math +[@@deriving show] + +type frame = {delim: delim; purpose: purpose} +type level = {frame: frame option; mutable last_closed: purpose option} +type t = {enclosing: frame list} + +let purpose (t : t) : purpose = + match t.enclosing with f :: _ -> f.purpose | [] -> Body + +let in_math (t : t) : bool = purpose t = Math + +let token_head : Tokens.token -> string option = function + | IDENT s -> Some s + | SUBTREE -> Some "subtree" + | SCOPE -> Some "scope" + | PUT -> Some "put" + | DEFAULT -> Some "put?" + | GET -> Some "get" + | IMPORT -> Some "import" + | EXPORT -> Some "export" + | NAMESPACE -> Some "namespace" + | OPEN -> Some "open" + | DEF -> Some "def" + | ALLOC -> Some "alloc" + | LET -> Some "let" + | FUN -> Some "fun" + | OBJECT -> Some "object" + | PATCH -> Some "patch" + | CALL -> Some "call" + | DATALOG -> Some "datalog" + | XML_ELT_IDENT (_, name) -> Some name + | DECL_XMLNS name -> Some ("xmlns:" ^ name) + | HASH_IDENT _ | TEXT _ | VERBATIM _ | COMMENT _ | WHITESPACE _ | SLASH + | LBRACE | RBRACE | LSQUARE | RSQUARE | LPAREN | RPAREN | HASH_LBRACE + | HASH_HASH_LBRACE | TICK | AT_SIGN | HASH | EOF | DX_ENTAILED | DX_VAR _ -> + None + +let purpose_for ~pending_head ~enclosing_purpose ~prev_sibling_purpose + ~(delim : delim) : purpose = + match delim with + | Math_inline | Math_display -> Math + | Brace -> begin + match pending_head with + | Some "transclude" -> Reference_address + | Some "route-asset" -> Asset + | Some "date" -> Date + | _ -> enclosing_purpose + end + | Square -> begin + match pending_head with + | Some "subtree" -> Subtree_address + | Some "route-asset" -> Asset + | Some "date" -> Date + | None when enclosing_purpose = Body -> Reference_address + | _ -> enclosing_purpose + end + | Paren -> begin + match pending_head with + | Some "route-asset" -> Asset + | Some "date" -> Date + | None + when enclosing_purpose = Body + && prev_sibling_purpose = Some Reference_address -> + Reference_address + | _ -> enclosing_purpose + end + +let dispatch lexbuf = + match Stack.top Lexer.mode_stack with + | Lexer.Main -> Lexer.token lexbuf + | Ident_init -> Lexer.ident_init lexbuf + | Ident_fragments -> Lexer.ident_fragments lexbuf + | Verbatim (herald, buffer) -> Lexer.verbatim herald buffer lexbuf + +let reset_lexer_state () = + Stack.clear Lexer.mode_stack; + Stack.push Lexer.Main Lexer.mode_stack + +let classify ~(document : Lsp.Text_document.t) ~(position : L.Position.t) : t = + let text = Lsp.Text_document.text document in + let source : Grace.Source.t = `String {name = None; content = text} in + let cursor = + (Analysis.byte_index_of_position ~source:(Range.total ~source) position + :> int) + in + let levels = ref [{frame = None; last_closed = None}] in + let pending_head = ref None in + let current_purpose () = + match !levels with + | {frame = Some f; _} :: _ -> f.purpose + | {frame = None; _} :: _ | [] -> Body + in + let prev_sibling_purpose () = + match !levels with {last_closed; _} :: _ -> last_closed | [] -> None + in + let push delim = + let p = + purpose_for ~pending_head:!pending_head + ~enclosing_purpose:(current_purpose ()) + ~prev_sibling_purpose:(prev_sibling_purpose ()) ~delim + in + levels := {frame = Some {delim; purpose = p}; last_closed = None} :: !levels; + pending_head := None + in + let pop () = + match !levels with + | closed :: (parent :: _ as rest) -> + Option.iter (fun f -> parent.last_closed <- Some f.purpose) closed.frame; + levels := rest + | _ -> () + in + let handle_token (tok : Tokens.token) = + match tok with + | LBRACE -> push Brace + | LSQUARE -> push Square + | LPAREN -> push Paren + | HASH_LBRACE -> push Math_inline + | HASH_HASH_LBRACE -> push Math_display + | RBRACE | RSQUARE | RPAREN -> + pop (); + pending_head := None + | tok -> pending_head := token_head tok + in + reset_lexer_state (); + let lexbuf = Lexing.from_string text in + (try + let continue_ = ref true in + while !continue_ do + let toks = dispatch lexbuf in + if Lexing.lexeme_end lexbuf > cursor then continue_ := false + else begin + List.iter handle_token toks; + if List.mem Tokens.EOF toks then continue_ := false + end + done + with _ -> ()); + reset_lexer_state (); + {enclosing = List.filter_map (fun {frame; _} -> frame) !levels} diff --git a/lib/language_server/dune b/lib/language_server/dune index e6ce8f3..bd833e3 100644 --- a/lib/language_server/dune +++ b/lib/language_server/dune @@ -20,6 +20,7 @@ forester.search forester.prelude forester.core + forester.parser forester.compiler forester.frontend forester.server diff --git a/test/Testables.ml b/test/Testables.ml index 1569532..d50e6ce 100644 --- a/test/Testables.ml +++ b/test/Testables.ml @@ -35,4 +35,3 @@ let document = let tree = testable Code.pp_tree ( = ) let content = testable Types.pp_content ( = ) let action = testable Action.pp ( = ) -let completion_type = testable Forester_lsp.Completion.pp_completion ( = ) diff --git a/test/lsp.t b/test/lsp.t index 5f9f383..d01159b 100644 --- a/test/lsp.t +++ b/test/lsp.t @@ -4,8 +4,6 @@ > \title{hello > EOF -The completion behavior should probably be refined to only return math stuff when the cursor is in math mode... - $ nvim --headless --clean -l lsp_driver.lua { column = 0, @@ -13,4 +11,4 @@ The completion behavior should probably be refined to only return math stuff whe message = "parse error", severity = 1 } - { "⋃", ",", ";", "Alpha", "Beta", "Big", "Bigg", "Biggl", "Biggr", "Bigl", "Bigr", "Box", "Bumpeq", "Cap", "Colonapprox", "Coloneq", "Coloneqq", "Colonsim", "Cup", "Del", "Delta", "Diamond", "Downarrow", "Eqcolon", "Eqqcolon", "Eta", "Gamma", "Im", "Intersection", "Iota", "Kappa", "Lambda", "Leftarrow", "Leftrightarrow", "Lleftarrow", "Longleftrightarrow", "Lsh", "Mu", "Nu", "Omega", "Oplus", "Otimes", "Perp", "Phi", "Pi", "Pr", "Psi", "Re", "Rho", "Rightarrow", "Rrightarrow", "Rsh", "Sigma", "Subset", "Supset", "Tau", "Theta", "Union", "Uparrow", "Updownarrow", "Upsi", "Upsilon", "VDash", "Vbar", "Vdash", "Vee", "Vert", "Vvdash", "Wedge", "Xi", "Zeta", "_", "aleph", "align", "alloc", "alpha", "amalg", "angle", "approx", "approxeq", "arccos", "arcsin", "arctan", "arg", "array", "arrayopts", "ast", "asymp", "atop", "author", "author/literal", "backepsilon", "backprime", "backsim", "backsimeq", "backslash", "bar", "barwedge", "because", "begin", "begintoggle", "beta", "beth", "between", "bgcolor", "bghighlight", "bghilight", "big", "bigcap", "bigcirc", "bigcup", "bigg", "biggl", "biggr", "biginterleave", "bigl", "bigodot", "bigoplus", "bigotimes", "bigr", "bigsqcap", "bigsqcup", "bigstar", "bigtriangledown", "bigtriangleup", "biguplus", "bigvee", "bigwedge", "binom", "blacklozenge", "blacksquare", "blacktriangle", "blacktriangledown", "blacktriangleleft", "blacktriangleright", "blockquote", "boldsymbol", "bot", "bottom", "bowtie", "boxdot", "boxed", "boxminus", "boxplus", "boxtimes", "bullet", "bumpeq", "call", "cap", "cdot", "cdots", "cellopts", "check", "chi", "circ", "circeq", "circlearrowleft", "circlearrowright", "circledast", "circledcirc", "circleddash", "closure", "clubsuit", "code", "colalign", "collayout", "collines", "colon", "colonapprox", "coloneq", "coloneqq", "colonsim", "color", "colspan", "cong", "conint", "contourintegral", "contributor", "contributor/literal", "coprod", "coproduct", "cos", "cosh", "cot", "coth", "csc", "cup", "curlyeqprec", "curlyeqsucc", "curlyvee", "curlywedge", "current-tree", "curvearrowbotright", "curvearrowleft", "curvearrowright", "dagger", "darr", "dashleftarrow", "dashrightarrow", "dashv", "datalog", "date", "dblcolon", "ddagger", "ddddot", "dddot", "ddot", "ddots", "def", "deg", "delta", "det", "diamond", "diamondsuit", "digamma", "dim", "displaystyle", "div", "divideontimes", "dlap", "dot", "doteq", "doteqdot", "dotplus", "dots", "doublebarwedge", "doubleintegral", "downarrow", "downdownarrows", "downharpoonleft", "downharpoonright", "downuparrow", "duparr", "ell", "em", "embedsin", "empty", "emptyset", "end", "endtoggle", "epsilon", "eqcirc", "eqcolon", "eqqcolon", "eqslantgtr", "eqslantless", "equalcols", "equalrows", "equiv", "eta", "eth", "execute", "exists", "exp", "export", "fallingdotseq", "fghighlight", "fghilight", "figcaption", "figure", "flat", "forall", "frac", "frame", "frown", "fun", "gamma", "gcd", "ge", "geq", "geqq", "geqslant", "get", "gg", "ggg", "gnapprox", "gneq", "gneqq", "gnsim", "gt", "gtrapprox", "gtrdot", "gtreqless", "gtreqqless", "gtrless", "gtrsim", "gvertneqq", "hat", "hbar", "heartsuit", "hom", "hookleftarrow", "hookrightarrow", "iff", "iiiint", "iiint", "iint", "imath", "impliedby", "implies", "import", "in", "includegraphics", "inf", "infinity", "infty", "int", "integral", "intercal", "intersection", "invamp", "iota", "jmath", "kappa", "ker", "lVert", "lambda", "land", "lang", "langle", "lbrace", "lceil", "ldots", "le", "left", "leftarrow", "leftarrowtail", "leftharpoondown", "leftharpoonup", "leftleftarrows", "leftrightarrow", "leftrightarrows", "leftrightharpoons", "leftrightsquigarrow", "leftsquigarrow", "leftthreetimes", "lefttorightarrow", "leq", "leqq", "leqslant", "lessapprox", "lessdot", "lesseqgtr", "lesseqqgtr", "lessgtr", "lesssim", "let", "lfloor", "lg", "lhd", "li", "lim", "liminf", "limsup", "ll", "llangle", "llap", "llbracket", "lll", "lmoustache", "ln", "lnapprox", "lneq", "lneqq", "lnsim", "log", "longleftarrow", "longrightarrow", "looparrowleft", "looparrowright", "lor", "lozenge", "lparen", "lt", "ltimes", "lvert", "lvertneqq", "map", "mapsto", "mathbb", "mathbf", "mathbin", "mathcal", "mathclap", "mathfr", "mathfrak", "mathit", "mathllap", "mathop", "mathord", "mathpunct", "mathraisebox", "mathrel", "mathrlap", "mathrm", "mathscr", "mathsf", "mathtt", "max", "measuredangle", "medspace", "meta", "mho", "mid", "middle", "min", "minusb", "mod", "models", "mp", "mu", "multimap", "multiscripts", "nLeftarrow", "nLeftrightarrow", "nRightarrow", "nVDash", "nabla", "namespace", "natural", "ncong", "ne", "neArr", "neArrow", "nearr", "nearrow", "neg", "negmedspace", "negspace", "negthickspace", "negthinspace", "neq", "nequiv", "nexists", "ngeq", "ngeqq", "ngeqslant", "ngtr", "ni", "nleftarrow", "nleftrightarrow", "nleq", "nleqq", "nleqslant", "nless", "nmid", "not", "notin", "notni", "nparallel", "nprec", "npreceq", "nrightarrow", "nshortmid", "nshortparallel", "nsim", "nsubset", "nsubseteq", "nsubseteqq", "nsucc", "nsucceq", "nsupset", "nsupseteq", "ntriangleleft", "ntrianglelefteq", "ntriangleright", "ntrianglerighteq", "nu", "number", "nvDash", "nvdash", "nwArr", "nwArrow", "nwarr", "nwarrow", "object", "odash", "odot", "oint", "ol", "omega", "omicron", "ominus", "open", "operatorname", "oplus", "oslash", "otimes", "over", "overbrace", "overleftarrow", "overleftrightarrow", "overline", "overrightarrow", "overset", "p", "padding", "parallel", "parent", "parr", "partial", "patch", "perp", "phantom", "phi", "pi", "pitchfork", "plusb", "pm", "pmod", "pre", "prec", "precapprox", "preccurlyeq", "preceq", "precnapprox", "precnsim", "precsim", "prime", "prod", "product", "propto", "psi", "put", "put?", "qed", "qquad", "quad", "quadrupleintegral", "query", "rVert", "rang", "rangle", "rbrace", "rceil", "ref", "rel/has-author", "rel/has-direct-contributor", "rel/has-tag", "rel/has-taxon", "rel/in-host", "rel/is-article", "rel/is-asset", "rel/is-node", "rel/is-person", "rel/is-reference", "rel/links-to", "rel/transcludes", "rel/transcludes/reflexive-transitive-closure", "rel/transcludes/transitive-closure", "rfloor", "rhd", "rho", "right", "rightarrow", "rightarrowtail", "rightharpoondown", "rightharpoonup", "rightleftarrows", "rightleftharpoons", "rightrightarrows", "rightsquigarrow", "rightthreetimes", "righttoleftarrow", "risingdotseq", "rlap", "rmoustache", "root", "route-asset", "rowalign", "rowlines", "rowopts", "rowspan", "rparen", "rrangle", "rrbracket", "rtimes", "rvert", "scope", "scriptscriptsize", "scriptsize", "seArr", "seArrow", "searr", "searrow", "sec", "setminus", "sharp", "shortmid", "shortparallel", "sigma", "sim", "simeq", "sin", "sinh", "slash", "smallfrown", "smallsetminus", "smallsmile", "smile", "space", "spadesuit", "sphericalangle", "sqcap", "sqcup", "sqrt", "sqsubset", "sqsubseteq", "sqsupset", "sqsupseteq", "square", "sslash", "stackrel", "star", "startverb", "statusline", "strong", "subset", "subseteq", "subseteqq", "subsetneq", "subsetneqq", "substack", "subtree", "succ", "succapprox", "succcurlyeq", "succeq", "succnapprox", "succnsim", "succsim", "sum", "sup", "supset", "supseteq", "supseteqq", "supsetneq", "supsetneqq", "swArr", "swArrow", "swarr", "swarrow", "syndicate-current-tree-as-atom-feed", "syndicate-query-as-json-blob", "tag", "tan", "tanh", "tau", "taxon", "tbinom", "tensor", "tex", "text", "textsize", "textstyle", "tfrac", "therefore", "theta", "thickapprox", "thicksim", "thickspace", "thinspace", "tilde", "times", "timesb", "title", "to", "toggle", "tooltip", "top", "transclude", "transclude/expanded", "transclude/heading", "transclude/metadata", "transclude/numbered", "transclude/toc", "triangle", "triangledown", "triangleleft", "trianglelefteq", "triangleq", "triangleright", "trianglerighteq", "tripleintegral", "twoheadleftarrow", "twoheadrightarrow", "udots", "ul", "ulap", "underbrace", "underleftarrow", "underleftrightarrow", "underline", "underoverset", "underrightarrow", "underset", "union", "unlhd", "unrhd", "uparr", "uparrow", "updarr", "updownarrow", "upharpoonleft", "upharpoonright", "uplus", "upsilon", "upuparrows", "vDash", "varepsilon", "varkappa", "varnothing", "varphi", "varpi", "varpropto", "varrho", "varsigma", "varsubsetneq", "varsubsetneqq", "varsupsetneq", "varsupsetneqq", "vartheta", "vartriangleleft", "vartriangleright", "vdash", "vdots", "vec", "vee", "veebar", "vert", "wedge", "widebar", "widecheck", "widehat", "widetilde", "widevec", "wp", "wr", "xLeftarrow", "xLeftrightarrow", "xRightarrow", "xhookleftarrow", "xhookrightarrow", "xi", "xleftarrow", "xleftrightarrow", "xleftrightharpoons", "xmapsto", "xmlns", "xrightarrow", "xrightleftharpoons", "zeta" } + { "alloc", "author", "author/literal", "blockquote", "call", "code", "contributor", "contributor/literal", "current-tree", "datalog", "date", "def", "em", "execute", "export", "figcaption", "figure", "fun", "get", "import", "let", "li", "meta", "namespace", "number", "object", "ol", "open", "p", "parent", "patch", "pre", "put", "put?", "query", "ref", "rel/has-author", "rel/has-direct-contributor", "rel/has-tag", "rel/has-taxon", "rel/in-host", "rel/is-article", "rel/is-asset", "rel/is-node", "rel/is-person", "rel/is-reference", "rel/links-to", "rel/transcludes", "rel/transcludes/reflexive-transitive-closure", "rel/transcludes/transitive-closure", "route-asset", "scope", "startverb", "strong", "subtree", "syndicate-current-tree-as-atom-feed", "syndicate-query-as-json-blob", "tag", "taxon", "tex", "title", "transclude", "transclude/expanded", "transclude/heading", "transclude/metadata", "transclude/numbered", "transclude/toc", "ul", "xmlns" }