umath 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 29cd6743561f1b47beec9dbd77d4afa5cad6b109b8ca1be39f48d7a24d0f5dad
4
+ data.tar.gz: 3d92b72dbb72d885024387869ac7db8be1f4adcb7e0a6397c264245fd02128b6
5
+ SHA512:
6
+ metadata.gz: f4e0eb27a7ee0f12455eff207ef4a3d3e58b136786053c6cf633dfcb4767ef58ce4d6fd8ede5f6eb3931850afe2116e709415377be73aca98f395cbfb1d39126
7
+ data.tar.gz: cdfd7bde07ce5f5a73883ae6daa5bba759dd0d69f303f6447f7e25ec181ad89c3f6bc5c3c60e00ef48c2be59e90632dd04ceeafa101810482266a3d7522aeab8
data/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release. Umath is the UnicodeMath parser split out of
6
+ Plurimath, as planned in plurimath/plurimath#530 and plurimath/plurimath#541.
7
+
8
+ - `Umath.parse(input, decimal: ".", engine: :ruby)` returns a
9
+ `Umath::Model::Document`, a typed model of the formula declared with
10
+ LutaML Model. Symbols are `Umath::Model::MathSymbol` objects with
11
+ their spelling, Unicode value and kind, read from the symbol catalog in
12
+ `data/umath-symbols.json`.
13
+ - Invalid input raises `Umath::ParseError` with the position in the
14
+ caller's input (`char_offset`, `byte_offset`). Every error Umath
15
+ raises on purpose is a `Umath::Error`.
16
+ - The grammar is Plurimath's UnicodeMath grammar, compiled with Parsanol
17
+ into a checksummed artifact (`artifacts/umath.json`). It runs on
18
+ Parsanol's Ruby engine by default. Parsanol's native engine is an opt-in
19
+ with `engine: :native` (or `engine: :auto`, which uses native where it is
20
+ available): for 18 inputs of the testsuite's UnicodeMath fixtures it gives
21
+ a different tree from the Ruby engine and legacy Plurimath; see the
22
+ README's section on engines.
23
+ - CI replays the unicode generation of plurimath-testsuite pinned in
24
+ `spec/fixtures/testsuite-pin.yml`, and compares the parse tree with the
25
+ pinned Plurimath commit for every UnicodeMath case of the testsuite.
26
+ - Requires Ruby 3.2 or later. The runtime dependencies are Parsanol and
27
+ LutaML Model.
data/LICENSE.md ADDED
@@ -0,0 +1,33 @@
1
+ Licenses & Copyright
2
+ ====================
3
+
4
+ This license file adheres to the formatting guidelines of
5
+ [readable-licenses](https://github.com/nevir/readable-licenses).
6
+
7
+
8
+ Ribose's BSD 2-Clause License
9
+ -----------------------------
10
+
11
+ Copyright (c) 2026, [Ribose Inc](https://www.ribose.com).
12
+ All rights reserved.
13
+
14
+ Redistribution and use in source and binary forms, with or without modification,
15
+ are permitted provided that the following conditions are met:
16
+
17
+ 1. Redistributions of source code must retain the above copyright notice,
18
+ this list of conditions and the following disclaimer.
19
+
20
+ 2. Redistributions in binary form must reproduce the above copyright notice,
21
+ this list of conditions and the following disclaimer in the documentation
22
+ and/or other materials provided with the distribution.
23
+
24
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
25
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
28
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.adoc ADDED
@@ -0,0 +1,155 @@
1
+ = Umath: UnicodeMath parser for Ruby
2
+
3
+ image:https://img.shields.io/gem/v/umath.svg["Gem Version", link="https://rubygems.org/gems/umath"]
4
+ image:https://github.com/plurimath/umath/actions/workflows/test.yml/badge.svg["Build Status", link="https://github.com/plurimath/umath/actions/workflows/test.yml"]
5
+
6
+ == Purpose
7
+
8
+ Umath parses https://www.unicode.org/notes/tn28/[UnicodeMath], the
9
+ linear format for mathematics described in Unicode Technical Note 28, into a
10
+ typed model of the formula. It reads the UnicodeMath that
11
+ https://github.com/plurimath/plurimath[Plurimath] reads, with the same
12
+ grammar, and is built for Plurimath to use for UnicodeMath input
13
+ (https://github.com/plurimath/plurimath/issues/530[plurimath/plurimath#530]).
14
+
15
+ The model records the structure of the formula: which symbols, fractions,
16
+ scripts, roots, matrices and brackets it contains, and how they nest. Your
17
+ code decides what to do with it, such as rendering, converting or analysing
18
+ it. Umath does not write MathML or any other output format; Plurimath
19
+ does that.
20
+
21
+ == Installation
22
+
23
+ Add it to your `Gemfile`:
24
+
25
+ [source,ruby]
26
+ ----
27
+ gem "umath", "~> 0.1.0"
28
+ ----
29
+
30
+ Umath needs Ruby 3.2 or later. Its runtime dependencies are
31
+ https://github.com/parsanol/parsanol-ruby[Parsanol] and
32
+ https://github.com/lutaml/lutaml-model[LutaML Model]. It does not load
33
+ Plurimath.
34
+
35
+ == Usage
36
+
37
+ [source,ruby]
38
+ ----
39
+ require "umath"
40
+
41
+ doc = Umath.parse("a/b")
42
+ frac = doc.expression.frac
43
+ frac.numerator.atom.alphanumeric.spelling # => "a"
44
+ doc.to_hash
45
+ # => {"expression" => {"frac" => {"denominator" => {...}, "numerator" => {...}}}, "decimal" => "."}
46
+ ----
47
+
48
+ `Umath.parse` returns a `Umath::Model::Document`. Its
49
+ `expression` holds the formula and `decimal` the decimal marker it was parsed
50
+ with. Input with no formula, such as an empty string, gives a document whose
51
+ `expression` is nil.
52
+
53
+ The model classes are plain LutaML Model classes, generated from the grammar:
54
+ each named part of the grammar is a class with one attribute per part it can
55
+ contain. Parts that can hold a whole expression, such as a numerator or a
56
+ subscript, share the class `Umath::Model::Expression`. Symbols are
57
+ `Umath::Model::MathSymbol` objects with the text the grammar matched
58
+ (`spelling`), its Unicode text (`value`) and its kind:
59
+
60
+ [source,ruby]
61
+ ----
62
+ symbol = Umath.parse("α").expression.factor.ordinary_symbols
63
+ symbol.spelling # => "α"
64
+ symbol.value # => "α"
65
+ symbol.kind # => "symbol"
66
+ ----
67
+
68
+ The spelling is the text after the input has been rewritten as Plurimath
69
+ does (non-ASCII characters become HTML entities), so `value` is usually the
70
+ field you want.
71
+
72
+ Numbers keep their digits and decimal marker as written. A dot is the
73
+ decimal marker unless you say otherwise; a comma and the Arabic decimal
74
+ separator are also supported:
75
+
76
+ [source,ruby]
77
+ ----
78
+ Umath.parse("1,5", decimal: ",")
79
+ Umath.parse("1٫5", decimal: "٫")
80
+ ----
81
+
82
+ The rules for each model class are in link:docs/model-contract.adoc[the
83
+ model contract], and the accepted syntax and the preprocessing of the input
84
+ in link:docs/parser-contract.adoc[the parser contract].
85
+
86
+ == Errors
87
+
88
+ Input that is not valid UnicodeMath raises `Umath::ParseError`, which
89
+ tells you where the parser gave up, as a position in the text you passed:
90
+
91
+ [source,ruby]
92
+ ----
93
+ begin
94
+ Umath.parse("α)")
95
+ rescue Umath::ParseError => e
96
+ e.char_offset # => 1, position in characters
97
+ e.byte_offset # => 2, position in UTF-8 bytes
98
+ end
99
+ ----
100
+
101
+ Every error Umath raises on purpose is a `Umath::Error`, except
102
+ `ArgumentError` for an invalid argument, such as input that is not a valid
103
+ UTF-8 string, an unknown engine or an unsupported decimal marker.
104
+ `Umath::NestingTooDeep`, a kind of `ParseError` with no offsets, means
105
+ the engine could not handle how deeply the input nests, not that the input
106
+ is invalid.
107
+
108
+ == Engines
109
+
110
+ Umath runs its grammar on Parsanol's pure Ruby engine or on its native
111
+ engine. The default is `engine: :ruby`. You can opt into the native engine
112
+ with `engine: :native`, or with `engine: :auto`, which uses the native
113
+ engine where its extension is available and the Ruby engine elsewhere.
114
+ Asking for `engine: :native` where it cannot run raises
115
+ `Umath::EngineUnavailable` instead of quietly using Ruby.
116
+ `Umath.last_engine` tells you which engine handled the last call on
117
+ the current thread.
118
+
119
+ Ruby is the default because it is the engine that matches legacy Plurimath.
120
+ For 18 of the inputs in the testsuite's `fixtures/unicode-expectations.yaml`
121
+ that legacy Plurimath accepts, the native engine takes another alternative
122
+ of the same grammar and returns a different tree from the Ruby engine; the
123
+ Ruby engine returns the legacy tree for all of them. These inputs are listed
124
+ in
125
+ link:spec/parser/fixtures/oracle/native-divergences.yml[`spec/parser/fixtures/oracle/native-divergences.yml`],
126
+ and CI checks that they still differ, so the list shrinks when the native
127
+ engine changes. The native engine is faster, and the Ruby engine is slower,
128
+ especially on input it refuses. Choose `engine: :native` when speed matters
129
+ more to you than the legacy tree for those inputs.
130
+
131
+ == Testing against the Plurimath testsuite
132
+
133
+ The UnicodeMath cases of
134
+ https://github.com/plurimath/plurimath-testsuite[plurimath-testsuite] are
135
+ kept there as numbered, immutable generations.
136
+ `spec/fixtures/testsuite-pin.yml` names the exact commit, generation and
137
+ digest this gem is tested against, and CI replays every raw-tree row of that
138
+ generation on both engines. CI also compares the parse tree with the one the
139
+ pinned Plurimath commit produces for every UnicodeMath case of the testsuite.
140
+ See link:docs/testsuite-pin.adoc[the testsuite pin].
141
+
142
+ == Relation to Plurimath
143
+
144
+ Umath is one of the language gems split out of Plurimath, with
145
+ https://github.com/plurimath/amath[amath] for AsciiMath and
146
+ https://github.com/plurimath/latexmath[latexmath] for LaTeX. The plan and its
147
+ progress are tracked in
148
+ https://github.com/plurimath/plurimath/issues/541[plurimath/plurimath#541].
149
+ Plurimath is to turn the model into its own formula objects and write the
150
+ output formats.
151
+
152
+ == License
153
+
154
+ Copyright Ribose Inc. Licensed under the BSD 2-Clause License; see
155
+ link:LICENSE.md[].
@@ -0,0 +1 @@
1
+ {"artifact_kind":"parg-bindings","target":"ruby","binding_version":1,"grammar":"Umath","grammar_version":"0.1.0","grammar_source_hash":"dcfb908f12ee4ad6a45810048976bb7d315672e8123e19b9cad0ec2d1a4f7cb1","shape":"parsanol-tree/v2","entries":{"comma":{"bindings":[{"capture":"umath_tree","path":"umath_tree","type":"raw","card":null,"preprocess":null},{"capture":"umath_tree","path":"model","type":"any","card":null,"preprocess":null}]},"dot":{"bindings":[{"capture":"umath_tree","path":"umath_tree","type":"raw","card":null,"preprocess":null},{"capture":"umath_tree","path":"model","type":"any","card":null,"preprocess":null}]},"arabic":{"bindings":[{"capture":"umath_tree","path":"umath_tree","type":"raw","card":null,"preprocess":null},{"capture":"umath_tree","path":"model","type":"any","card":null,"preprocess":null}]}},"checksum":"sha256:1fa6ebae4357838250620e879ca774a4f2c9af45e6b7893b153d04b4db0648ba"}