plurimath 0.11.11 → 0.11.13
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.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-tests.yml +7 -0
- data/.github/workflows/shared-testsuite.yml +24 -0
- data/.gitmodules +3 -0
- data/.rspec-opal +3 -0
- data/Gemfile +18 -0
- data/README.adoc +297 -3
- data/docs/catalog.adoc +73 -0
- data/docs/uncertainty-asciimath-tree.md +92 -0
- data/docs/uncertainty.adoc +522 -0
- data/lib/plurimath/asciimath/parse.rb +43 -226
- data/lib/plurimath/asciimath/parser.rb +58 -1
- data/lib/plurimath/asciimath/slice_adapter.rb +138 -0
- data/lib/plurimath/asciimath/transform.rb +218 -0
- data/lib/plurimath/asciimath.rb +1 -0
- data/lib/plurimath/catalog/entry.rb +109 -0
- data/lib/plurimath/catalog/validation.rb +109 -0
- data/lib/plurimath/catalog.rb +96 -0
- data/lib/plurimath/cli/convert.rb +40 -0
- data/lib/plurimath/cli/helpers.rb +49 -0
- data/lib/plurimath/cli/render.rb +64 -0
- data/lib/plurimath/cli.rb +55 -57
- data/lib/plurimath/documentation.rb +99 -0
- data/lib/plurimath/errors/ambiguous_decimal_error.rb +19 -0
- data/lib/plurimath/errors/invalid_uncertainty.rb +18 -0
- data/lib/plurimath/errors/missing_dependency_error.rb +23 -0
- data/lib/plurimath/errors/rendering_failed.rb +17 -0
- data/lib/plurimath/errors/rendering_unavailable.rb +25 -0
- data/lib/plurimath/errors/unsupported_render_format.rb +18 -0
- data/lib/plurimath/errors/unsupported_semantic_tree_error.rb +12 -0
- data/lib/plurimath/errors.rb +5 -0
- data/lib/plurimath/math/evaluation/evaluator.rb +48 -0
- data/lib/plurimath/math/evaluation/expression_parser.rb +29 -20
- data/lib/plurimath/math/formula/class_tree.rb +92 -0
- data/lib/plurimath/math/formula.rb +43 -2
- data/lib/plurimath/math/function/abs.rb +6 -0
- data/lib/plurimath/math/function/arccos.rb +6 -0
- data/lib/plurimath/math/function/arcsin.rb +6 -0
- data/lib/plurimath/math/function/arctan.rb +6 -0
- data/lib/plurimath/math/function/arg.rb +20 -0
- data/lib/plurimath/math/function/bar.rb +6 -0
- data/lib/plurimath/math/function/base.rb +6 -0
- data/lib/plurimath/math/function/binary_function.rb +4 -0
- data/lib/plurimath/math/function/cancel.rb +6 -0
- data/lib/plurimath/math/function/ceil.rb +6 -0
- data/lib/plurimath/math/function/color.rb +12 -0
- data/lib/plurimath/math/function/cos.rb +6 -0
- data/lib/plurimath/math/function/cosh.rb +6 -0
- data/lib/plurimath/math/function/cot.rb +6 -0
- data/lib/plurimath/math/function/coth.rb +6 -0
- data/lib/plurimath/math/function/csc.rb +6 -0
- data/lib/plurimath/math/function/csch.rb +6 -0
- data/lib/plurimath/math/function/ddot.rb +6 -0
- data/lib/plurimath/math/function/deg.rb +6 -0
- data/lib/plurimath/math/function/det.rb +6 -0
- data/lib/plurimath/math/function/dim.rb +6 -0
- data/lib/plurimath/math/function/dot.rb +6 -0
- data/lib/plurimath/math/function/exp.rb +6 -0
- data/lib/plurimath/math/function/fenced.rb +6 -0
- data/lib/plurimath/math/function/floor.rb +6 -0
- data/lib/plurimath/math/function/font_style/bold-fraktur.rb +6 -0
- data/lib/plurimath/math/function/font_style/bold-italic.rb +6 -0
- data/lib/plurimath/math/function/font_style/bold-sans-serif.rb +6 -0
- data/lib/plurimath/math/function/font_style/bold-script.rb +6 -0
- data/lib/plurimath/math/function/font_style/bold.rb +6 -0
- data/lib/plurimath/math/function/font_style/double_struck.rb +6 -0
- data/lib/plurimath/math/function/font_style/fraktur.rb +6 -0
- data/lib/plurimath/math/function/font_style/italic.rb +6 -0
- data/lib/plurimath/math/function/font_style/monospace.rb +6 -0
- data/lib/plurimath/math/function/font_style/normal.rb +6 -0
- data/lib/plurimath/math/function/font_style/sans-serif-bold-italic.rb +6 -0
- data/lib/plurimath/math/function/font_style/sans-serif-italic.rb +6 -0
- data/lib/plurimath/math/function/font_style/sans-serif.rb +6 -0
- data/lib/plurimath/math/function/font_style/script.rb +6 -0
- data/lib/plurimath/math/function/font_style.rb +4 -0
- data/lib/plurimath/math/function/frac.rb +6 -0
- data/lib/plurimath/math/function/gcd.rb +6 -0
- data/lib/plurimath/math/function/glb.rb +12 -0
- data/lib/plurimath/math/function/hat.rb +6 -0
- data/lib/plurimath/math/function/hom.rb +6 -0
- data/lib/plurimath/math/function/inf.rb +6 -0
- data/lib/plurimath/math/function/int.rb +7 -1
- data/lib/plurimath/math/function/intent.rb +35 -0
- data/lib/plurimath/math/function/ker.rb +6 -0
- data/lib/plurimath/math/function/lcm.rb +12 -0
- data/lib/plurimath/math/function/lg.rb +6 -0
- data/lib/plurimath/math/function/lim.rb +6 -0
- data/lib/plurimath/math/function/liminf.rb +6 -0
- data/lib/plurimath/math/function/limits.rb +6 -0
- data/lib/plurimath/math/function/limsup.rb +6 -0
- data/lib/plurimath/math/function/ln.rb +6 -0
- data/lib/plurimath/math/function/log.rb +6 -0
- data/lib/plurimath/math/function/longdiv.rb +6 -0
- data/lib/plurimath/math/function/lub.rb +6 -0
- data/lib/plurimath/math/function/max.rb +6 -0
- data/lib/plurimath/math/function/mbox.rb +6 -0
- data/lib/plurimath/math/function/menclose.rb +19 -0
- data/lib/plurimath/math/function/merror.rb +22 -0
- data/lib/plurimath/math/function/min.rb +6 -0
- data/lib/plurimath/math/function/mlabeledtr.rb +23 -0
- data/lib/plurimath/math/function/mod.rb +6 -0
- data/lib/plurimath/math/function/msgroup.rb +20 -0
- data/lib/plurimath/math/function/multiscript.rb +6 -0
- data/lib/plurimath/math/function/nary.rb +11 -0
- data/lib/plurimath/math/function/norm.rb +6 -0
- data/lib/plurimath/math/function/obrace.rb +6 -0
- data/lib/plurimath/math/function/oint.rb +7 -1
- data/lib/plurimath/math/function/over.rb +6 -0
- data/lib/plurimath/math/function/overleftrightarrow.rb +6 -0
- data/lib/plurimath/math/function/overset.rb +6 -0
- data/lib/plurimath/math/function/phantom.rb +6 -0
- data/lib/plurimath/math/function/power.rb +6 -0
- data/lib/plurimath/math/function/power_base.rb +6 -0
- data/lib/plurimath/math/function/prod.rb +7 -1
- data/lib/plurimath/math/function/right.rb +1 -1
- data/lib/plurimath/math/function/root.rb +6 -0
- data/lib/plurimath/math/function/rule.rb +20 -0
- data/lib/plurimath/math/function/scarries.rb +20 -0
- data/lib/plurimath/math/function/sec.rb +6 -0
- data/lib/plurimath/math/function/sech.rb +6 -0
- data/lib/plurimath/math/function/semantics.rb +20 -0
- data/lib/plurimath/math/function/sin.rb +6 -0
- data/lib/plurimath/math/function/sinh.rb +6 -0
- data/lib/plurimath/math/function/sqrt.rb +6 -0
- data/lib/plurimath/math/function/stackrel.rb +6 -0
- data/lib/plurimath/math/function/substack.rb +6 -0
- data/lib/plurimath/math/function/sum.rb +7 -1
- data/lib/plurimath/math/function/sup.rb +6 -0
- data/lib/plurimath/math/function/table/align.rb +6 -0
- data/lib/plurimath/math/function/table/array.rb +6 -0
- data/lib/plurimath/math/function/table/bmatrix.rb +6 -0
- data/lib/plurimath/math/function/table/cases.rb +6 -0
- data/lib/plurimath/math/function/table/eqarray.rb +6 -0
- data/lib/plurimath/math/function/table/matrix.rb +6 -0
- data/lib/plurimath/math/function/table/multline.rb +6 -0
- data/lib/plurimath/math/function/table/pmatrix.rb +6 -0
- data/lib/plurimath/math/function/table/split.rb +6 -0
- data/lib/plurimath/math/function/table/vmatrix.rb +6 -0
- data/lib/plurimath/math/function/table.rb +10 -0
- data/lib/plurimath/math/function/tan.rb +6 -0
- data/lib/plurimath/math/function/tanh.rb +6 -0
- data/lib/plurimath/math/function/ternary_function.rb +4 -0
- data/lib/plurimath/math/function/text.rb +6 -0
- data/lib/plurimath/math/function/tilde.rb +6 -0
- data/lib/plurimath/math/function/ubrace.rb +6 -0
- data/lib/plurimath/math/function/ul.rb +6 -0
- data/lib/plurimath/math/function/unary_function.rb +4 -0
- data/lib/plurimath/math/function/uncertainty.rb +655 -0
- data/lib/plurimath/math/function/underover.rb +6 -0
- data/lib/plurimath/math/function/underset.rb +6 -0
- data/lib/plurimath/math/function/vec.rb +6 -0
- data/lib/plurimath/math/function.rb +1 -0
- data/lib/plurimath/math/renderer.rb +95 -0
- data/lib/plurimath/math/semantic_tree.rb +331 -0
- data/lib/plurimath/math/symbols/symbol.rb +12 -5
- data/lib/plurimath/math.rb +26 -0
- data/lib/plurimath/mathml/translator.rb +32 -0
- data/lib/plurimath/mathml/uncertainty_marker.rb +118 -0
- data/lib/plurimath/omml/translator.rb +68 -0
- data/lib/plurimath/omml/utility.rb +9 -1
- data/lib/plurimath/symbol_documentation.rb +57 -0
- data/lib/plurimath/unicode_math/parsing_rules/common_rules.rb +2 -1
- data/lib/plurimath/unicode_math/parsing_rules/masked.rb +3 -0
- data/lib/plurimath/unicode_math/transform.rb +64 -3
- data/lib/plurimath/unicode_math/utility.rb +38 -0
- data/lib/plurimath/version.rb +1 -1
- data/lib/plurimath.rb +3 -0
- data/plurimath.gemspec +3 -1
- data/scripts/replay_asciimath_corpus.rb +611 -0
- metadata +30 -4
|
@@ -2,232 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
class Asciimath
|
|
5
|
-
class
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
rule(:ternary_classes) do
|
|
51
|
-
arr_to_expression(Constants::TERNARY_CLASSES, :ternary_class)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
rule(:binary_classes) do
|
|
55
|
-
arr_to_expression(Constants::BINARY_CLASSES, :binary_class)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
rule(:sub_sup_classes) do
|
|
59
|
-
arr_to_expression(Constants::SUB_SUP_CLASSES, :binary_class)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
rule(:open_table) do
|
|
63
|
-
arr_to_expression(Constants::TABLE_PARENTHESIS.keys, :table_left)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
rule(:close_table) do
|
|
67
|
-
arr_to_expression(Constants::TABLE_PARENTHESIS.values, :table_right)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
rule(:lparen) do
|
|
71
|
-
Constants::PARENTHESIS.keys.reduce do |expression, parenthesis|
|
|
72
|
-
expression = str(expression) if expression.is_a?(Symbol)
|
|
73
|
-
expression | str(parenthesis)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
rule(:rparen) do
|
|
78
|
-
Constants::PARENTHESIS.values.reduce do |expression, parenthesis|
|
|
79
|
-
expression = str(expression) if expression.is_a?(String)
|
|
80
|
-
expression | str(parenthesis)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
rule(:left_right) do
|
|
85
|
-
(str("left") >> space? >> left_right_open_paren.as(:left) >> space? >> (iteration.maybe >> sequence.maybe).as(:left_right_value) >> space? >> str("right") >> space? >> left_right_close_paren.as(:right)) |
|
|
86
|
-
((table.as(:numerator) >> space? >> match(/(?<!\/)\/(?!\/)/) >> space? >> iteration.as(:denominator)).as(:frac) >> expression) |
|
|
87
|
-
(table.as(:table) >> power_base.maybe >> expression.maybe)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
rule(:quoted_text) do
|
|
91
|
-
(str('"') >> str("unitsml(") >> (str(')"').absent? >> any).repeat.as(:unitsml) >> str(')"')) |
|
|
92
|
-
(str('"') >> match("[^\"]").repeat.as(:text) >> str('"')) |
|
|
93
|
-
(str('"') >> str("").as(:text))
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
rule(:symbol_text_or_integer) do
|
|
97
|
-
sub_sup_classes |
|
|
98
|
-
binary_classes |
|
|
99
|
-
ternary_classes |
|
|
100
|
-
hash_to_expression(Constants.precompile_constants) |
|
|
101
|
-
(match(/[0-9]/).as(:number) >> (decimal_marker >> match("[0-9]")).absent? >> str(",").as(:comma)).repeat(1).as(:comma_separated) |
|
|
102
|
-
quoted_text |
|
|
103
|
-
(str("d").as(:d) >> str("x").as(:x)).as(:intermediate_exp) |
|
|
104
|
-
((str("left").absent? >> str("right").absent?) >> match["a-zA-Z"].as(:symbol)) |
|
|
105
|
-
match(/[^\[{(\\\/@;:.,'"|\]})0-9a-zA-Z\-><$%^&*_=+!`~\s?ℒℛᑕᑐ]/).as(:symbol) |
|
|
106
|
-
number
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
rule(:power_base) do
|
|
110
|
-
(base >> space? >> script_operand.as(:base_value) >> power >> (power >> power.maybe).absent? >> space? >> script_operand.as(:power_value)) |
|
|
111
|
-
(space? >> base >> space? >> script_operand.as(:base_value)).as(:base) |
|
|
112
|
-
(space? >> power >> (power >> power.maybe).absent? >> space? >> script_operand.as(:power_value)).as(:power) |
|
|
113
|
-
(space? >> base >> space? >> power.as(:symbol).as(:base_value)).as(:base) |
|
|
114
|
-
(space? >> power >> (power >> power.maybe).absent? >> space? >> base.as(:symbol).as(:power_value)).as(:power)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
rule(:power_base_rules) do
|
|
118
|
-
(sub_sup_classes >> power_base).as(:power_base) |
|
|
119
|
-
(binary_classes >> space? >> sequence.as(:base_value).maybe >> space? >> sequence.as(:power_value).maybe).as(:power_base) |
|
|
120
|
-
(sequence.as(:power_base) >> power_base).as(:power_base)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
rule(:table) do
|
|
124
|
-
(str("{").as(:table_left) >> space? >> tr >> space? >> close_table.as(:table_right)) |
|
|
125
|
-
(open_table.as(:table_left) >> space? >> tr >> space? >> close_table.as(:table_right)) |
|
|
126
|
-
(str("norm").as(:norm) >> open_table.as(:table_left) >> space? >> tr >> space? >> close_table.as(:table_right)) |
|
|
127
|
-
(str("|").as(:table_left) >> space? >> tr >> space? >> str("|").as(:table_right)) |
|
|
128
|
-
(str("left") >> space? >> left_right_open_paren.as(:left) >> space? >> tr >> space? >> str("right") >> space? >> left_right_close_paren.as(:right))
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
rule(:tr) do
|
|
132
|
-
((left_right_open_paren.as(:open_tr) >> td.as(:tds_list) >> left_right_close_paren).as(:table_row) >> space? >> comma >> space? >> tr.as(:expr)) |
|
|
133
|
-
(left_right_open_paren.as(:open_tr) >> td.as(:tds_list) >> left_right_close_paren).as(:table_row)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
rule(:color_value) do
|
|
137
|
-
(color_left_parenthesis.capture(:paren).as(:lparen) >> expression.as(:rgb_color) >> color_right_parenthesis.maybe.as(:rparen)).as(:intermediate_exp) |
|
|
138
|
-
iteration
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
rule(:sequence) do
|
|
142
|
-
(lparen.as(:lparen) >> space? >> expression.maybe.as(:expr) >> space? >> rparen.maybe.as(:rparen)).as(:intermediate_exp) |
|
|
143
|
-
(str("text") >> lparen.capture(:paren) >> read_text.as(:text) >> rparen.maybe).as(:intermediate_exp) |
|
|
144
|
-
symbol_text_or_integer
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
rule(:frac) do
|
|
148
|
-
(sequence.as(:numerator) >> space? >> match(/(?<!\/)\/(?!\/)/) >> space? >> iteration.as(:denominator)).as(:frac) |
|
|
149
|
-
((power_base_rules | power_base).as(:numerator) >> space? >> match(/(?<!\/)\/(?!\/)/) >> space? >> iteration.as(:denominator)).as(:frac)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
rule(:mod) do
|
|
153
|
-
(sequence.as(:dividend) >> space? >> str("mod").as(:mod) >> space? >> mod_operand.as(:divisor)).as(:mod) |
|
|
154
|
-
((power_base_rules >> power_base).as(:dividend) >> space? >> str("mod").as(:mod) >> space? >> mod_operand.as(:divisor)).as(:mod) |
|
|
155
|
-
(power_base_rules.as(:dividend) >> space? >> str("mod").as(:mod) >> space? >> mod_operand.as(:divisor)).as(:mod)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
rule(:iteration) do
|
|
159
|
-
ternary_classes_rules |
|
|
160
|
-
(table.as(:table) >> power_base.maybe) |
|
|
161
|
-
comma.as(:comma) |
|
|
162
|
-
mod |
|
|
163
|
-
(sequence.as(:sequence) >> space? >> str("//").as(:symbol)) |
|
|
164
|
-
(str("color") >> color_value.as(:color) >> sequence.as(:color_value)) |
|
|
165
|
-
frac |
|
|
166
|
-
(power_base_rules >> power_base) |
|
|
167
|
-
power_base_rules |
|
|
168
|
-
sequence.as(:sequence) |
|
|
169
|
-
space
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
rule(:expression) do
|
|
173
|
-
left_right.as(:left_right) |
|
|
174
|
-
(iteration >> space? >> expression).as(:expr) |
|
|
175
|
-
(base.as(:symbol) >> expression.maybe).as(:expr) |
|
|
176
|
-
(power.as(:symbol) >> expression.maybe).as(:expr) |
|
|
177
|
-
str("") |
|
|
178
|
-
(rparen.as(:rparen) >> space? >> controversial_symbols >> comma.as(:comma).maybe >> expression).repeat(1).as(:expr) |
|
|
179
|
-
(power.as(:symbol) >> space? >> expression).as(:expr) |
|
|
180
|
-
(table.as(:table) >> space? >> rparen.as(:rparen) >> space? >> expression.as(:expr).maybe) |
|
|
181
|
-
comma.as(:comma).maybe
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
root :expression
|
|
185
|
-
|
|
186
|
-
def arr_to_expression(arr, name = nil)
|
|
187
|
-
type = arr.first.class
|
|
188
|
-
arr.reduce do |expression, expr_string|
|
|
189
|
-
expression = str(expression).as(name) if expression.is_a?(type)
|
|
190
|
-
expression | str(expr_string).as(name)
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def read_text
|
|
195
|
-
dynamic do |_sour, context|
|
|
196
|
-
rparen = Constants::PARENTHESIS[context.captures[:paren].to_sym]
|
|
197
|
-
match("[^#{rparen}]").repeat
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def hash_to_expression(arr)
|
|
202
|
-
type = arr.first.class
|
|
203
|
-
@@expression ||= arr.reduce do |expression, expr_string|
|
|
204
|
-
expression = dynamic_parser_rules(expression) if expression.is_a?(type)
|
|
205
|
-
expression | dynamic_parser_rules(expr_string)
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
def dynamic_parser_rules(expr)
|
|
210
|
-
first_value = str(expr.first.to_s)
|
|
211
|
-
case expr.last
|
|
212
|
-
when :symbol then (str("\\").as(:slash) >> match("\s").repeat >> str("\n")) | first_value.as(:symbol)
|
|
213
|
-
when :unary_class then unary_functions(first_value)
|
|
214
|
-
when :fonts then first_value.as(:fonts_class) >> space? >> sequence.as(:fonts_value)
|
|
215
|
-
when :special_fonts then first_value.as(:bold_fonts)
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
def decimal_marker
|
|
220
|
-
str(Plurimath.configuration.decimal)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def unary_functions(first_value)
|
|
224
|
-
if ["'underbrace'", "'ubrace'"].include?(first_value.to_s)
|
|
225
|
-
(first_value.as(:unary_class) >> space? >> str("_").as(:symbol)).as(:unary) |
|
|
226
|
-
(first_value.as(:unary_class) >> space? >> sequence.maybe).as(:unary)
|
|
227
|
-
else
|
|
228
|
-
(first_value.as(:unary_class).as(:power_base) >> space? >> power_base).as(:unary) |
|
|
229
|
-
(first_value.as(:unary_class) >> space? >> sequence.maybe).as(:unary)
|
|
230
|
-
end
|
|
5
|
+
# The AsciiMath grammar lives in the amath gem. This class keeps the
|
|
6
|
+
# tree-level entry point for callers that parse without transforming
|
|
7
|
+
# (plurimath-testsuite's corpus generator calls Parse.new.parse): it
|
|
8
|
+
# returns the Parslet-shaped tree Asciimath::Transform expects, and
|
|
9
|
+
# raises Parslet::ParseFailed with a cause at amath's rejection position.
|
|
10
|
+
class Parse
|
|
11
|
+
# amath is not released yet, so the Gemfile line points at GitHub.
|
|
12
|
+
AMATH_GEMFILE_LINE = 'gem "amath", github: "plurimath/amath"'
|
|
13
|
+
|
|
14
|
+
def parse(text)
|
|
15
|
+
load_amath
|
|
16
|
+
parse_with_amath(text)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# Loaded here, outside parse_with_amath, so its `rescue ::Amath::...`
|
|
22
|
+
# clause is never evaluated while Amath is undefined.
|
|
23
|
+
def load_amath
|
|
24
|
+
require "amath"
|
|
25
|
+
rescue LoadError => e
|
|
26
|
+
raise unless e.path == "amath" || e.message.end_with?("-- amath")
|
|
27
|
+
|
|
28
|
+
raise Math::MissingDependencyError.new(
|
|
29
|
+
:asciimath,
|
|
30
|
+
format_name: "AsciiMath",
|
|
31
|
+
gem_name: "amath",
|
|
32
|
+
gemfile_line: AMATH_GEMFILE_LINE,
|
|
33
|
+
), cause: nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse_with_amath(text)
|
|
37
|
+
tree = ::Amath.parse(text, decimal: Plurimath.configuration.decimal)
|
|
38
|
+
SliceAdapter.convert(tree, text)
|
|
39
|
+
rescue ::Amath::ParseError => e
|
|
40
|
+
raise parse_failed(e, text)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def parse_failed(error, text)
|
|
44
|
+
position = Parslet::Position.new(text, error.byte_offset)
|
|
45
|
+
cause = Parslet::Cause.new(error.message, Parslet::Source.new(text),
|
|
46
|
+
position, [])
|
|
47
|
+
Parslet::ParseFailed.new(error.message, cause)
|
|
231
48
|
end
|
|
232
49
|
end
|
|
233
50
|
end
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
class Asciimath
|
|
5
5
|
class Parser
|
|
6
|
+
# An uncertainty call. amath splits letters, so xustd(1,5) is x
|
|
7
|
+
# followed by ustd(1,5): a preceding letter does not exempt it.
|
|
8
|
+
UNCERTAINTY_CALL = /u(?:std|exp|cov)\(/
|
|
9
|
+
|
|
6
10
|
attr_accessor :text
|
|
7
11
|
|
|
8
12
|
def initialize(text)
|
|
@@ -15,12 +19,65 @@ module Plurimath
|
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def parse
|
|
18
|
-
|
|
22
|
+
ambiguous_uncertainty_comma!
|
|
23
|
+
nodes = parse_tree
|
|
19
24
|
transformed_tree = Transform.new.apply(nodes)
|
|
20
25
|
return transformed_tree if transformed_tree.is_a?(Math::Formula)
|
|
21
26
|
|
|
22
27
|
Math::Formula.new(transformed_tree)
|
|
23
28
|
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def parse_tree
|
|
33
|
+
Parse.new.parse(text)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Uncertainty arguments use "." as the decimal mark and "," between
|
|
37
|
+
# arguments. Under a locale whose decimal mark is not ".", a separator
|
|
38
|
+
# comma directly followed by a digit (ustd(1,5), uexp(10,0, 0,3, 2))
|
|
39
|
+
# most likely is a decimal comma: amath would read it as a separator,
|
|
40
|
+
# or give up on the call and read letters (ustd(1,230, 0,004) under fr).
|
|
41
|
+
# Raise instead of guessing; other input is not checked.
|
|
42
|
+
def ambiguous_uncertainty_comma!
|
|
43
|
+
decimal = Plurimath.configuration.decimal
|
|
44
|
+
return if decimal == "." || text.nil?
|
|
45
|
+
|
|
46
|
+
text.to_enum(:scan, UNCERTAINTY_CALL).each do
|
|
47
|
+
start = Regexp.last_match.begin(0)
|
|
48
|
+
next if text[0...start].count('"').odd?
|
|
49
|
+
|
|
50
|
+
call = uncertainty_call(start, Regexp.last_match.end(0))
|
|
51
|
+
next unless call
|
|
52
|
+
|
|
53
|
+
raise Math::AmbiguousDecimalError.new(
|
|
54
|
+
"uncertainty forms use '.' as the decimal mark; the comma " \
|
|
55
|
+
"before a digit in #{call} is ambiguous with decimal " \
|
|
56
|
+
"'#{decimal}' (locale #{Plurimath.configuration.locale}), " \
|
|
57
|
+
"write the value with '.' and separate arguments with ', '",
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The call text from start to its closing parenthesis if a top-level
|
|
63
|
+
# argument separator in it is directly followed by a digit, else nil.
|
|
64
|
+
def uncertainty_call(start, index)
|
|
65
|
+
depth = 1
|
|
66
|
+
quoted = false
|
|
67
|
+
ambiguous = false
|
|
68
|
+
while index < text.length && depth.positive?
|
|
69
|
+
char = text[index]
|
|
70
|
+
quoted = !quoted if char == '"'
|
|
71
|
+
unless quoted
|
|
72
|
+
depth += 1 if char == "("
|
|
73
|
+
depth -= 1 if char == ")"
|
|
74
|
+
ambiguous ||= depth == 1 && char == "," &&
|
|
75
|
+
text[index + 1].to_s.match?(/\A[0-9]\z/)
|
|
76
|
+
end
|
|
77
|
+
index += 1
|
|
78
|
+
end
|
|
79
|
+
text[start...index] if ambiguous
|
|
80
|
+
end
|
|
24
81
|
end
|
|
25
82
|
end
|
|
26
83
|
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plurimath
|
|
4
|
+
class Asciimath
|
|
5
|
+
# Turns a parse tree produced by amath (Parsanol) into a Parslet-shaped
|
|
6
|
+
# tree (Symbol keys, Parslet::Slice leaves), so Asciimath::Transform and
|
|
7
|
+
# the other Parslet::Slice consumers keep working unchanged.
|
|
8
|
+
#
|
|
9
|
+
# - Every leaf becomes a real Parslet::Slice. Parsanol slices carry a byte
|
|
10
|
+
# offset into the parsed string; Parslet::Position also stores a byte
|
|
11
|
+
# offset and derives the character offset (Slice#offset) from it, so the
|
|
12
|
+
# byte offset is kept and #offset answers in characters, as Parslet does.
|
|
13
|
+
# - Plain String leaves (Parsanol returns a bare "" for an empty match)
|
|
14
|
+
# become Parslet::Slice at the end of the last leaf seen so far, in tree
|
|
15
|
+
# order; for empty input that is ""@0, as Parslet gives.
|
|
16
|
+
# - Hashes keep their key order (String keys become Symbols, as Parslet
|
|
17
|
+
# always uses Symbols), arrays keep their order, [] stays [], nil stays
|
|
18
|
+
# nil.
|
|
19
|
+
module SliceAdapter
|
|
20
|
+
class OffsetError < StandardError; end
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def convert(tree, input)
|
|
25
|
+
input = input.to_s
|
|
26
|
+
line_cache = Parslet::Source::LineCache.new
|
|
27
|
+
line_cache.scan_for_line_endings(0, input)
|
|
28
|
+
Walker.new(input, line_cache).walk(tree)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Walker
|
|
32
|
+
def initialize(input, line_cache)
|
|
33
|
+
@input = input
|
|
34
|
+
@line_cache = line_cache
|
|
35
|
+
@cursor = 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Depth-first with an explicit stack instead of recursion: a long
|
|
39
|
+
# AsciiMath sum is a tree about two levels deep per term, which would
|
|
40
|
+
# overflow a small (1 MiB, as on Windows) stack. Children are visited
|
|
41
|
+
# in the same order as a recursive walk, so the cursor that places
|
|
42
|
+
# empty-string leaves sees the leaves in the same order.
|
|
43
|
+
def walk(root)
|
|
44
|
+
return leaf(root) unless container?(root)
|
|
45
|
+
|
|
46
|
+
result = empty_copy(root)
|
|
47
|
+
stack = [[entries(root), result, 0]]
|
|
48
|
+
until stack.empty?
|
|
49
|
+
frame = stack.last
|
|
50
|
+
items, out, index = frame
|
|
51
|
+
next stack.pop if index == items.size
|
|
52
|
+
|
|
53
|
+
frame[2] = index + 1
|
|
54
|
+
key, value = items[index]
|
|
55
|
+
if container?(value)
|
|
56
|
+
child = empty_copy(value)
|
|
57
|
+
store(out, key, child)
|
|
58
|
+
stack.push([entries(value), child, 0])
|
|
59
|
+
else
|
|
60
|
+
store(out, key, leaf(value))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
result
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def container?(node)
|
|
69
|
+
node.is_a?(Hash) || node.is_a?(Array)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def empty_copy(node)
|
|
73
|
+
node.is_a?(Hash) ? {} : []
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def entries(node)
|
|
77
|
+
node.is_a?(Hash) ? node.to_a : node.each_with_index.map { |v, i| [i, v] }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def store(out, key, value)
|
|
81
|
+
if out.is_a?(Hash)
|
|
82
|
+
out[symbol_key(key)] = value
|
|
83
|
+
else
|
|
84
|
+
out << value
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def leaf(node)
|
|
89
|
+
case node
|
|
90
|
+
when ::Parslet::Slice then keep(node)
|
|
91
|
+
when String then slice(@cursor, node)
|
|
92
|
+
when nil then nil
|
|
93
|
+
else
|
|
94
|
+
return slice_from(node) if foreign_slice?(node)
|
|
95
|
+
|
|
96
|
+
node
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def keep(node)
|
|
101
|
+
advance(node.position.bytepos, node.to_s)
|
|
102
|
+
node
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def symbol_key(key)
|
|
106
|
+
key.is_a?(String) ? key.to_sym : key
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def foreign_slice?(node)
|
|
110
|
+
node.respond_to?(:offset) && node.respond_to?(:to_str)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def slice_from(node)
|
|
114
|
+
content = node.to_str
|
|
115
|
+
bytepos = node.offset
|
|
116
|
+
unless @input.byteslice(bytepos, content.bytesize) == content
|
|
117
|
+
raise OffsetError,
|
|
118
|
+
"slice #{content.inspect} does not match the input at " \
|
|
119
|
+
"byte #{bytepos}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
slice(bytepos, content)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def slice(bytepos, content)
|
|
126
|
+
advance(bytepos, content)
|
|
127
|
+
position = ::Parslet::Position.new(@input, bytepos)
|
|
128
|
+
::Parslet::Slice.new(position, content.to_s.dup, @line_cache)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def advance(bytepos, content)
|
|
132
|
+
finish = bytepos + content.to_s.bytesize
|
|
133
|
+
@cursor = finish if finish > @cursor
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|