latexmath 0.1.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/LICENSE.md +33 -0
  4. data/README.adoc +133 -14
  5. data/artifacts/latex.bindings.python.json +1 -0
  6. data/artifacts/latex.bindings.rs.json +1 -0
  7. data/artifacts/latex.bindings.ruby.json +1 -0
  8. data/artifacts/latex.bindings.ts.json +1 -0
  9. data/artifacts/latex.checksums.txt +19 -0
  10. data/artifacts/latex.json +1 -0
  11. data/data/latex-symbols.json +3301 -0
  12. data/docs/model-contract.adoc +125 -0
  13. data/docs/parser-contract.adoc +349 -0
  14. data/docs/testsuite-pin.adoc +76 -0
  15. data/grammars/latex.wrappers.json +17 -0
  16. data/lib/latexmath/model/nodes.rb +918 -0
  17. data/lib/latexmath/model.rb +44 -0
  18. data/lib/latexmath/parser/artifact.rb +80 -0
  19. data/lib/latexmath/parser/configuration.rb +35 -0
  20. data/lib/latexmath/parser/engine.rb +161 -0
  21. data/lib/latexmath/parser/errors.rb +51 -0
  22. data/lib/latexmath/parser/native_recursion_guard.rb +43 -0
  23. data/lib/latexmath/parser/preprocessor.rb +77 -0
  24. data/lib/latexmath/parser/raw_parser.rb +84 -0
  25. data/lib/latexmath/parser/source_map.rb +121 -0
  26. data/lib/latexmath/parser/unwrap.rb +91 -0
  27. data/lib/latexmath/parser.rb +97 -0
  28. data/lib/latexmath/symbols.rb +74 -0
  29. data/lib/latexmath/version.rb +1 -1
  30. data/lib/latexmath.rb +5 -87
  31. data/model/latex.lml.checksums.txt +36 -0
  32. data/model/latex.lml.json +3350 -0
  33. metadata +60 -71
  34. data/.editorconfig +0 -14
  35. data/.github/workflows/test.yml +0 -80
  36. data/.gitignore +0 -22
  37. data/.rspec +0 -3
  38. data/.rubocop.yml +0 -129
  39. data/CODE_OF_CONDUCT.md +0 -74
  40. data/Gemfile +0 -10
  41. data/Gemfile.lock +0 -79
  42. data/Rakefile +0 -95
  43. data/bin/console +0 -14
  44. data/bin/setup +0 -8
  45. data/exe/latexmath +0 -8
  46. data/latexmath.gemspec +0 -34
  47. data/lib/latexmath/aggregator.rb +0 -351
  48. data/lib/latexmath/converter.rb +0 -424
  49. data/lib/latexmath/equation.rb +0 -13
  50. data/lib/latexmath/ext.rb +0 -9
  51. data/lib/latexmath/symbol.rb +0 -21
  52. data/lib/latexmath/tokenizer.rb +0 -82
  53. data/lib/latexmath/xml/builder.rb +0 -4
  54. data/lib/latexmath/xml/element.rb +0 -25
  55. data/lib/unimathsymbols.js.erb +0 -4
  56. data/lib/unimathsymbols.txt +0 -2864
  57. data/opal/latexmath-opal.rb +0 -40
  58. data/opal/ox.rb +0 -64
  59. data/opal/pseudoenumerator.rb +0 -19
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "htmlentities"
4
+
5
+ module Latexmath
6
+ # Maps positions in the preprocessed text back to the caller's text.
7
+ #
8
+ # It runs the steps of Preprocessor.call again, each as a scan that
9
+ # records, for every character it writes, the caller character it came
10
+ # from: a character that passes through keeps its origin; the characters
11
+ # of a replacement (a decoded or encoded entity, "\\ " for an encoded
12
+ # newline, a restored \text group) start at the origin of the text they
13
+ # replace, and a restored group's characters map one to one onto the
14
+ # caller's group. Removed text leaves no characters behind, so a position
15
+ # after it maps to the caller character that follows it.
16
+ #
17
+ # Only used to report refusals; the grammar input itself always comes
18
+ # from Preprocessor.call. ParseError positions are mapped only when this
19
+ # scan writes exactly the bytes Preprocessor.call wrote.
20
+ #
21
+ # Internal.
22
+ module SourceMap
23
+ FLAVOR = "xhtml1" # HTMLEntities.new's default, as Preprocessor::ENTITIES
24
+ DECODE = HTMLEntities::Decoder.new(FLAVOR).instance_variable_get(:@entity_regexp)
25
+ NAMES = HTMLEntities::MAPPINGS.fetch(FLAVOR)
26
+ # HTMLEntities' :hexadecimal encoding: the basic characters and every
27
+ # character outside U+0020..U+007E.
28
+ ENCODE = /[<>'"&]|[^\u{20}-\u{7E}]/
29
+
30
+ # Preprocessor.gsub_space_and_unicodes, as [pattern, literal replacement].
31
+ STEPS = [
32
+ [/((?<!\\) )|\n+/, ""],
33
+ [/\\\\ /, "\\\\"],
34
+ [/&#x26;/, "&"],
35
+ [/&#x22;/, "\""],
36
+ [/(?<!\\\\)\\&#xa;/, "\\ "],
37
+ [/&#xa;/, ""],
38
+ ].freeze
39
+
40
+ module_function
41
+
42
+ # [preprocessed text, origins] for the normalized UTF-8 caller +input+:
43
+ # origins[i] is the caller character offset of preprocessed character i,
44
+ # and origins[text.length] is input.length.
45
+ def call(input)
46
+ originals = Preprocessor.text_groups(input).map { |range| [input[range], range.begin] }
47
+ text = input
48
+ map = (0..input.length).to_a
49
+ text, map = scan(text, map, DECODE) { |m| decode(m) }
50
+ text, map = scan(text, map, ENCODE) { |m| encode(m[0]) }
51
+ STEPS.each { |pattern, replacement| text, map = scan(text, map, pattern) { replacement } }
52
+ restore_groups(text, map, originals)
53
+ end
54
+
55
+ # The caller character offset of character +char+ of +text+ (the
56
+ # preprocessed text of +input+), or nil when the mapping does not
57
+ # reproduce +text+.
58
+ def caller_char(input, text, char)
59
+ mapped, map = call(input)
60
+ return nil unless mapped.b == text.b && char && char <= mapped.length
61
+
62
+ map[char]
63
+ end
64
+
65
+ def scan(text, map, pattern)
66
+ out = +""
67
+ origins = []
68
+ pos = 0
69
+ while (m = pattern.match(text, pos))
70
+ start = m.begin(0)
71
+ out << text[pos...start]
72
+ origins.concat(map[pos...start])
73
+ replacement = yield(m)
74
+ out << replacement
75
+ origins.concat([map[start]] * replacement.length)
76
+ pos = m.end(0)
77
+ end
78
+ out << text[pos..]
79
+ origins.concat(map[pos..])
80
+ [out, origins]
81
+ end
82
+
83
+ # HTMLEntities::Decoder#decode for one match.
84
+ def decode(match)
85
+ if match[1] && (codepoint = NAMES[match[1]])
86
+ codepoint.chr(Encoding::UTF_8)
87
+ elsif match[2]
88
+ match[2].to_i(10).chr(Encoding::UTF_8)
89
+ elsif match[3]
90
+ match[3].to_i(16).chr(Encoding::UTF_8)
91
+ else
92
+ match[0]
93
+ end
94
+ end
95
+
96
+ def encode(char)
97
+ "&#x#{char.codepoints.first.to_s(16)};"
98
+ end
99
+
100
+ # Preprocessor.replace_text_groups: the n-th group found in +text+ gives
101
+ # way to the n-th group of the caller's input, character for character.
102
+ def restore_groups(text, map, originals)
103
+ out = +""
104
+ origins = []
105
+ pos = 0
106
+ Preprocessor.text_groups(text).each do |range|
107
+ out << text[pos...range.begin]
108
+ origins.concat(map[pos...range.begin])
109
+ group, start = originals.shift
110
+ if group
111
+ out << group
112
+ origins.concat((start...(start + group.length)).to_a)
113
+ end
114
+ pos = range.end + 1
115
+ end
116
+ out << text[pos..]
117
+ origins.concat(map[pos..])
118
+ [out, origins]
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Latexmath
6
+ # The inverse of the grammar's shape normalization (option F), for the
7
+ # raw-tree boundary and parity replay only.
8
+ #
9
+ # The generator only ADDS captures, listed in grammars/latex.wrappers.json
10
+ # as {name => kind}. Unwrap replaces each wrapper by the value legacy
11
+ # produced there and re-runs Parslet's own flattening algebra
12
+ # (Parsanol::Atoms::CanFlatten#merge_fold / #flatten_repetition) over the
13
+ # parts, bottom-up:
14
+ #
15
+ # hash entries are split, in order, into runs of ordinary keys (one hash
16
+ # part each) and wrapper keys (one part each, by kind):
17
+ # text -> the wrapped value (nil / [] -> "", legacy unnamed)
18
+ # empty -> "" (String)
19
+ # list -> the list ([] -> "")
20
+ # nil -> nil
21
+ # text_list -> the text; "" (or []) -> [] (legacy named, empty)
22
+ # items -> the list, unchanged
23
+ # then folded with merge_fold, as legacy folded the sequence.
24
+ # A hash of nil-kind wrappers only is nil.
25
+ # array elements are unwrapped, then flatten_repetition (named).
26
+ module Unwrap
27
+ extend Parsanol::Atoms::CanFlatten
28
+
29
+ PATH = File.expand_path("../../../grammars/latex.wrappers.json", __dir__)
30
+
31
+ @wrappers = nil
32
+ @lock = Mutex.new
33
+
34
+ module_function
35
+
36
+ # The wrapper table of the bundled grammar.
37
+ def wrappers
38
+ @lock.synchronize { @wrappers ||= load(PATH).freeze }
39
+ end
40
+
41
+ def load(path)
42
+ JSON.parse(File.read(path, encoding: "UTF-8")).fetch("wrappers")
43
+ end
44
+
45
+ def call(value, wrappers = self.wrappers)
46
+ case value
47
+ when Hash then unwrap_hash(value, wrappers)
48
+ when Array then Unwrap.send(:flatten_repetition, value.map { |v| call(v, wrappers) }, true)
49
+ else value
50
+ end
51
+ end
52
+
53
+ def unwrap_hash(value, wrappers)
54
+ return value.to_h { |k, v| [k, call(v, wrappers)] } if value.keys.none? { |k| wrappers.key?(k.to_s) }
55
+
56
+ parts = []
57
+ run = nil
58
+ only_nil = true
59
+ value.each do |k, v|
60
+ kind = wrappers[k.to_s]
61
+ if kind
62
+ parts << run if run
63
+ run = nil
64
+ only_nil &&= kind == "nil"
65
+ parts << part(kind, call(v, wrappers))
66
+ else
67
+ only_nil = false
68
+ (run ||= {})[k] = call(v, wrappers)
69
+ end
70
+ end
71
+ parts << run if run
72
+ return nil if only_nil
73
+
74
+ Unwrap.send(:foldl, parts) { |l, r| Unwrap.send(:merge_fold, l, r) }
75
+ end
76
+
77
+ def part(kind, v)
78
+ case kind
79
+ when "text" then v.nil? || v == [] ? +"" : v
80
+ when "empty" then +""
81
+ when "list" then v == [] ? +"" : v
82
+ when "nil" then nil
83
+ when "text_list" then v == [] || v == "" ? [] : v
84
+ when "items" then v
85
+ else raise ArgumentError, "unknown wrapper kind #{kind}"
86
+ end
87
+ end
88
+
89
+ private_class_method :unwrap_hash, :part
90
+ end
91
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parsanol"
4
+ require_relative "version"
5
+
6
+ # The LaTeX stage parser, backed by a compiled Parsanol (PARG) grammar
7
+ # artifact generated from the pinned legacy Plurimath grammar.
8
+ #
9
+ # Latexmath.parse(input, decimal:, engine:) returns the typed LaTeX model, a
10
+ # Latexmath::Model::Document. Underneath are the grammar artifact, its
11
+ # declared syntax bindings and the internal raw-tree boundary
12
+ # (Latexmath::RawParser) that parity replay compares with the legacy
13
+ # Plurimath parser. See docs/parser-contract.adoc and
14
+ # docs/model-contract.adoc.
15
+ #
16
+ # lib/latexmath.rb requires this file.
17
+ module Latexmath
18
+ autoload :Artifact, "latexmath/parser/artifact"
19
+ autoload :Configuration, "latexmath/parser/configuration"
20
+ autoload :Engine, "latexmath/parser/engine"
21
+ autoload :Model, "latexmath/model"
22
+ autoload :Preprocessor, "latexmath/parser/preprocessor"
23
+ autoload :RawParser, "latexmath/parser/raw_parser"
24
+ autoload :SourceMap, "latexmath/parser/source_map"
25
+ autoload :Symbols, "latexmath/symbols"
26
+ autoload :Unwrap, "latexmath/parser/unwrap"
27
+
28
+ @artifacts = nil
29
+ @artifacts_lock = Mutex.new
30
+
31
+ class << self
32
+ # Parses LaTeX math +input+ (a String, valid UTF-8 or convertible to it)
33
+ # into the typed model: a Model::Document whose +expression+ is the
34
+ # formula's tree and +decimal+ the decimal marker it was parsed with
35
+ # ("." , "," or "\u066B"). Input with no formula (empty, or only
36
+ # spaces and newlines, which the preprocessing removes) gives a
37
+ # Document whose expression is nil.
38
+ #
39
+ # +engine+: :auto (native when available, else Ruby), :native or :ruby.
40
+ # Raises ParseError (offsets into +input+), NestingTooDeep,
41
+ # ArgumentError, ArtifactError or EngineUnavailable.
42
+ def parse(input, decimal: ".", engine: :auto)
43
+ text = RawParser.normalize_input(input)
44
+ RawParser.entry_for(decimal)
45
+ return Model::Document.new(decimal: decimal) if Preprocessor.call(text).empty?
46
+
47
+ raw = RawParser.parse(text, decimal: decimal, engine: engine)
48
+ build(raw, decimal, text)
49
+ end
50
+
51
+ # The verified, paired parse and Ruby bindings artifacts (internal).
52
+ def artifacts
53
+ artifact_store.fetch
54
+ end
55
+
56
+ # Points Latexmath at other artifacts (for tests). The next parse loads
57
+ # and verifies them.
58
+ def use_artifacts(path, bindings_path)
59
+ artifact_store.use(path, bindings_path)
60
+ end
61
+
62
+ def artifact_paths
63
+ [artifact_store.path, artifact_store.bindings_path]
64
+ end
65
+
66
+ # The engine (:native or :ruby) of the last parser run on this thread,
67
+ # or nil before the first one.
68
+ def last_engine
69
+ Engine.last_engine
70
+ end
71
+
72
+ def native_available?
73
+ Engine.native_available?
74
+ end
75
+
76
+ private
77
+
78
+ def build(raw, decimal, text)
79
+ Model::Document.new(expression: raw.model, decimal: decimal)
80
+ rescue SystemStackError
81
+ raise NestingTooDeep.new("LaTeX input nests too deeply to build its model (Ruby stack exhausted)",
82
+ input: text, byte_offset: nil, char_offset: nil, grammar_input: raw.text)
83
+ end
84
+
85
+ def artifact_store
86
+ @artifacts_lock.synchronize do
87
+ @artifacts ||= Artifact.new(Configuration::DEFAULT_ARTIFACT_PATH,
88
+ Configuration::DEFAULT_BINDINGS_PATH)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Installs a hook into Parsanol at load time, so these are required, not
95
+ # autoloaded.
96
+ require_relative "parser/errors"
97
+ require_relative "parser/native_recursion_guard"
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Latexmath
6
+ # The legacy LaTeX symbol table, exported from the pinned Plurimath by
7
+ # scripts/export_symbols.rb into data/latex-symbols.json: for every
8
+ # spelling a symbol capture can have, its Unicode value, the MathML
9
+ # element the legacy writer uses and its kind. Loading it needs no
10
+ # Plurimath.
11
+ #
12
+ # The legacy transform looks a captured symbol up by its text, stripped;
13
+ # text the table lacks stays a plain symbol of that text (#resolve).
14
+ module Symbols
15
+ DATA_PATH = File.expand_path("../../data/latex-symbols.json", __dir__)
16
+ FORMAT = "latexmath-symbols/v1"
17
+
18
+ # +spelling+: the captured text ("alpha", "&#x3b1;", "("); +value+: its
19
+ # Unicode text; +mathml+: "mi" or "mo", nil for text the table lacks;
20
+ # +kind+: "symbol", "open", "close" or "paren"; +plurimath_class+: the
21
+ # legacy class relative to Plurimath::Math::Symbols.
22
+ Entry = Struct.new(:spelling, :value, :mathml, :kind, :plurimath_class, keyword_init: true)
23
+
24
+ @table = nil
25
+ @lock = Mutex.new
26
+
27
+ module_function
28
+
29
+ # The Entry for the captured +text+, or nil when the table lacks it.
30
+ def lookup(text)
31
+ table[:entries][text.to_s.strip]
32
+ end
33
+
34
+ # The Entry for the captured +text+; text the table lacks becomes a
35
+ # plain symbol of its own (stripped) text, as in the legacy transform.
36
+ def resolve(text)
37
+ key = text.to_s.strip
38
+ table[:entries][key] ||
39
+ Entry.new(spelling: key, value: key, mathml: nil, kind: "symbol", plurimath_class: "Symbol").freeze
40
+ end
41
+
42
+ def size
43
+ table[:entries].size
44
+ end
45
+
46
+ # Where the table comes from: the oracle repository, commit, lib tree and
47
+ # Ruby of the export.
48
+ def provenance
49
+ table[:oracle]
50
+ end
51
+
52
+ def table
53
+ @lock.synchronize { @table ||= load_table(DATA_PATH) }
54
+ end
55
+
56
+ def load_table(path)
57
+ data = JSON.parse(File.read(path, encoding: "UTF-8"))
58
+ raise ArtifactError, "#{path} is #{data['format'].inspect}, not #{FORMAT}" unless data["format"] == FORMAT
59
+
60
+ entries = data.fetch("symbols").to_h do |r|
61
+ entry = Entry.new(spelling: r.fetch("spelling"), value: r.fetch("value"), mathml: r.fetch("mathml"),
62
+ kind: r.fetch("kind"), plurimath_class: r.fetch("class")).freeze
63
+ [entry.spelling, entry]
64
+ end
65
+ raise ArtifactError, "#{path} repeats a spelling" unless entries.size == data["symbols"].size
66
+
67
+ { oracle: data.fetch("oracle").freeze, entries: entries.freeze }.freeze
68
+ rescue JSON::ParserError, KeyError, SystemCallError => e
69
+ raise ArtifactError, "symbol table #{path} cannot be loaded: #{e.class}: #{e.message}"
70
+ end
71
+
72
+ private_class_method :table, :load_table
73
+ end
74
+ end
@@ -1,3 +1,3 @@
1
1
  module Latexmath
2
- VERSION = '0.1.4'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
data/lib/latexmath.rb CHANGED
@@ -1,88 +1,6 @@
1
- #require 'byebug' unless RUBY_ENGINE == 'opal'
2
- require 'json'
3
- require 'htmlentities'
4
- require 'ox'
5
- require_relative 'latexmath/ext'
6
- require_relative 'latexmath/version'
7
- require_relative 'latexmath/constants/symbols'
8
- require_relative 'latexmath/aggregator'
9
- require_relative 'latexmath/converter'
10
- require_relative 'latexmath/symbol'
11
- require_relative 'latexmath/tokenizer'
12
- require_relative 'latexmath/xml/element'
13
- require_relative 'latexmath/equation'
1
+ # frozen_string_literal: true
14
2
 
15
- module Latexmath
16
- MATRICES = [
17
- '\\matrix',
18
- '\\matrix*',
19
- '\\pmatrix',
20
- '\\pmatrix*',
21
- '\\bmatrix',
22
- '\\bmatrix*',
23
- '\\Bmatrix',
24
- '\\Bmatrix*',
25
- '\\vmatrix',
26
- '\\vmatrix*',
27
- '\\Vmatrix',
28
- '\\Vmatrix*',
29
- '\\array',
30
- '\\split',
31
- '\\substack'
32
- ].freeze
33
-
34
- SPACES = ['\\,', '\\:', '\\;', '\\\\'].freeze
35
- STYLES = {
36
- '\\bf' => 'mathbf'
37
- }.freeze
38
-
39
- LIMITS = ['\\lim', '\\sup', '\\inf', '\\max', '\\min'].freeze
40
- COMMANDS = {
41
- # command: [params_count, mathml_equivalent, attributes]
42
- '_' => [2, 'msub', {}],
43
- '^' => [2, 'msup', {}],
44
- '_^' => [3, 'msubsup', {}],
45
- '\\frac' => [2, 'mfrac', {}],
46
- '\\sqrt' => [1, 'msqrt', {}],
47
- '\\root' => [2, 'mroot', {}],
48
- '\\binom' => [2, 'mfrac', { "linethickness": '0' }],
49
- '\\left' => [
50
- 1,
51
- 'mo',
52
- [%w[stretchy true], %w[fence true], %w[form prefix]]
53
- ],
54
- '\\right' => [
55
- 1,
56
- 'mo',
57
- [%w[stretchy true], %w[fence true], %w[form postfix]]
58
- ],
59
- '\\overline' => [1, 'mover', {}],
60
- '\\bar' => [1, 'mover', {}],
61
- '\\underline' => [1, 'munder', {}],
62
- '\\limits' => [3, 'munderover', {}],
63
- '\\overrightarrow' => [1, 'mover', {}]
64
- }
65
-
66
- COMMANDS['\\quad'] = [0, 'mo', { "mathvariant": 'italic', separator: 'true' }]
67
- COMMANDS['\\qquad'] = [0, 'mo', { "mathvariant": 'italic', separator: 'true' }]
68
- SPACES.each do |space|
69
- COMMANDS[space] = [0, 'mspace', { "width": '0.167em' }]
70
- end
71
-
72
- MATRICES.each do |matrix|
73
- COMMANDS[matrix] = [1, 'mtable', {}]
74
- end
75
-
76
- LIMITS.each do |limit|
77
- COMMANDS[limit] = [1, 'munder', {}]
78
- end
79
-
80
- def self.parse(string)
81
- lxm_input = HTMLEntities.new.decode(string)
82
-
83
- # parse
84
- Equation.new(lxm_input)
85
- end
86
-
87
- class Error < StandardError; end
88
- end
3
+ # LaTeX math parser for Plurimath. Latexmath.parse returns the typed LaTeX
4
+ # model; see lib/latexmath/parser.rb, docs/parser-contract.adoc and
5
+ # docs/model-contract.adoc.
6
+ require_relative "latexmath/parser"
@@ -0,0 +1,36 @@
1
+ # Written by scripts/verify_lml.rb: the LML checksum of every model
2
+ # document, then the sha256 of the documents file.
3
+ Latexmath::Model::Args sha256:5462960711eec1726badec07cd0a0697463c8fc35df4769e10bd23323bfabaa6
4
+ Latexmath::Model::Base sha256:3ce2dcae82ec2dc51287195a3cee13977f9325889a8523da2643f56c9d3ef1f4
5
+ Latexmath::Model::Binary sha256:10ab6af22512dc6e3b794d02d715b4b45e23c7a448766233d09e12cc96e5fb1f
6
+ Latexmath::Model::BinaryFunctions sha256:91faa02cd9931ba27287f393594d7b1312d8324f99fe7f07a64e3bbc15b2ef17
7
+ Latexmath::Model::Dividend sha256:b3e9b1d3d03d9640d83b3268ced1204f7d8ccde89453115823df34bf43aa0dd8
8
+ Latexmath::Model::Divisor sha256:8830d0ca59e31ba6645cde667705156f73d02fdc60d87745f8d8beac81725f0a
9
+ Latexmath::Model::Document sha256:c770d3c85caedc6dd289102e426cc8cf5cc7327e54f021c36ec9d277c5fbe77a
10
+ Latexmath::Model::Ending sha256:4e6b05b7cc3e0cf383082b5b91eab692d4fcbe7aa9f3709b015b80c8eedaec8b
11
+ Latexmath::Model::Environment sha256:4ca449f28a047f5c233571f18c73a53ad40fc283aedb7ab7876a25866f3fce10
12
+ Latexmath::Model::Expression sha256:e5cb9286cf37e399d9fe11058e517d5dd4a11e3076fb7d08141ca4689252ec07
13
+ Latexmath::Model::FirstValue sha256:f23a8ce39e0e1a4800e8f46ccef2cc39b1f0f50333aab254c41cee80ad423638
14
+ Latexmath::Model::IntermediateExp sha256:124f5a4368fc210f21361fbc54629c6da4c44c4e9185c354c78a4aac97b3cee7
15
+ Latexmath::Model::ItemsItem sha256:28625103e5527d530dec8971463a4186aaa1fa31a4960be1e44103e1199cf601
16
+ Latexmath::Model::LatexTree sha256:ab699f312f98a337d0443fb0e46e901ec3a5f02d3b1dcc1a1e41edf2db8a8c96
17
+ Latexmath::Model::LeftParen sha256:9948524e67fa426033187ebc1c9e015e2c5e3a4ace59b81ca3d223de9e849484
18
+ Latexmath::Model::LeftRight sha256:d219a07366ebf63b556957d75d0a2ef9a7b0b4d69db9d5a47cef3ba198673072
19
+ Latexmath::Model::Limits sha256:04d4af34438ccab20272798c0d23877dd1a14cc95c01290ffc37ba156d1a8a0c
20
+ Latexmath::Model::MathSymbol sha256:655eefa4badd24c9960f2ba8728f4a1981b808c9fbe076bfdc7875b8703cea3a
21
+ Latexmath::Model::Options sha256:09363eec7dc62f50a06ea6a1065280fbeaef1385e2e84f1da92f7df4e9a01e6c
22
+ Latexmath::Model::Over sha256:5ec5c3ccd41a5c65149887cbed15632ac8dd2a05bd66ba337cde4b6cb1cee7c1
23
+ Latexmath::Model::Power sha256:6bf73b69d65980865f2c1d8922e0a2ab6c43c76d96f9135b94f9ffb2936da81a
24
+ Latexmath::Model::PowerBase sha256:31dae32f1de56ab2203d7b649c760a1e25ab170b6847dbec3286d5c9cd24ecae
25
+ Latexmath::Model::RightParen sha256:b6f7510f3a8bf4cf8b61e15074e483f921c1ec51c512a333a7eb68cc53e24569
26
+ Latexmath::Model::SecondValue sha256:94bef28cac697a6d37d1e84bf8441202effabfb540ae017790faba5c83c59a3c
27
+ Latexmath::Model::Sequence sha256:3e7caee1c9af131b51beb87a8d666b03961c0885bba79f9db865c2c5eb9cca61
28
+ Latexmath::Model::Subscript sha256:ad5250291647cc6ec2caaecf3571a2a0244d77e2a3320b2513f00050a747c08b
29
+ Latexmath::Model::Supscript sha256:2143627f3829bcf53abfdebbdbab2824de4ddfcbdd77530075d0edbcfa07d48f
30
+ Latexmath::Model::SymbolNode sha256:e01f443c419fdcca1b2c3c70e21532d9aea1be13a4304558e01bb08644ae5fa1
31
+ Latexmath::Model::TableData sha256:b2e2b1c329b4017897fde5d61958487c3e88da33581c414480e15d8746fb7572
32
+ Latexmath::Model::TernaryClass sha256:bd741ce478aa270bd90cd24c9e2e8a073afc750e9796113a79e79284c5ae4f8b
33
+ Latexmath::Model::ThirdValue sha256:2ce323686afb2f7ad053ac49a62eb1dc092cbddd126f94f5ac2bc39cf043c4ac
34
+ Latexmath::Model::UnaryFunctions sha256:df5b690a991e6963faecbfdd2bebb06deb319bd0485d20c48379a3d844a67326
35
+ Latexmath::Model::UnderOver sha256:a6e7aa8d2ae55610b8473eed4fa39481c75bbfc09e1a44f4d14fd2df63ce8713
36
+ model/latex.lml.json sha256:312b1de981c4aa6435fba996c4fc34d34f401e36742a6cdc1c4e9f1cf1f20d60