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 +7 -0
- data/CHANGELOG.md +27 -0
- data/LICENSE.md +33 -0
- data/README.adoc +155 -0
- data/artifacts/umath.bindings.ruby.json +1 -0
- data/artifacts/umath.json +1 -0
- data/data/umath-symbols.json +13145 -0
- data/docs/model-contract.adoc +184 -0
- data/docs/parser-contract.adoc +341 -0
- data/docs/testsuite-pin.adoc +95 -0
- data/lib/umath/artifact.rb +84 -0
- data/lib/umath/configuration.rb +32 -0
- data/lib/umath/engine.rb +163 -0
- data/lib/umath/errors.rb +38 -0
- data/lib/umath/model/nodes.rb +737 -0
- data/lib/umath/model.rb +30 -0
- data/lib/umath/native_recursion_guard.rb +43 -0
- data/lib/umath/parser.rb +50 -0
- data/lib/umath/preprocessor.rb +91 -0
- data/lib/umath/raw_parser.rb +59 -0
- data/lib/umath/source_map.rb +146 -0
- data/lib/umath/symbols.rb +71 -0
- data/lib/umath/version.rb +5 -0
- data/lib/umath.rb +73 -0
- metadata +110 -0
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"}
|