plurimath 0.11.0 → 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 +311 -58
- data/Gemfile +3 -2
- data/README.adoc +331 -15
- 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/deprecation.rb +2 -1
- data/lib/plurimath/errors/configuration_error.rb +5 -1
- data/lib/plurimath/errors/deprecation_error.rb +2 -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/invalid_number.rb +17 -0
- data/lib/plurimath/errors/unsupported_base.rb +18 -0
- data/lib/plurimath/errors/{formatter/unsupported_locale.rb → unsupported_locale.rb} +1 -1
- data/lib/plurimath/errors.rb +9 -0
- data/lib/plurimath/formatter/numbers/base_notation.rb +55 -27
- data/lib/plurimath/formatter/numbers/format_options.rb +123 -17
- 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 +8 -17
- data/lib/plurimath/formatter/numbers/integer.rb +3 -0
- data/lib/plurimath/formatter/numbers/mathml_renderer.rb +56 -0
- data/lib/plurimath/formatter/numbers/notation_renderer.rb +58 -28
- data/lib/plurimath/formatter/numbers/number_renderer.rb +26 -13
- data/lib/plurimath/formatter/numbers/omml_renderer.rb +74 -0
- data/lib/plurimath/formatter/numbers/precision_resolver.rb +16 -3
- data/lib/plurimath/formatter/numbers/sign_renderer.rb +2 -1
- data/lib/plurimath/formatter/numbers/significant.rb +4 -2
- data/lib/plurimath/formatter/numbers/source.rb +46 -6
- data/lib/plurimath/formatter/numbers/text_renderer.rb +52 -0
- data/lib/plurimath/formatter/numbers.rb +6 -2
- data/lib/plurimath/formatter/standard.rb +6 -0
- data/lib/plurimath/formatter/supported_locales.rb +1 -1
- data/lib/plurimath/formatter.rb +0 -2
- data/lib/plurimath/html/parse.rb +5 -0
- data/lib/plurimath/html/transform.rb +26 -12
- data/lib/plurimath/latex/parse.rb +5 -0
- data/lib/plurimath/latex/transform.rb +2 -0
- data/lib/plurimath/math/core.rb +69 -14
- 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/overleftrightarrow.rb +2 -2
- 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 +44 -23
- 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 +5 -1
- data/lib/plurimath/mathml/constants.rb +18 -0
- data/lib/plurimath/number_formatter.rb +63 -3
- data/lib/plurimath/omml/formula_transformation.rb +2 -1
- data/lib/plurimath/omml/translator.rb +4 -1
- data/lib/plurimath/setup/opal.rb.erb +13 -0
- data/lib/plurimath/unicode_math/parse.rb +98 -54
- data/lib/plurimath/unicode_math/parser.rb +7 -4
- data/lib/plurimath/unicode_math/parsing_rules/absence_rules.rb +33 -21
- data/lib/plurimath/unicode_math/parsing_rules/common_rules.rb +28 -16
- data/lib/plurimath/unicode_math/parsing_rules/constants_rules.rb +18 -7
- data/lib/plurimath/unicode_math/parsing_rules/masked.rb +35 -15
- data/lib/plurimath/unicode_math/parsing_rules/sub_sup.rb +98 -72
- data/lib/plurimath/unicode_math/transform.rb +1 -0
- data/lib/plurimath/utility.rb +1 -1
- data/lib/plurimath/version.rb +1 -1
- data/lib/plurimath.rb +2 -0
- metadata +24 -5
- data/lib/plurimath/errors/formatter/unsupported_base.rb +0 -21
- 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
|
|
@@ -513,7 +590,7 @@ formatter = Plurimath::NumberFormatter.new(
|
|
|
513
590
|
<1> Locale to be used for number formatting.
|
|
514
591
|
<2> String pattern to define the number formatting.
|
|
515
592
|
<3> Hash containing relevant options for number formatting.
|
|
516
|
-
<4> Number of decimal places to
|
|
593
|
+
<4> Number of decimal places to keep; the fraction is truncated, not rounded.
|
|
517
594
|
|
|
518
595
|
Where,
|
|
519
596
|
|
|
@@ -534,7 +611,8 @@ options for number formatting. Use either `localize_number` or
|
|
|
534
611
|
See <<localizer_symbols,format options hash>> for details.
|
|
535
612
|
|
|
536
613
|
`precision: <precision-number>`:: (optional, default `nil`)
|
|
537
|
-
Number of decimal places to
|
|
614
|
+
Number of decimal places to keep; the fraction is truncated, not
|
|
615
|
+
rounded. Accepts an integer value.
|
|
538
616
|
+
|
|
539
617
|
.Specifying a precision of 6 digits
|
|
540
618
|
[example]
|
|
@@ -571,6 +649,28 @@ through a Hash object containing specified options.
|
|
|
571
649
|
|
|
572
650
|
This Hash object is called the "format options Hash".
|
|
573
651
|
|
|
652
|
+
Option values follow a common handling policy:
|
|
653
|
+
|
|
654
|
+
* Count options (`precision`, `significant`, `digit_count`, `group_digits`,
|
|
655
|
+
`fraction_group_digits`, `padding_digits`, `padding_group_digits`) accept an
|
|
656
|
+
`Integer` or its `String`/`Symbol` numeric equivalent (`"4"`). Any other
|
|
657
|
+
value, including negative numbers, raises `Plurimath::ConfigurationError`.
|
|
658
|
+
* Separator options (`decimal`, `group`, `fraction_group`, `base_prefix`,
|
|
659
|
+
`base_postfix`) accept free-form strings. An explicitly passed `nil`
|
|
660
|
+
disables the separator — for `decimal` this renders the digits without a
|
|
661
|
+
decimal mark, and output correctness is then the caller's responsibility.
|
|
662
|
+
One exception: `group: nil` falls back to the default separator instead of
|
|
663
|
+
disabling (use `group: ""` to disable grouping). Boolean values raise
|
|
664
|
+
`Plurimath::ConfigurationError`.
|
|
665
|
+
* Enumerated options (`notation`, `number_sign`, `exponent_sign`, `e`,
|
|
666
|
+
`times`, `hex_capital`) accept their documented values as `Symbol` or
|
|
667
|
+
`String`; unrecognized `String`/`Symbol` values fall back to the default
|
|
668
|
+
behavior. Any other type raises `Plurimath::ConfigurationError` — including
|
|
669
|
+
Boolean, except for `hex_capital`, where `true`/`false` are documented values.
|
|
670
|
+
* The `locale` always falls back to `:en` for unsupported or wrong-typed
|
|
671
|
+
values; it never raises.
|
|
672
|
+
* Invalid number input raises `Plurimath::Errors::InvalidNumber`.
|
|
673
|
+
|
|
574
674
|
Available options are explained below.
|
|
575
675
|
|
|
576
676
|
NOTE: Each option takes an input of a certain specified type. Using an input
|
|
@@ -584,6 +684,15 @@ The same option keys can be passed through `localizer_symbols`, through the
|
|
|
584
684
|
per-call `format:` hash, or through `Plurimath::Formatter::Standard`'s
|
|
585
685
|
`options:` argument.
|
|
586
686
|
|
|
687
|
+
When rendering through `Plurimath::Math::Formula` serialization methods
|
|
688
|
+
(`to_asciimath`, `to_latex`, `to_mathml`, `to_unicodemath`, `to_html`),
|
|
689
|
+
per-element overrides can be supplied via `options[:format]`. The hash is
|
|
690
|
+
forwarded to `NumberFormatter#localized_number`'s `format:` keyword, taking
|
|
691
|
+
precedence over the configured formatter's symbols for that one element.
|
|
692
|
+
Custom formatters with a simpler `localized_number(value)` signature are
|
|
693
|
+
unaffected unless `options[:format]` is supplied, in which case their method
|
|
694
|
+
must accept the `format:` keyword.
|
|
695
|
+
|
|
587
696
|
|
|
588
697
|
`decimal`:: (`String` value)
|
|
589
698
|
Symbol to use for the decimal point. Accepts a character.
|
|
@@ -695,12 +804,12 @@ Missing integer digits are inserted before grouping using the configured
|
|
|
695
804
|
"32" with `padding_group_digits: 4, group: ""` => "0032"
|
|
696
805
|
"32123" with `padding_group_digits: 4, group: ""` => "00032123"
|
|
697
806
|
====
|
|
698
|
-
====
|
|
699
807
|
|
|
700
808
|
`base`:: (`Numeric` value)
|
|
701
809
|
Sets the numeric base (radix) used to render both the integer and fractional parts of the number.
|
|
702
810
|
Supported values are 2 (binary), 8 (octal), 10 (decimal, default) and 16 (hexadecimal).
|
|
703
|
-
|
|
811
|
+
The value is also accepted as its `String` or `Symbol` equivalent (`"16"`).
|
|
812
|
+
Passing any other value for `base` raises `Plurimath::Errors::UnsupportedBase`.
|
|
704
813
|
+
|
|
705
814
|
.Rendering numbers in different bases
|
|
706
815
|
[example]
|
|
@@ -752,6 +861,10 @@ uppercase letters. This includes both hexadecimal digits and any separators
|
|
|
752
861
|
Use `:numbers_only` to uppercase generated hexadecimal digits without changing
|
|
753
862
|
separator characters. It has no effect for other bases.
|
|
754
863
|
+
|
|
864
|
+
Both values are also accepted as their `String` or `Symbol` equivalents
|
|
865
|
+
(`"true"`, `:true`, `"numbers_only"`). Matching is case-sensitive; any other
|
|
866
|
+
value (including any form of `false`) disables uppercasing.
|
|
867
|
+
+
|
|
755
868
|
.Uppercase hexadecimal output
|
|
756
869
|
[example]
|
|
757
870
|
====
|
|
@@ -775,6 +888,205 @@ The decimal separator is rendered as configured by the `decimal` option.
|
|
|
775
888
|
NOTE: The fractional-part conversion treats the digits after the decimal point as a fractional value (for example, `"10.75"` is interpreted
|
|
776
889
|
as decimal `0.75` for the fractional part) and converts that real value to the requested base, honoring the configured precision.
|
|
777
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
|
+
|
|
778
1090
|
===== Base conversion with other formatting options
|
|
779
1091
|
|
|
780
1092
|
Base conversion works seamlessly with other formatting options such as `precision`, `digit_count`,
|
|
@@ -1027,8 +1339,11 @@ In this example it is `' '` (a space).
|
|
|
1027
1339
|
----
|
|
1028
1340
|
formatter = Plurimath::NumberFormatter.new(:en, localize_number: "#,##0.### ###")
|
|
1029
1341
|
formatter.localized_number("1234.56789")
|
|
1030
|
-
# => "1,234.
|
|
1342
|
+
# => "1,234.567 89"
|
|
1031
1343
|
----
|
|
1344
|
+
|
|
1345
|
+
NOTE: A space used as a group or fraction-group delimiter in the
|
|
1346
|
+
`localize_number` pattern is emitted as U+00A0 NO-BREAK SPACE in the output.
|
|
1032
1347
|
====
|
|
1033
1348
|
|
|
1034
1349
|
|
|
@@ -1052,7 +1367,7 @@ formatter.localized_number(
|
|
|
1052
1367
|
----
|
|
1053
1368
|
<1> The number to be formatted.
|
|
1054
1369
|
<2> The locale to be used for number formatting.
|
|
1055
|
-
<3> The number of decimal places to
|
|
1370
|
+
<3> The number of decimal places to keep; the fraction is truncated, not rounded.
|
|
1056
1371
|
<4> Hash containing the relevant options for number formatting.
|
|
1057
1372
|
|
|
1058
1373
|
Where,
|
|
@@ -1065,8 +1380,8 @@ Value is a symbol.
|
|
|
1065
1380
|
Overrides the locale set during the creation of the `NumberFormatter` object. If
|
|
1066
1381
|
not provided, the locale of the `NumberFormatter` instance will be used.
|
|
1067
1382
|
|
|
1068
|
-
`precision: <precision-number>`:: (optional) The number of decimal places to
|
|
1069
|
-
|
|
1383
|
+
`precision: <precision-number>`:: (optional) The number of decimal places to keep
|
|
1384
|
+
(the fraction is truncated, not rounded). If not provided, the precision of the `NumberFormatter` instance will
|
|
1070
1385
|
be used.
|
|
1071
1386
|
|
|
1072
1387
|
`format: <format-hash>`:: (optional, default `{}`) A Hash containing the relevant
|
|
@@ -1075,7 +1390,8 @@ configuration of the `NumberFormatter`.
|
|
|
1075
1390
|
Takes a Hash in the form of the <<localizer_symbols,format options hash>>.
|
|
1076
1391
|
|
|
1077
1392
|
`precision: <precision-number>`::
|
|
1078
|
-
Number of decimal places to
|
|
1393
|
+
Number of decimal places to keep; the fraction is truncated, not
|
|
1394
|
+
rounded. Accepts an integer value.
|
|
1079
1395
|
+
|
|
1080
1396
|
.Specifying a precision of 6 digits
|
|
1081
1397
|
[example]
|
|
@@ -1631,12 +1947,12 @@ end
|
|
|
1631
1947
|
|
|
1632
1948
|
Formula rendering uses the first formatter method available in this order:
|
|
1633
1949
|
`format_number(formula, number)`, then `format(formula, number)`, then
|
|
1634
|
-
`
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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.
|
|
1640
1956
|
|
|
1641
1957
|
To implement contextual formatting, define a `format_number` method in a custom
|
|
1642
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
|
|
@@ -6,7 +6,8 @@ module Plurimath
|
|
|
6
6
|
DEFAULT_BEHAVIOR = :collect
|
|
7
7
|
|
|
8
8
|
class << self
|
|
9
|
-
def warn(feature:, message: nil, replacement: nil, since: nil,
|
|
9
|
+
def warn(feature:, message: nil, replacement: nil, since: nil,
|
|
10
|
+
remove_in: nil)
|
|
10
11
|
feature = validate_feature(feature)
|
|
11
12
|
return if behavior == :collect && emitted_features[feature]
|
|
12
13
|
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
class ConfigurationError < Error
|
|
5
|
-
def initialize(type, value: nil, supported: nil)
|
|
5
|
+
def initialize(type, value: nil, supported: nil, option: nil)
|
|
6
6
|
@type = type
|
|
7
7
|
@value = value
|
|
8
8
|
@supported = supported
|
|
9
|
+
@option = option
|
|
9
10
|
super(message)
|
|
10
11
|
end
|
|
11
12
|
|
|
@@ -19,6 +20,9 @@ module Plurimath
|
|
|
19
20
|
when :conflicting_formatter_options
|
|
20
21
|
"formatter options cannot be used together: choose either " \
|
|
21
22
|
":padding_digits or :padding_group_digits"
|
|
23
|
+
when :invalid_formatter_option
|
|
24
|
+
"invalid value #{@value.inspect} for formatter option " \
|
|
25
|
+
"#{@option.inspect}#{" (expected #{@supported})" if @supported}"
|
|
22
26
|
else
|
|
23
27
|
"invalid Plurimath configuration"
|
|
24
28
|
end
|
|
@@ -6,7 +6,8 @@ module Plurimath
|
|
|
6
6
|
|
|
7
7
|
attr_reader :feature, :replacement, :since, :remove_in
|
|
8
8
|
|
|
9
|
-
def initialize(feature:, message: nil, replacement: nil, since: nil,
|
|
9
|
+
def initialize(feature:, message: nil, replacement: nil, since: nil,
|
|
10
|
+
remove_in: nil)
|
|
10
11
|
@feature = feature
|
|
11
12
|
@replacement = replacement
|
|
12
13
|
@since = since
|
|
@@ -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
|