plurimath 0.11.1 → 0.11.3
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/.gitignore +3 -0
- data/.rubocop_todo.yml +108 -175
- data/Gemfile +1 -0
- data/README.adoc +282 -6
- data/lib/plurimath/asciimath/parse.rb +6 -1
- data/lib/plurimath/asciimath/transform.rb +2 -0
- data/lib/plurimath/base_number_prefix.rb +43 -0
- data/lib/plurimath/configuration.rb +9 -1
- data/lib/plurimath/errors/evaluation/division_by_zero_error.rb +13 -0
- data/lib/plurimath/errors/evaluation/error.rb +9 -0
- data/lib/plurimath/errors/evaluation/invalid_binding_error.rb +14 -0
- data/lib/plurimath/errors/evaluation/invalid_binding_key_error.rb +14 -0
- data/lib/plurimath/errors/evaluation/math_domain_error.rb +9 -0
- data/lib/plurimath/errors/evaluation/missing_variable_error.rb +13 -0
- data/lib/plurimath/errors/evaluation/non_finite_result_error.rb +13 -0
- data/lib/plurimath/errors/evaluation/unsupported_expression_error.rb +13 -0
- data/lib/plurimath/errors/evaluation.rb +18 -0
- data/lib/plurimath/errors.rb +1 -0
- data/lib/plurimath/formatter/numbers/base_notation.rb +54 -31
- data/lib/plurimath/formatter/numbers/formatted_notation.rb +62 -0
- data/lib/plurimath/formatter/numbers/formatted_number.rb +87 -0
- data/lib/plurimath/formatter/numbers/fraction.rb +1 -1
- data/lib/plurimath/formatter/numbers/mathml_renderer.rb +56 -0
- data/lib/plurimath/formatter/numbers/notation_renderer.rb +30 -29
- data/lib/plurimath/formatter/numbers/number_renderer.rb +10 -9
- data/lib/plurimath/formatter/numbers/omml_renderer.rb +74 -0
- data/lib/plurimath/formatter/numbers/source.rb +29 -4
- data/lib/plurimath/formatter/numbers/text_renderer.rb +52 -0
- data/lib/plurimath/formatter/numbers.rb +6 -2
- data/lib/plurimath/html/parse.rb +5 -0
- data/lib/plurimath/html/transform.rb +2 -0
- data/lib/plurimath/latex/parse.rb +5 -0
- data/lib/plurimath/latex/transform.rb +2 -0
- data/lib/plurimath/math/core.rb +52 -0
- data/lib/plurimath/math/evaluation/evaluator.rb +147 -0
- data/lib/plurimath/math/evaluation/expression_parser.rb +215 -0
- data/lib/plurimath/math/evaluation/iteration.rb +63 -0
- data/lib/plurimath/math/evaluation.rb +13 -0
- data/lib/plurimath/math/formula.rb +9 -0
- data/lib/plurimath/math/function/abs.rb +4 -0
- data/lib/plurimath/math/function/arccos.rb +4 -0
- data/lib/plurimath/math/function/arcsin.rb +4 -0
- data/lib/plurimath/math/function/arctan.rb +4 -0
- data/lib/plurimath/math/function/ceil.rb +4 -0
- data/lib/plurimath/math/function/cos.rb +4 -0
- data/lib/plurimath/math/function/cosh.rb +4 -0
- data/lib/plurimath/math/function/cot.rb +4 -0
- data/lib/plurimath/math/function/coth.rb +4 -0
- data/lib/plurimath/math/function/csc.rb +4 -0
- data/lib/plurimath/math/function/csch.rb +4 -0
- data/lib/plurimath/math/function/exp.rb +4 -0
- data/lib/plurimath/math/function/fenced.rb +4 -0
- data/lib/plurimath/math/function/floor.rb +4 -0
- data/lib/plurimath/math/function/frac.rb +7 -0
- data/lib/plurimath/math/function/gcd.rb +9 -0
- data/lib/plurimath/math/function/lcm.rb +9 -0
- data/lib/plurimath/math/function/lg.rb +4 -0
- data/lib/plurimath/math/function/ln.rb +4 -0
- data/lib/plurimath/math/function/log.rb +19 -0
- data/lib/plurimath/math/function/max.rb +4 -0
- data/lib/plurimath/math/function/min.rb +4 -0
- data/lib/plurimath/math/function/mod.rb +15 -0
- data/lib/plurimath/math/function/power.rb +10 -0
- data/lib/plurimath/math/function/prod.rb +10 -0
- data/lib/plurimath/math/function/root.rb +7 -0
- data/lib/plurimath/math/function/sec.rb +4 -0
- data/lib/plurimath/math/function/sech.rb +4 -0
- data/lib/plurimath/math/function/sin.rb +4 -0
- data/lib/plurimath/math/function/sinh.rb +4 -0
- data/lib/plurimath/math/function/sqrt.rb +4 -0
- data/lib/plurimath/math/function/sum.rb +10 -0
- data/lib/plurimath/math/function/tan.rb +4 -0
- data/lib/plurimath/math/function/tanh.rb +4 -0
- data/lib/plurimath/math/function/text.rb +17 -0
- data/lib/plurimath/math/number.rb +40 -29
- data/lib/plurimath/math/symbols/cdot.rb +4 -0
- data/lib/plurimath/math/symbols/div.rb +4 -0
- data/lib/plurimath/math/symbols/hat.rb +4 -0
- data/lib/plurimath/math/symbols/minus.rb +4 -0
- data/lib/plurimath/math/symbols/pi.rb +5 -1
- data/lib/plurimath/math/symbols/plus.rb +4 -0
- data/lib/plurimath/math/symbols/slash.rb +4 -0
- data/lib/plurimath/math/symbols/symbol.rb +45 -0
- data/lib/plurimath/math/symbols/times.rb +4 -0
- data/lib/plurimath/math.rb +1 -0
- data/lib/plurimath/mathml/constants.rb +18 -0
- data/lib/plurimath/number_formatter.rb +47 -28
- data/lib/plurimath/setup/opal.rb.erb +13 -0
- data/lib/plurimath/unicode_math/parse.rb +5 -1
- data/lib/plurimath/unicode_math/transform.rb +469 -755
- data/lib/plurimath/utility.rb +1 -1
- data/lib/plurimath/version.rb +1 -1
- data/lib/plurimath.rb +1 -0
- metadata +21 -3
- data/lib/plurimath/formatter/numbers/parts_renderer.rb +0 -30
data/README.adoc
CHANGED
|
@@ -286,6 +286,83 @@ formula = Plurimath::Math.parse(omml, :omml)
|
|
|
286
286
|
----
|
|
287
287
|
|
|
288
288
|
|
|
289
|
+
=== Evaluating formulas
|
|
290
|
+
|
|
291
|
+
`Plurimath::Math::Formula#evaluate` computes numeric results from supported
|
|
292
|
+
semantic formula trees.
|
|
293
|
+
|
|
294
|
+
[source,ruby]
|
|
295
|
+
----
|
|
296
|
+
formula = Plurimath::Math.parse("sqrt(a^2+b^2)", :asciimath)
|
|
297
|
+
formula.evaluate(a: 3, b: 4)
|
|
298
|
+
# => 5.0
|
|
299
|
+
----
|
|
300
|
+
|
|
301
|
+
Variable bindings can use string or symbol keys.
|
|
302
|
+
|
|
303
|
+
[source,ruby]
|
|
304
|
+
----
|
|
305
|
+
formula = Plurimath::Math.parse("a+b-c*d", :asciimath)
|
|
306
|
+
formula.evaluate("a" => 10, b: 5, c: 2, d: 3)
|
|
307
|
+
# => 9
|
|
308
|
+
----
|
|
309
|
+
|
|
310
|
+
Evaluation supports numeric literals, variables, addition, subtraction,
|
|
311
|
+
multiplication, division, fractions, powers, grouping, square roots, `pi`,
|
|
312
|
+
roots, absolute values, floor/ceiling, `sin`, `cos`, `tan`, inverse trig,
|
|
313
|
+
reciprocal trig, hyperbolic trig, `ln`, and `exp`. Trigonometric functions use
|
|
314
|
+
radians.
|
|
315
|
+
|
|
316
|
+
Logarithms evaluate with `ln`, `lg`, and `log` — `log` defaults to base 10,
|
|
317
|
+
`log_b` uses the subscript base (which must be positive and not 1), and a
|
|
318
|
+
superscript such as `log_2^3(8)` raises the result to that power. `mod`,
|
|
319
|
+
`max`, `min`, `gcd`, and `lcm` are supported (`gcd`/`lcm` require integer
|
|
320
|
+
arguments), as are bounded `sum` and `prod` with integer bounds — empty
|
|
321
|
+
ranges follow the standard conventions of `0` for sums and `1` for products,
|
|
322
|
+
and the iteration index shadows any outer binding of the same name. Ranges
|
|
323
|
+
are capped at `Plurimath.configuration.evaluation_max_iterations` (default
|
|
324
|
+
1,000,000) to guard against runaway loops; set it (globally via
|
|
325
|
+
`Plurimath.configure` or scoped via `Plurimath.with_configuration`) to a
|
|
326
|
+
larger value, or to `nil` to remove the cap.
|
|
327
|
+
|
|
328
|
+
`pi` resolves to the constant from each syntax's pi symbol — `pi` in
|
|
329
|
+
AsciiMath, `\pi` in LaTeX, and the `π` symbol (or `π` entity) in the
|
|
330
|
+
other syntaxes. Plain-text `pi` is treated per syntax: a single bindable
|
|
331
|
+
variable in MathML (`<mi>pi</mi>`) and OMML text runs, but individual
|
|
332
|
+
identifiers (`p*i`) in LaTeX, UnicodeMath, and HTML, which tokenize letters
|
|
333
|
+
one by one.
|
|
334
|
+
|
|
335
|
+
Implicit multiplication by juxtaposition is supported for adjacent operands,
|
|
336
|
+
so `2a`, `2(a+b)`, `(a+b)(a-b)`, and `pi r^2` multiply. Two adjacent numeric
|
|
337
|
+
literals such as `2 3` raise instead, because that shape usually indicates a
|
|
338
|
+
split number literal.
|
|
339
|
+
|
|
340
|
+
Chained powers follow the parsed tree structure, so `2^3^2` evaluates as
|
|
341
|
+
`(2^3)^2`; use explicit grouping such as `2^(3^2)` for right association.
|
|
342
|
+
Division uses `Float` arithmetic, and pole detection is exact: `cot(0)` raises
|
|
343
|
+
a division-by-zero error, while `sec(pi/2)` returns a large finite number
|
|
344
|
+
because the floating-point value of `pi/2` is not exactly the pole.
|
|
345
|
+
|
|
346
|
+
Binding values must be real `Numeric` values. A recognized `pi` constant
|
|
347
|
+
always resolves to its numeric value and cannot be overridden by a binding
|
|
348
|
+
such as `pi: 3`.
|
|
349
|
+
|
|
350
|
+
Binding keys must be strings or symbols; other key types raise
|
|
351
|
+
`Plurimath::Math::Evaluation::InvalidBindingKeyError`.
|
|
352
|
+
|
|
353
|
+
Missing variables raise
|
|
354
|
+
`Plurimath::Math::Evaluation::MissingVariableError`. Binding values that are
|
|
355
|
+
not real numbers raise `Plurimath::Math::Evaluation::InvalidBindingError`.
|
|
356
|
+
Division by zero raises `Plurimath::Math::Evaluation::DivisionByZeroError`.
|
|
357
|
+
Math domain violations such as `ln(-1)`, including results that are not real
|
|
358
|
+
numbers, raise `Plurimath::Math::Evaluation::MathDomainError`. Evaluations
|
|
359
|
+
that do not produce a finite number, such as `ln(0)`, raise
|
|
360
|
+
`Plurimath::Math::Evaluation::NonFiniteResultError`. Unsupported expressions,
|
|
361
|
+
including equations that would require solving, raise
|
|
362
|
+
`Plurimath::Math::Evaluation::UnsupportedExpressionError`. All evaluation
|
|
363
|
+
errors subclass `Plurimath::Math::Evaluation::Error`.
|
|
364
|
+
|
|
365
|
+
|
|
289
366
|
=== Converting to other formats
|
|
290
367
|
|
|
291
368
|
Once you have a `Plurimath::Math::Formula` object, you can convert it to
|
|
@@ -811,6 +888,205 @@ The decimal separator is rendered as configured by the `decimal` option.
|
|
|
811
888
|
NOTE: The fractional-part conversion treats the digits after the decimal point as a fractional value (for example, `"10.75"` is interpreted
|
|
812
889
|
as decimal `0.75` for the fractional part) and converts that real value to the requested base, honoring the configured precision.
|
|
813
890
|
|
|
891
|
+
|
|
892
|
+
===== Semantic base notation rendering
|
|
893
|
+
|
|
894
|
+
When numbers are formatted with a non-decimal `base`, Plurimath produces
|
|
895
|
+
*structured output* in each representation language rather than flat
|
|
896
|
+
prefix/postfix strings. The base is rendered as a subscript, making the
|
|
897
|
+
output semantically rich and accessible to screen readers and other tools.
|
|
898
|
+
|
|
899
|
+
For plain-text uses (`localized_number`), prefix/postfix notation is still
|
|
900
|
+
available through the `base_prefix` and `base_postfix` options.
|
|
901
|
+
|
|
902
|
+
.Semantic rendering by format
|
|
903
|
+
[example]
|
|
904
|
+
====
|
|
905
|
+
[source,ruby]
|
|
906
|
+
----
|
|
907
|
+
formatter = Plurimath::Formatter::Standard.new(options: { base: 16 })
|
|
908
|
+
Plurimath.configuration.number_formatter = formatter
|
|
909
|
+
|
|
910
|
+
formula = Plurimath::Math.parse("255", :asciimath)
|
|
911
|
+
|
|
912
|
+
formula.to_asciimath
|
|
913
|
+
# => "ff_(16)"
|
|
914
|
+
|
|
915
|
+
formula.to_latex
|
|
916
|
+
# => "\\mathrm{ff}_{16}"
|
|
917
|
+
|
|
918
|
+
formula.to_html
|
|
919
|
+
# => "ff<sub>16</sub>"
|
|
920
|
+
|
|
921
|
+
formula.to_unicodemath
|
|
922
|
+
# => "ff_(16)"
|
|
923
|
+
----
|
|
924
|
+
|
|
925
|
+
MathML produces a structured `<msub>` element:
|
|
926
|
+
|
|
927
|
+
[source,xml]
|
|
928
|
+
----
|
|
929
|
+
<msub><mn>ff</mn><mn>16</mn></msub>
|
|
930
|
+
----
|
|
931
|
+
|
|
932
|
+
OMML produces a structured `<m:sSub>` element:
|
|
933
|
+
|
|
934
|
+
[source,xml]
|
|
935
|
+
----
|
|
936
|
+
<m:sSub>
|
|
937
|
+
<m:sSubPr>...</m:sSubPr>
|
|
938
|
+
<m:e><m:r><m:t>ff</m:t></m:r></m:e>
|
|
939
|
+
<m:sub><m:r><m:t>16</m:t></m:r></m:sub>
|
|
940
|
+
</m:sSub>
|
|
941
|
+
----
|
|
942
|
+
====
|
|
943
|
+
|
|
944
|
+
Negative numbers wrap the sign around the subscript structure:
|
|
945
|
+
|
|
946
|
+
[source,ruby]
|
|
947
|
+
----
|
|
948
|
+
formula = Plurimath::Math.parse("-255", :asciimath)
|
|
949
|
+
formula.to_latex
|
|
950
|
+
# => "-\\mathrm{ff}_{16}"
|
|
951
|
+
----
|
|
952
|
+
|
|
953
|
+
[source,xml]
|
|
954
|
+
----
|
|
955
|
+
<mrow><mo>-</mo><msub><mn>ff</mn><mn>16</mn></msub></mrow>
|
|
956
|
+
====
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
===== Structured notation rendering
|
|
960
|
+
|
|
961
|
+
Scientific and engineering notation also produces structured output in MathML.
|
|
962
|
+
The `× 10^exp` portion is rendered as separate elements:
|
|
963
|
+
|
|
964
|
+
[source,ruby]
|
|
965
|
+
----
|
|
966
|
+
formatter = Plurimath::Formatter::Standard.new(
|
|
967
|
+
options: { notation: :scientific, exponent_sign: :plus },
|
|
968
|
+
)
|
|
969
|
+
Plurimath.configuration.number_formatter = formatter
|
|
970
|
+
|
|
971
|
+
formula = Plurimath::Math.parse("14000", :asciimath)
|
|
972
|
+
----
|
|
973
|
+
|
|
974
|
+
[source,xml]
|
|
975
|
+
----
|
|
976
|
+
<mrow>
|
|
977
|
+
<mn>1.4000</mn>
|
|
978
|
+
<mo>×</mo>
|
|
979
|
+
<msup><mn>10</mn><mn>+4</mn></msup>
|
|
980
|
+
</mrow>
|
|
981
|
+
----
|
|
982
|
+
|
|
983
|
+
E-notation remains as a flat `<mn>` element:
|
|
984
|
+
|
|
985
|
+
[source,xml]
|
|
986
|
+
----
|
|
987
|
+
<mn>1.4000E+4</mn>
|
|
988
|
+
====
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
===== Structured formatter API
|
|
992
|
+
|
|
993
|
+
`NumberFormatter#localized_number` continues to return a flat string for
|
|
994
|
+
backward compatibility. A new `NumberFormatter#formatted_number` method returns
|
|
995
|
+
a structured value object that carries the semantic parts of the formatted
|
|
996
|
+
number.
|
|
997
|
+
|
|
998
|
+
[source,ruby]
|
|
999
|
+
----
|
|
1000
|
+
formatter = Plurimath::NumberFormatter.new(:en)
|
|
1001
|
+
result = formatter.formatted_number("255", format: { base: 16 })
|
|
1002
|
+
|
|
1003
|
+
result # => FormattedNumber
|
|
1004
|
+
result.to_s # => "0xff"
|
|
1005
|
+
result.digits_string # => "ff"
|
|
1006
|
+
result.sign_text # => nil (no sign for positive numbers)
|
|
1007
|
+
result.base_notation.base # => 16
|
|
1008
|
+
result.negative? # => false
|
|
1009
|
+
result.fractional? # => false
|
|
1010
|
+
----
|
|
1011
|
+
|
|
1012
|
+
The `sign_text` accessor returns `"-"` for negative numbers, `"+"` when
|
|
1013
|
+
`number_sign: :plus` is set, and `nil` otherwise. Output renderers use it
|
|
1014
|
+
to produce format-specific sign elements.
|
|
1015
|
+
|
|
1016
|
+
For notation numbers, `formatted_number` returns a `FormattedNotation` value
|
|
1017
|
+
object:
|
|
1018
|
+
|
|
1019
|
+
[source,ruby]
|
|
1020
|
+
----
|
|
1021
|
+
result = formatter.formatted_number("14000", format: { notation: :scientific })
|
|
1022
|
+
|
|
1023
|
+
result # => FormattedNotation
|
|
1024
|
+
result.to_s # => "1.4000 × 10^4"
|
|
1025
|
+
result.coefficient # => FormattedNumber
|
|
1026
|
+
result.coefficient.to_s # => "1.4000"
|
|
1027
|
+
result.exponent # => 4
|
|
1028
|
+
result.formatted_exponent # => "4" ("+4" when exponent_sign: :plus)
|
|
1029
|
+
result.notation_style # => :scientific
|
|
1030
|
+
----
|
|
1031
|
+
|
|
1032
|
+
These value objects are what Plurimath uses internally to produce the
|
|
1033
|
+
structured output shown above.
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
===== Storing base on Math::Number
|
|
1037
|
+
|
|
1038
|
+
`Plurimath::Math::Number` now accepts a `base:` keyword, storing the radix
|
|
1039
|
+
as a semantic attribute. When present, the stored base is used as the default
|
|
1040
|
+
for formatting — no explicit `base:` option is needed in the format hash.
|
|
1041
|
+
|
|
1042
|
+
[source,ruby]
|
|
1043
|
+
----
|
|
1044
|
+
number = Plurimath::Math::Number.new("FF", base: 16)
|
|
1045
|
+
# When rendered with a configured formatter, the stored base (16) is used:
|
|
1046
|
+
number.to_asciimath(options: {})
|
|
1047
|
+
# => "ff_(16)"
|
|
1048
|
+
----
|
|
1049
|
+
|
|
1050
|
+
The stored base represents the *semantic truth* about the number and takes
|
|
1051
|
+
precedence over the formatter's built-in base configuration. An explicit
|
|
1052
|
+
`format: { base: N }` parameter still overrides everything.
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
===== Parse-time base prefix literals
|
|
1056
|
+
|
|
1057
|
+
All four input parsers (AsciiMath, LaTeX, UnicodeMath, HTML) recognize
|
|
1058
|
+
base-prefixed number literals and store the base automatically on the
|
|
1059
|
+
resulting `Math::Number` node — no manual `base:` keyword needed.
|
|
1060
|
+
|
|
1061
|
+
[cols="1,1,3", options="header"]
|
|
1062
|
+
|===
|
|
1063
|
+
|Prefix |Base |Example
|
|
1064
|
+
|
|
1065
|
+
|`0x` / `0X` |16 |`0xFF`, `0xBE EF`
|
|
1066
|
+
|`0b` / `0B` |2 |`0b1010`
|
|
1067
|
+
|`0o` / `0O` |8 |`0o17`
|
|
1068
|
+
|===
|
|
1069
|
+
|
|
1070
|
+
[source,ruby]
|
|
1071
|
+
----
|
|
1072
|
+
formula = Plurimath::Math.parse("0xFF", :asciimath)
|
|
1073
|
+
formula.value.first.value # => "FF"
|
|
1074
|
+
formula.value.first.base # => 16
|
|
1075
|
+
|
|
1076
|
+
# The stored base drives semantic rendering in every output format:
|
|
1077
|
+
formula.to_asciimath # => "ff_(16)"
|
|
1078
|
+
formula.to_latex # => "\mathrm{ff}_{16}"
|
|
1079
|
+
formula.to_html # => "ff<sub>16</sub>"
|
|
1080
|
+
----
|
|
1081
|
+
|
|
1082
|
+
NOTE: Hex literals store the original hex digits (`"FF"`) because letters
|
|
1083
|
+
are unambiguous. Binary and octal literals store the *decimal equivalent*
|
|
1084
|
+
(`0b1010` → value `"10"`) because their digit sets overlap with decimal.
|
|
1085
|
+
|
|
1086
|
+
The shared `Plurimath::BaseNumberPrefix` module defines these parser and
|
|
1087
|
+
transform rules once; every format parser includes it. Adding a new base
|
|
1088
|
+
prefix requires editing only `lib/plurimath/base_number_prefix.rb`.
|
|
1089
|
+
|
|
814
1090
|
===== Base conversion with other formatting options
|
|
815
1091
|
|
|
816
1092
|
Base conversion works seamlessly with other formatting options such as `precision`, `digit_count`,
|
|
@@ -1671,12 +1947,12 @@ end
|
|
|
1671
1947
|
|
|
1672
1948
|
Formula rendering uses the first formatter method available in this order:
|
|
1673
1949
|
`format_number(formula, number)`, then `format(formula, number)`, then
|
|
1674
|
-
`
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1950
|
+
`formatted_number(number.value.to_s)` (returns a structured value object),
|
|
1951
|
+
then `localized_number(number.value.to_s)` (returns a flat string). This
|
|
1952
|
+
keeps existing custom formatters working while giving new code access to the
|
|
1953
|
+
structured result through `formatted_number`. New custom formatters should
|
|
1954
|
+
define `format_number`; the `format` fallback exists for compatibility with
|
|
1955
|
+
existing custom formatters.
|
|
1680
1956
|
|
|
1681
1957
|
To implement contextual formatting, define a `format_number` method in a custom
|
|
1682
1958
|
formatter subclass.
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
class Asciimath
|
|
5
5
|
class Parse < Parslet::Parser
|
|
6
|
+
include Plurimath::BaseNumberPrefix::Parser
|
|
7
|
+
|
|
6
8
|
rule(:td) { expression.as(:td) }
|
|
7
9
|
rule(:base) { str("__|").absent? >> str("_") }
|
|
8
10
|
rule(:power) { str("^") }
|
|
@@ -10,7 +12,10 @@ module Plurimath
|
|
|
10
12
|
rule(:comma) { str(",") >> space? }
|
|
11
13
|
rule(:space?) { space.maybe }
|
|
12
14
|
rule(:number) do
|
|
13
|
-
|
|
15
|
+
hex_number |
|
|
16
|
+
binary_number |
|
|
17
|
+
octal_number |
|
|
18
|
+
(match("[0-9]").repeat(1) >> decimal_marker >> match("[0-9]").repeat(1)).as(:number) |
|
|
14
19
|
match("[0-9]").repeat(1).as(:number) |
|
|
15
20
|
str(".").as(:symbol)
|
|
16
21
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plurimath
|
|
4
|
+
# Shared base-prefix number literal rules (0x, 0b, 0o) for all Parslet
|
|
5
|
+
# parsers and transforms. Including the Parser sub-module adds hex/binary/
|
|
6
|
+
# octal parser atoms; including the Transform sub-module adds the
|
|
7
|
+
# corresponding Math::Number construction rules. Define base prefixes here
|
|
8
|
+
# once — every format parser picks them up automatically.
|
|
9
|
+
module BaseNumberPrefix
|
|
10
|
+
# Adds Parslet parser rules for hex/binary/octal prefixed literals.
|
|
11
|
+
# Include in any Parslet::Parser subclass.
|
|
12
|
+
module Parser
|
|
13
|
+
def self.included(base)
|
|
14
|
+
base.class_eval do
|
|
15
|
+
rule(:hex_number) do
|
|
16
|
+
(str("0x") | str("0X")) >> match["0-9a-fA-F"].repeat(1).as(:hex_number)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
rule(:binary_number) do
|
|
20
|
+
(str("0b") | str("0B")) >> match["01"].repeat(1).as(:binary_number)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
rule(:octal_number) do
|
|
24
|
+
(str("0o") | str("0O")) >> match["0-7"].repeat(1).as(:octal_number)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Adds Parslet transform rules that build Math::Number with the correct
|
|
31
|
+
# base attribute from hex/binary/octal parse tree nodes.
|
|
32
|
+
# Include in any Parslet::Transform subclass.
|
|
33
|
+
module Transform
|
|
34
|
+
def self.included(base)
|
|
35
|
+
base.class_eval do
|
|
36
|
+
rule(hex_number: simple(:hex)) { Math::Number.new(hex.to_s, base: 16) }
|
|
37
|
+
rule(binary_number: simple(:bin)) { Math::Number.new(bin.to_s.to_i(2).to_s, base: 2) }
|
|
38
|
+
rule(octal_number: simple(:oct)) { Math::Number.new(oct.to_s.to_i(8).to_s, base: 8) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -4,7 +4,15 @@ module Plurimath
|
|
|
4
4
|
class Configuration
|
|
5
5
|
DEFAULT_DECIMAL = "."
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# Cap on bounded sum/prod iterations, guarding the untrusted-document
|
|
8
|
+
# evaluation path against runaway loops. Set to nil to disable the cap.
|
|
9
|
+
DEFAULT_MAX_ITERATIONS = 100_000
|
|
10
|
+
|
|
11
|
+
attr_accessor :number_formatter, :locale, :evaluation_max_iterations
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@evaluation_max_iterations = DEFAULT_MAX_ITERATIONS
|
|
15
|
+
end
|
|
8
16
|
|
|
9
17
|
def deprecation
|
|
10
18
|
Deprecation
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plurimath
|
|
4
|
+
module Errors
|
|
5
|
+
module Evaluation
|
|
6
|
+
class InvalidBindingError < Error
|
|
7
|
+
def initialize(name, value)
|
|
8
|
+
super("wrong value for variable `#{name}` " \
|
|
9
|
+
"(given #{value.class}, expected a real number)")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plurimath
|
|
4
|
+
module Errors
|
|
5
|
+
module Evaluation
|
|
6
|
+
class InvalidBindingKeyError < Error
|
|
7
|
+
def initialize(key)
|
|
8
|
+
super("wrong type for binding key " \
|
|
9
|
+
"(given #{key.class}, expected String or Symbol)")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plurimath
|
|
4
|
+
module Errors
|
|
5
|
+
module Evaluation
|
|
6
|
+
autoload :Error, "#{__dir__}/evaluation/error"
|
|
7
|
+
autoload :DivisionByZeroError, "#{__dir__}/evaluation/division_by_zero_error"
|
|
8
|
+
autoload :InvalidBindingError, "#{__dir__}/evaluation/invalid_binding_error"
|
|
9
|
+
autoload :InvalidBindingKeyError,
|
|
10
|
+
"#{__dir__}/evaluation/invalid_binding_key_error"
|
|
11
|
+
autoload :MathDomainError, "#{__dir__}/evaluation/math_domain_error"
|
|
12
|
+
autoload :MissingVariableError, "#{__dir__}/evaluation/missing_variable_error"
|
|
13
|
+
autoload :NonFiniteResultError, "#{__dir__}/evaluation/non_finite_result_error"
|
|
14
|
+
autoload :UnsupportedExpressionError,
|
|
15
|
+
"#{__dir__}/evaluation/unsupported_expression_error"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/plurimath/errors.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
module Errors
|
|
5
|
+
autoload :Evaluation, "#{__dir__}/errors/evaluation"
|
|
5
6
|
autoload :InvalidNumber, "#{__dir__}/errors/invalid_number"
|
|
6
7
|
autoload :UnsupportedBase, "#{__dir__}/errors/unsupported_base"
|
|
7
8
|
autoload :UnsupportedLocale, "#{__dir__}/errors/unsupported_locale"
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
module Formatter
|
|
5
5
|
module Numbers
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# Describes base prefix/postfix notation as a resolved value object.
|
|
7
|
+
# Constructed from FormatOptions via .from_options; carries the resolved
|
|
8
|
+
# prefix and postfix strings so renderers can decide how to represent the
|
|
9
|
+
# base (inline text, MathML subscript, LaTeX subscript, etc.).
|
|
8
10
|
class BaseNotation
|
|
9
11
|
DEFAULT_PREFIXES = {
|
|
10
12
|
2 => "0b",
|
|
@@ -13,59 +15,80 @@ module Plurimath
|
|
|
13
15
|
16 => "0x",
|
|
14
16
|
}.freeze
|
|
15
17
|
|
|
16
|
-
attr_reader :base
|
|
18
|
+
attr_reader :base, :prefix, :postfix, :hex_capital
|
|
17
19
|
|
|
18
|
-
def initialize(
|
|
19
|
-
|
|
20
|
-
@base =
|
|
21
|
-
|
|
20
|
+
def initialize(base:, prefix: "", postfix: "", hex_capital: nil,
|
|
21
|
+
explicit_prefix: false, explicit_postfix: false)
|
|
22
|
+
@base = base
|
|
23
|
+
@prefix = prefix
|
|
24
|
+
@postfix = postfix
|
|
25
|
+
@hex_capital = hex_capital
|
|
26
|
+
@explicit_prefix = explicit_prefix
|
|
27
|
+
@explicit_postfix = explicit_postfix
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Base::HEX_DIGITS.upcase)
|
|
28
|
-
else
|
|
29
|
-
string
|
|
30
|
-
end
|
|
31
|
-
return rendered if default?
|
|
30
|
+
def self.from_options(options)
|
|
31
|
+
base = options.base
|
|
32
|
+
validate!(base)
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
new(
|
|
35
|
+
base: base,
|
|
36
|
+
prefix: resolve_prefix(options, base),
|
|
37
|
+
postfix: options.base_postfix.to_s,
|
|
38
|
+
hex_capital: options.hex_capital,
|
|
39
|
+
explicit_prefix: options.base_prefix?,
|
|
40
|
+
explicit_postfix: options.base_postfix?,
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.supported?(base)
|
|
45
|
+
DEFAULT_PREFIXES.key?(base)
|
|
34
46
|
end
|
|
35
47
|
|
|
36
48
|
def default?
|
|
37
49
|
base == Base::DEFAULT_BASE
|
|
38
50
|
end
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
# Caller overrode prefix or postfix at format-call time. Renderers that
|
|
53
|
+
# honor semantic base notation (msub, \mathrm{}_{base}, _(base)) must
|
|
54
|
+
# defer to the literal prefix/postfix in that case.
|
|
55
|
+
def literal?
|
|
56
|
+
!default? && (explicit_prefix? || explicit_postfix?)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def semantic?
|
|
60
|
+
!default? && !literal?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def upcase_hex?
|
|
64
|
+
base == 16 && hex_capital == true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def wrap(digits)
|
|
68
|
+
"#{prefix}#{digits}#{postfix}"
|
|
42
69
|
end
|
|
43
70
|
|
|
44
71
|
private
|
|
45
72
|
|
|
46
|
-
attr_reader :
|
|
73
|
+
attr_reader :explicit_prefix, :explicit_postfix
|
|
47
74
|
|
|
48
|
-
|
|
75
|
+
alias explicit_prefix? explicit_prefix
|
|
76
|
+
alias explicit_postfix? explicit_postfix
|
|
77
|
+
|
|
78
|
+
def self.resolve_prefix(options, base)
|
|
49
79
|
return options.base_prefix if options.base_prefix?
|
|
50
|
-
# A postfix without an explicit prefix opts out of the default prefix.
|
|
51
80
|
return "" if options.base_postfix?
|
|
52
81
|
|
|
53
82
|
DEFAULT_PREFIXES[base]
|
|
54
83
|
end
|
|
84
|
+
private_class_method :resolve_prefix
|
|
55
85
|
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def upcase_hex?
|
|
61
|
-
base == 16 && options.hex_capital == true
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def validate_base!
|
|
65
|
-
return if self.class.supported?(base)
|
|
86
|
+
def self.validate!(base)
|
|
87
|
+
return if supported?(base)
|
|
66
88
|
|
|
67
89
|
raise Plurimath::Errors::UnsupportedBase.new(base, DEFAULT_PREFIXES)
|
|
68
90
|
end
|
|
91
|
+
private_class_method :validate!
|
|
69
92
|
end
|
|
70
93
|
end
|
|
71
94
|
end
|