latexmath 0.1.0 → 0.1.5
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/.editorconfig +14 -0
- data/.github/workflows/test.yml +80 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +129 -0
- data/Gemfile +6 -3
- data/Gemfile.lock +44 -3
- data/README.adoc +28 -0
- data/Rakefile +93 -4
- data/bin/console +3 -3
- data/exe/latexmath +8 -0
- data/latexmath.gemspec +17 -14
- data/lib/latexmath.rb +76 -10
- data/lib/latexmath/aggregator.rb +351 -0
- data/lib/latexmath/constants/symbols.rb +3936 -0
- data/lib/latexmath/converter.rb +424 -0
- data/lib/latexmath/equation.rb +3 -30
- data/lib/latexmath/ext.rb +9 -0
- data/lib/latexmath/symbol.rb +21 -0
- data/lib/latexmath/tokenizer.rb +82 -0
- data/lib/latexmath/version.rb +1 -1
- data/lib/latexmath/xml/builder.rb +4 -0
- data/lib/latexmath/xml/element.rb +25 -0
- data/lib/unimathsymbols.js.erb +4 -0
- data/lib/unimathsymbols.txt +2864 -0
- data/opal/latexmath-opal.rb +40 -0
- data/opal/ox.rb +64 -0
- data/opal/pseudoenumerator.rb +19 -0
- metadata +74 -17
- data/.travis.yml +0 -6
- data/README.md +0 -40
- data/lib/latexmath/latexml_requirement.rb +0 -84
- data/lib/latexmath/requirement.rb +0 -12
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'latexmath'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "latexmath"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
data/exe/latexmath
ADDED
data/latexmath.gemspec
CHANGED
@@ -1,31 +1,34 @@
|
|
1
1
|
require_relative 'lib/latexmath/version'
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name =
|
4
|
+
spec.name = 'latexmath'
|
5
5
|
spec.version = Latexmath::VERSION
|
6
6
|
spec.authors = ['Ribose Inc.']
|
7
7
|
spec.email = ['open.source@ribose.com']
|
8
8
|
|
9
|
-
spec.summary =
|
10
|
-
spec.description =
|
11
|
-
spec.homepage =
|
12
|
-
spec.license =
|
9
|
+
spec.summary = 'Converts LaTeX math into MathML.'
|
10
|
+
spec.description = 'Converts LaTeX math into MathML.'
|
11
|
+
spec.homepage = 'https://github.com/plurimath/latexmath'
|
12
|
+
spec.license = 'BSD-2-Clause'
|
13
13
|
|
14
|
-
spec.required_ruby_version = Gem::Requirement.new(
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
15
15
|
|
16
|
-
spec.metadata[
|
17
|
-
spec.metadata[
|
18
|
-
spec.metadata[
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/plurimath/latexmath'
|
18
|
+
spec.metadata['changelog_uri'] = 'https://github.com/plurimath/latexmath/blob/master/CHANGELOG.md.'
|
19
19
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
-
spec.files
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
23
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
24
|
end
|
25
|
-
spec.bindir =
|
25
|
+
spec.bindir = 'exe'
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
-
spec.require_paths = [
|
27
|
+
spec.require_paths = ['lib']
|
28
28
|
|
29
|
-
spec.add_dependency
|
30
|
-
spec.add_dependency
|
29
|
+
spec.add_dependency 'htmlentities', '~> 4.3'
|
30
|
+
spec.add_dependency 'ox', '~> 2.13'
|
31
|
+
spec.add_development_dependency 'equivalent-xml'
|
32
|
+
spec.add_development_dependency 'execjs'
|
33
|
+
spec.add_development_dependency 'opal'
|
31
34
|
end
|
data/lib/latexmath.rb
CHANGED
@@ -1,22 +1,88 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
|
1
|
+
#require 'byebug' unless RUBY_ENGINE == 'opal'
|
2
|
+
require 'json'
|
3
|
+
require 'htmlentities'
|
4
|
+
require 'ox'
|
5
|
+
require_relative 'latexmath/ext'
|
6
|
+
require_relative 'latexmath/version'
|
7
|
+
require_relative 'latexmath/constants/symbols'
|
8
|
+
require_relative 'latexmath/aggregator'
|
9
|
+
require_relative 'latexmath/converter'
|
10
|
+
require_relative 'latexmath/symbol'
|
11
|
+
require_relative 'latexmath/tokenizer'
|
12
|
+
require_relative 'latexmath/xml/element'
|
13
|
+
require_relative 'latexmath/equation'
|
6
14
|
|
7
15
|
module Latexmath
|
8
|
-
|
9
|
-
|
16
|
+
MATRICES = [
|
17
|
+
'\\matrix',
|
18
|
+
'\\matrix*',
|
19
|
+
'\\pmatrix',
|
20
|
+
'\\pmatrix*',
|
21
|
+
'\\bmatrix',
|
22
|
+
'\\bmatrix*',
|
23
|
+
'\\Bmatrix',
|
24
|
+
'\\Bmatrix*',
|
25
|
+
'\\vmatrix',
|
26
|
+
'\\vmatrix*',
|
27
|
+
'\\Vmatrix',
|
28
|
+
'\\Vmatrix*',
|
29
|
+
'\\array',
|
30
|
+
'\\split',
|
31
|
+
'\\substack'
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
SPACES = ['\\,', '\\:', '\\;', '\\\\'].freeze
|
35
|
+
STYLES = {
|
36
|
+
'\\bf' => 'mathbf'
|
37
|
+
}.freeze
|
10
38
|
|
11
|
-
|
12
|
-
|
39
|
+
LIMITS = ['\\lim', '\\sup', '\\inf', '\\max', '\\min'].freeze
|
40
|
+
COMMANDS = {
|
41
|
+
# command: [params_count, mathml_equivalent, attributes]
|
42
|
+
'_' => [2, 'msub', {}],
|
43
|
+
'^' => [2, 'msup', {}],
|
44
|
+
'_^' => [3, 'msubsup', {}],
|
45
|
+
'\\frac' => [2, 'mfrac', {}],
|
46
|
+
'\\sqrt' => [1, 'msqrt', {}],
|
47
|
+
'\\root' => [2, 'mroot', {}],
|
48
|
+
'\\binom' => [2, 'mfrac', { "linethickness": '0' }],
|
49
|
+
'\\left' => [
|
50
|
+
1,
|
51
|
+
'mo',
|
52
|
+
[%w[stretchy true], %w[fence true], %w[form prefix]]
|
53
|
+
],
|
54
|
+
'\\right' => [
|
55
|
+
1,
|
56
|
+
'mo',
|
57
|
+
[%w[stretchy true], %w[fence true], %w[form postfix]]
|
58
|
+
],
|
59
|
+
'\\overline' => [1, 'mover', {}],
|
60
|
+
'\\bar' => [1, 'mover', {}],
|
61
|
+
'\\underline' => [1, 'munder', {}],
|
62
|
+
'\\limits' => [3, 'munderover', {}],
|
63
|
+
'\\overrightarrow' => [1, 'mover', {}]
|
13
64
|
}
|
14
65
|
|
66
|
+
COMMANDS['\\quad'] = [0, 'mo', { "mathvariant": 'italic', separator: 'true' }]
|
67
|
+
COMMANDS['\\qquad'] = [0, 'mo', { "mathvariant": 'italic', separator: 'true' }]
|
68
|
+
SPACES.each do |space|
|
69
|
+
COMMANDS[space] = [0, 'mspace', { "width": '0.167em' }]
|
70
|
+
end
|
71
|
+
|
72
|
+
MATRICES.each do |matrix|
|
73
|
+
COMMANDS[matrix] = [1, 'mtable', {}]
|
74
|
+
end
|
75
|
+
|
76
|
+
LIMITS.each do |limit|
|
77
|
+
COMMANDS[limit] = [1, 'munder', {}]
|
78
|
+
end
|
79
|
+
|
15
80
|
def self.parse(string)
|
16
|
-
lxm_input =
|
81
|
+
lxm_input = HTMLEntities.new.decode(string)
|
17
82
|
|
18
83
|
# parse
|
19
84
|
Equation.new(lxm_input)
|
20
85
|
end
|
21
86
|
|
87
|
+
class Error < StandardError; end
|
22
88
|
end
|
@@ -0,0 +1,351 @@
|
|
1
|
+
module Latexmath
|
2
|
+
class Aggregator
|
3
|
+
OPERATORS = '+-*/=[]_^{}()'.freeze
|
4
|
+
|
5
|
+
OPENING_BRACES = '{'.freeze
|
6
|
+
CLOSING_BRACES = '}'.freeze
|
7
|
+
OPENING_BRACKET = '['.freeze
|
8
|
+
CLOSING_BRACKET = ']'.freeze
|
9
|
+
OPENING_PARENTHESIS = '('.freeze
|
10
|
+
CLOSING_PARENTHESIS = ')'.freeze
|
11
|
+
|
12
|
+
BACKSLASH = '\\\\'.freeze
|
13
|
+
AMPERSAND = '&'.freeze
|
14
|
+
DASH = '-'.freeze
|
15
|
+
|
16
|
+
SUB_SUP = '_^'.freeze
|
17
|
+
SUBSCRIPT = '_'.freeze
|
18
|
+
SUPERSCRIPT = '^'.freeze
|
19
|
+
|
20
|
+
# Added prefix LATEX_ to avoid ruby reserved words
|
21
|
+
LATEX_LEFT = '\\left'.freeze
|
22
|
+
LATEX_RIGHT = '\\right'.freeze
|
23
|
+
LATEX_OVER = '\\over'.freeze
|
24
|
+
LATEX_HLINE = '\\hline'.freeze
|
25
|
+
LATEX_BEGIN = '\\begin'.freeze
|
26
|
+
LATEX_FRAC = '\\frac'.freeze
|
27
|
+
LATEX_ROOT = '\\root'.freeze
|
28
|
+
LATEX_SQRT = '\\sqrt'.freeze
|
29
|
+
|
30
|
+
def initialize(tokens)
|
31
|
+
@tokens = tokens
|
32
|
+
end
|
33
|
+
|
34
|
+
def aggregate(tokens = @tokens)
|
35
|
+
aggregated = []
|
36
|
+
|
37
|
+
loop do
|
38
|
+
begin
|
39
|
+
token = next_item_or_group(tokens)
|
40
|
+
raise StopIteration if token.nil?
|
41
|
+
|
42
|
+
if token.is_a?(Array)
|
43
|
+
aggregated << token
|
44
|
+
elsif token == OPENING_BRACKET
|
45
|
+
previous = nil
|
46
|
+
previous = aggregated[-1] if aggregated.any?
|
47
|
+
begin
|
48
|
+
g = group(tokens, opening: OPENING_BRACKET, closing: CLOSING_BRACKET)
|
49
|
+
if previous == LATEX_SQRT
|
50
|
+
root = tokens.shift
|
51
|
+
raise StopIteration if root.nil?
|
52
|
+
|
53
|
+
if root == OPENING_BRACES
|
54
|
+
begin
|
55
|
+
root = group(tokens)
|
56
|
+
rescue EmptyGroupError
|
57
|
+
root = ''
|
58
|
+
end
|
59
|
+
end
|
60
|
+
aggregated[-1] = LATEX_ROOT
|
61
|
+
aggregated << root
|
62
|
+
end
|
63
|
+
aggregated << g
|
64
|
+
rescue EmptyGroupError
|
65
|
+
next if previous == LATEX_SQRT
|
66
|
+
|
67
|
+
aggregated += [OPENING_BRACKET, CLOSING_BRACKET]
|
68
|
+
end
|
69
|
+
elsif LIMITS.include?(token)
|
70
|
+
raise StopIteration if tokens.shift.nil?
|
71
|
+
|
72
|
+
a = next_item_or_group(tokens)
|
73
|
+
aggregated += [token, a]
|
74
|
+
elsif token == '\\limits'
|
75
|
+
previous = aggregated.pop
|
76
|
+
raise StopIteration if tokens.shift.nil?
|
77
|
+
|
78
|
+
a = next_item_or_group(tokens)
|
79
|
+
raise StopIteration if tokens.shift.nil?
|
80
|
+
|
81
|
+
b = next_item_or_group(tokens)
|
82
|
+
aggregated += [token, previous, a, b]
|
83
|
+
elsif token && SUB_SUP.include?(token)
|
84
|
+
aggregated = process_sub_sup(aggregated, token, tokens)
|
85
|
+
elsif token.start_with?(LATEX_BEGIN) || MATRICES.include?(token)
|
86
|
+
aggregated += environment(token, tokens)
|
87
|
+
elsif token == LATEX_OVER
|
88
|
+
numerator = aggregated
|
89
|
+
aggregated = []
|
90
|
+
aggregated << LATEX_FRAC
|
91
|
+
aggregated << numerator
|
92
|
+
aggregated << aggregate(tokens)
|
93
|
+
else
|
94
|
+
aggregated << token
|
95
|
+
end
|
96
|
+
rescue EmptyGroupError
|
97
|
+
aggregated += [OPENING_BRACES, CLOSING_BRACES]
|
98
|
+
next
|
99
|
+
rescue StopIteration
|
100
|
+
aggregated << token unless token.nil?
|
101
|
+
break
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
aggregated
|
106
|
+
end
|
107
|
+
|
108
|
+
def environment(token, tokens)
|
109
|
+
env = if token.start_with?(LATEX_BEGIN)
|
110
|
+
token[7..-2]
|
111
|
+
else
|
112
|
+
token[1..token.size]
|
113
|
+
end
|
114
|
+
|
115
|
+
alignment = nil
|
116
|
+
content = []
|
117
|
+
row = []
|
118
|
+
has_rowline = false
|
119
|
+
|
120
|
+
loop do
|
121
|
+
begin
|
122
|
+
token = next_item_or_group(tokens)
|
123
|
+
raise StopIteration if token.nil?
|
124
|
+
|
125
|
+
if token.is_a? Array
|
126
|
+
begin
|
127
|
+
if env == 'array' && token.all? { |x| 'lcr|'.include?(x) }
|
128
|
+
alignment = token
|
129
|
+
else
|
130
|
+
row << process_row(token)
|
131
|
+
end
|
132
|
+
rescue TypeError
|
133
|
+
row << token
|
134
|
+
end
|
135
|
+
elsif token == "\\end{#{env}}"
|
136
|
+
break
|
137
|
+
elsif token == AMPERSAND
|
138
|
+
row << token
|
139
|
+
elsif token == BACKSLASH
|
140
|
+
row = group_columns(row) if row.include?(AMPERSAND)
|
141
|
+
row.insert(0, LATEX_HLINE) if has_rowline
|
142
|
+
content << row
|
143
|
+
row = []
|
144
|
+
has_rowline = false
|
145
|
+
elsif token == LATEX_HLINE
|
146
|
+
has_rowline = true
|
147
|
+
elsif token == OPENING_BRACKET && content.empty?
|
148
|
+
begin
|
149
|
+
alignment = group(tokens, opening: OPENING_BRACKET, closing: CLOSING_BRACKET)
|
150
|
+
rescue EmptyGroupError
|
151
|
+
next
|
152
|
+
end
|
153
|
+
elsif token == DASH
|
154
|
+
next_token = tokens.shift
|
155
|
+
raise StopIteration if next_token.nil?
|
156
|
+
|
157
|
+
row << if next_token == "\\end{#{env}}"
|
158
|
+
token
|
159
|
+
else
|
160
|
+
[token, next_token]
|
161
|
+
end
|
162
|
+
elsif SUB_SUP.include?(token)
|
163
|
+
row = process_sub_sup(row, token, tokens)
|
164
|
+
elsif token.start_with?(LATEX_BEGIN)
|
165
|
+
row += environment(token, tokens)
|
166
|
+
else
|
167
|
+
row << token
|
168
|
+
end
|
169
|
+
rescue EmptyGroupError
|
170
|
+
row << []
|
171
|
+
next
|
172
|
+
rescue StopIteration
|
173
|
+
break
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if row.any?
|
178
|
+
row = group_columns(row) if row.include?(AMPERSAND)
|
179
|
+
row.insert(0, LATEX_HLINE) if has_rowline
|
180
|
+
content << row
|
181
|
+
end
|
182
|
+
|
183
|
+
content = content.pop while content.size == 1 && content.first.is_a?(Array)
|
184
|
+
|
185
|
+
return ["\\#{env}", alignment.join, content] if alignment
|
186
|
+
|
187
|
+
["\\#{env}", content]
|
188
|
+
end
|
189
|
+
|
190
|
+
def group(tokens, opening: OPENING_BRACES, closing: CLOSING_BRACES, delimiter: nil)
|
191
|
+
g = []
|
192
|
+
|
193
|
+
if delimiter
|
194
|
+
g << delimiter
|
195
|
+
g << tokens.shift
|
196
|
+
end
|
197
|
+
|
198
|
+
loop do
|
199
|
+
begin
|
200
|
+
token = tokens.shift
|
201
|
+
raise StopIteration if token.nil?
|
202
|
+
|
203
|
+
if token == closing && delimiter.nil?
|
204
|
+
break if g.any?
|
205
|
+
|
206
|
+
raise EmptyGroupError
|
207
|
+
elsif token == opening
|
208
|
+
begin
|
209
|
+
g << group(tokens)
|
210
|
+
rescue EmptyGroupError
|
211
|
+
g += [[]]
|
212
|
+
end
|
213
|
+
elsif token == LATEX_LEFT
|
214
|
+
g << group(tokens, delimiter: token)
|
215
|
+
elsif token == LATEX_RIGHT
|
216
|
+
g << token
|
217
|
+
_token = tokens.shift
|
218
|
+
raise StopIteration if _token.nil?
|
219
|
+
|
220
|
+
g << _token
|
221
|
+
break
|
222
|
+
else
|
223
|
+
g << token
|
224
|
+
end
|
225
|
+
rescue StopIteration
|
226
|
+
break
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
if delimiter
|
231
|
+
right = g.index(LATEX_RIGHT)
|
232
|
+
raise ExtraLeftOrMissingRight if right.nil?
|
233
|
+
|
234
|
+
content = g[2..right - 1]
|
235
|
+
g_ = g
|
236
|
+
g_ = g[0..1] + [aggregate(content)] + g[right..g.size] if content.any?
|
237
|
+
|
238
|
+
return g_
|
239
|
+
end
|
240
|
+
|
241
|
+
aggregate(g)
|
242
|
+
end
|
243
|
+
|
244
|
+
def group_columns(row)
|
245
|
+
grouped = [[]]
|
246
|
+
row.each do |item|
|
247
|
+
if item == AMPERSAND
|
248
|
+
grouped << []
|
249
|
+
else
|
250
|
+
grouped[-1] << item
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
grouped.map { |item| item.size > 1 ? item : item.pop }
|
255
|
+
end
|
256
|
+
|
257
|
+
def next_item_or_group(tokens)
|
258
|
+
token = tokens.shift
|
259
|
+
raise StopIteration if token.nil?
|
260
|
+
|
261
|
+
return group(tokens) if token == OPENING_BRACES
|
262
|
+
|
263
|
+
return group(tokens, delimiter: token) if token == LATEX_LEFT
|
264
|
+
|
265
|
+
token
|
266
|
+
end
|
267
|
+
|
268
|
+
def find_opening_parenthesis(tokens)
|
269
|
+
closing = 0
|
270
|
+
|
271
|
+
tokens.map.with_index { |x, i| [i, x] }.reverse.each do |index, token|
|
272
|
+
if token == CLOSING_PARENTHESIS
|
273
|
+
closing += 1
|
274
|
+
elsif token == OPENING_PARENTHESIS
|
275
|
+
return index if closing == 0
|
276
|
+
|
277
|
+
closing -= 1
|
278
|
+
end
|
279
|
+
end
|
280
|
+
raise ExtraLeftOrMissingRight
|
281
|
+
end
|
282
|
+
|
283
|
+
def process_row(tokens)
|
284
|
+
row = []
|
285
|
+
content = []
|
286
|
+
|
287
|
+
tokens.each do |token|
|
288
|
+
if token == AMPERSAND
|
289
|
+
next
|
290
|
+
elsif token == BACKSLASH
|
291
|
+
content << row if row.any?
|
292
|
+
row = []
|
293
|
+
else
|
294
|
+
row << token
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
content << row if row.any?
|
299
|
+
|
300
|
+
content = content.pop while content.size == 1 && content.first.is_a?(Array)
|
301
|
+
|
302
|
+
content
|
303
|
+
end
|
304
|
+
|
305
|
+
def process_sub_sup(aggregated, token, tokens)
|
306
|
+
begin
|
307
|
+
previous = aggregated.pop
|
308
|
+
raise IndexError if previous.nil?
|
309
|
+
|
310
|
+
if previous.is_a?(String) && OPERATORS.include?(previous)
|
311
|
+
if (previous == CLOSING_PARENTHESIS) && aggregated.include?(OPENING_PARENTHESIS)
|
312
|
+
index = find_opening_parenthesis(aggregated)
|
313
|
+
aggregated = aggregated[0, index] + [token] + [aggregated[index..aggregated.size] + [previous]]
|
314
|
+
else
|
315
|
+
aggregated += [previous, token]
|
316
|
+
end
|
317
|
+
return aggregated
|
318
|
+
end
|
319
|
+
|
320
|
+
begin
|
321
|
+
next_token = next_item_or_group(tokens)
|
322
|
+
if aggregated.size >= 2
|
323
|
+
if aggregated[-2] == SUBSCRIPT && token == SUPERSCRIPT
|
324
|
+
aggregated[-2] = SUB_SUP
|
325
|
+
aggregated += [previous, next_token]
|
326
|
+
elsif (aggregated[-2] == SUPERSCRIPT) && (token == SUBSCRIPT)
|
327
|
+
aggregated[-2] = SUB_SUP
|
328
|
+
aggregated += [next_token, previous]
|
329
|
+
else
|
330
|
+
aggregated += [token, previous, next_token]
|
331
|
+
end
|
332
|
+
else
|
333
|
+
aggregated += [token, previous, next_token]
|
334
|
+
end
|
335
|
+
rescue EmptyGroupError
|
336
|
+
aggregated += [token, previous, []]
|
337
|
+
end
|
338
|
+
rescue IndexError
|
339
|
+
next_token = next_item_or_group(tokens)
|
340
|
+
aggregated += [token, '', next_token]
|
341
|
+
end
|
342
|
+
|
343
|
+
aggregated
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
class EmptyGroupError < StandardError; end
|
348
|
+
class ExtraLeftOrMissingRight < StandardError; end
|
349
|
+
class MissingSuperScriptOrSubscript < StandardError; end
|
350
|
+
class StopIteration < StandardError; end
|
351
|
+
end
|