asciimath 1.0.9 → 2.0.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.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +20 -0
  3. data/AST.adoc +457 -0
  4. data/CHANGELOG.adoc +47 -1
  5. data/Gemfile +5 -0
  6. data/README.adoc +68 -4
  7. data/asciimath.gemspec +3 -6
  8. data/dump_symbol_table.rb +46 -0
  9. data/lib/asciimath.rb +5 -4
  10. data/lib/asciimath/ast.rb +456 -0
  11. data/lib/asciimath/cli.rb +4 -1
  12. data/lib/asciimath/color_table.rb +21 -0
  13. data/lib/asciimath/html.rb +128 -112
  14. data/lib/asciimath/latex.rb +399 -0
  15. data/lib/asciimath/markup.rb +509 -0
  16. data/lib/asciimath/mathml.rb +206 -87
  17. data/lib/asciimath/parser.rb +510 -350
  18. data/lib/asciimath/symbol_table.rb +25 -0
  19. data/lib/asciimath/version.rb +1 -1
  20. data/spec/ast.rb +144 -0
  21. data/spec/customisation_spec.rb +28 -0
  22. data/spec/parser_spec.rb +623 -165
  23. data/spec/schema/mathml2/common/common-attribs.xsd +41 -0
  24. data/spec/schema/mathml2/common/math.xsd +126 -0
  25. data/spec/schema/mathml2/common/xlink-href.xsd +20 -0
  26. data/spec/schema/mathml2/content/arith.xsd +90 -0
  27. data/spec/schema/mathml2/content/calculus.xsd +146 -0
  28. data/spec/schema/mathml2/content/common-attrib.xsd +30 -0
  29. data/spec/schema/mathml2/content/constants.xsd +83 -0
  30. data/spec/schema/mathml2/content/constructs.xsd +260 -0
  31. data/spec/schema/mathml2/content/elementary-functions.xsd +117 -0
  32. data/spec/schema/mathml2/content/functions.xsd +73 -0
  33. data/spec/schema/mathml2/content/linear-algebra.xsd +173 -0
  34. data/spec/schema/mathml2/content/logic.xsd +53 -0
  35. data/spec/schema/mathml2/content/relations.xsd +55 -0
  36. data/spec/schema/mathml2/content/semantics.xsd +85 -0
  37. data/spec/schema/mathml2/content/sets.xsd +236 -0
  38. data/spec/schema/mathml2/content/statistics.xsd +136 -0
  39. data/spec/schema/mathml2/content/tokens.xsd +120 -0
  40. data/spec/schema/mathml2/content/vector-calculus.xsd +88 -0
  41. data/spec/schema/mathml2/mathml2.xsd +59 -0
  42. data/spec/schema/mathml2/presentation/action.xsd +44 -0
  43. data/spec/schema/mathml2/presentation/characters.xsd +37 -0
  44. data/spec/schema/mathml2/presentation/common-attribs.xsd +113 -0
  45. data/spec/schema/mathml2/presentation/common-types.xsd +103 -0
  46. data/spec/schema/mathml2/presentation/error.xsd +40 -0
  47. data/spec/schema/mathml2/presentation/layout.xsd +195 -0
  48. data/spec/schema/mathml2/presentation/scripts.xsd +186 -0
  49. data/spec/schema/mathml2/presentation/space.xsd +52 -0
  50. data/spec/schema/mathml2/presentation/style.xsd +69 -0
  51. data/spec/schema/mathml2/presentation/table.xsd +216 -0
  52. data/spec/schema/mathml2/presentation/tokens.xsd +124 -0
  53. data/spec/schema/mathml3/mathml3-common.xsd +99 -0
  54. data/spec/schema/mathml3/mathml3-content.xsd +684 -0
  55. data/spec/schema/mathml3/mathml3-presentation.xsd +2151 -0
  56. data/spec/schema/mathml3/mathml3-strict-content.xsd +186 -0
  57. data/spec/schema/mathml3/mathml3.xsd +9 -0
  58. metadata +88 -48
  59. data/.travis.yml +0 -18
data/CHANGELOG.adoc CHANGED
@@ -1,5 +1,51 @@
1
1
  = Asciimath Changelog
2
2
 
3
+ == 2.0.3
4
+
5
+ Enhancements::
6
+
7
+ * Issue #59: make escaping of non-ASCII characters in MathML and HTML output optional.
8
+
9
+ Bug fixes::
10
+
11
+ * Issue #58: produce `<mo>` tags instead of `<mi>` tags for non-alphanumeric strings.
12
+ * Issue #62: resolve infinite recursion in LaTeX generator.
13
+
14
+ == 2.0.2
15
+
16
+ Enhancements::
17
+
18
+ * Issue #51: generate `accent` and `accentunder` attributes on `munder`, `mover`, and `munderover` tags when appropriate
19
+ * Issue #52: map `phi` to U+03D5 and `varphi` to U+03C6 by default.
20
+ * Issue #53: Add support for Roman style font command `rm`.
21
+
22
+ Bug fixes::
23
+
24
+ * Issue #50: fix parsing error when unary and binary operators are missing an operand
25
+
26
+ == 2.0.1
27
+
28
+ Enhancements::
29
+
30
+ * Issue #40: add `ker` as standard symbol (@GarkGarcia)
31
+ * Issue #46: allow customisation of the parsing and rendering symbol tables (@GarkGarcia)
32
+
33
+ Bug fixes::
34
+
35
+ * Issue #48: fix parsing of single column matrices (@ronaldtse)
36
+
37
+ == 2.0.0
38
+
39
+ Enhancements::
40
+
41
+ * The AsciiMath parser has been significantly improved and should now have reached feature parity with the original Javascript implementation.
42
+ * Issue #16: add initial LaTeX output support (@GarkGarcia)
43
+ * Issue #26: avoid generating `<mfenced>` tags in MathML output
44
+
45
+ Bug fixes::
46
+
47
+ * Issue #14: add support for additional symbols and functions that are supported by Asciimath JS
48
+
3
49
  == 1.0.9
4
50
 
5
51
  Bug fixes::
@@ -60,4 +106,4 @@ Enhancements::
60
106
 
61
107
  Initial release::
62
108
 
63
- * An AsciiMath parser and MathML converter written in Ruby
109
+ * An AsciiMath parser and MathML converter written in Ruby
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in asciimath.gemspec
4
4
  gemspec
5
+
6
+ gem "bundler", "> 0"
7
+ gem "rake", "~> 13.0.0"
8
+ gem "rspec", "~> 3.9.0"
9
+ gem "nokogiri"
data/README.adoc CHANGED
@@ -1,13 +1,14 @@
1
1
  # AsciiMath
2
+ :uri-project: https://github.com/asciidoctor/asciimath
2
3
  ifndef::env-site[:status:]
3
4
 
4
- An http://asciimath.org[AsciiMath] parser and MathML generator written in pure Ruby.
5
+ An http://asciimath.org[AsciiMath] parser and MathML/LaTeX generator written in pure Ruby.
5
6
 
6
7
  ifdef::status[]
7
8
  [discrete]
8
9
  ## Status
9
10
 
10
- image:https://travis-ci.org/pepijnve/asciimath.svg?branch=master["Linux Build Status", link="https://travis-ci.org/asciidoctor/asciimath"]
11
+ image:{uri-project}/workflows/CI/badge.svg?branch=master["Build Status", link={uri-project}/actions?query=branch%3Amaster]
11
12
  image:https://img.shields.io/gem/v/asciimath.svg?label=gem%20version[Gem Version, link=https://rubygems.org/gems/asciimath]
12
13
  endif::status[]
13
14
 
@@ -48,15 +49,16 @@ parsed_expression = AsciiMath.parse(asciimath)
48
49
 
49
50
  The parsed expression is a set of nested Array and Hash objects.
50
51
 
51
- This expression can then be converted to MathML or HTML (experimental.
52
+ This expression can then be converted to MathML, HTML (experimental) or LaTeX.
52
53
 
53
54
  [source,ruby]
54
55
  ----
55
56
  math_ml = parsed_expression.to_mathml
56
57
  html = parsed_expression.to_html
58
+ latex = parsed_expression.to_latex
57
59
  ----
58
60
 
59
- The MathML or HTML code is returned as a String.
61
+ The MathML, HTML or LaTeX code is returned as a String.
60
62
 
61
63
  ### Command line
62
64
 
@@ -78,8 +80,53 @@ asciimath mathml "an asciimath string"
78
80
  asciimath html "an asciimath string"
79
81
  ----
80
82
 
83
+ .LaTeX Generation
84
+ [source]
85
+ ----
86
+ asciimath latex "an asciimath string"
87
+ ----
88
+
81
89
  This command will print out the generated code on stdout.
82
90
 
91
+ ## Extentions and Customization
92
+
93
+ The parser can be extended by passing a custum tokenization table:
94
+
95
+ [source, ruby]
96
+ ----
97
+ my_tokens_table = AsciiMath::SymbolTableBuilder.new
98
+ AsciiMath::Parser.add_default_parser_symbols(my_tokens_table)
99
+ my_tokens_table.add('mysymbol', :mysymbol, :symbol)
100
+
101
+ AsciiMath::parse("a + mysymbol + b", my_tokens_table.build)
102
+ ----
103
+
104
+ Furthermore, the behaviour of the tokenizer be customized by altering the value
105
+ associated with a token in `AsciiMath::Tokenizer::DEFAULT_PARSE_SYMBOL_TABLE`:
106
+
107
+ [source, ruby]
108
+ ----
109
+ my_tokens_table = AsciiMath::SymbolTableBuilder.new
110
+ AsciiMath::Parser.add_default_parser_symbols(my_tokens_table)
111
+ my_tokens_table.add('alpha', :beta, :symbol)
112
+
113
+ # Now "alpha + beta" is equivalent to "beta + beta"
114
+ AsciiMath::parse("alpha + beta", my_tokens_table.build)
115
+ ----
116
+
117
+ The same behaviour applies to each individual render (`MathMLBuilder`,
118
+ `HTMLBuilder` and `LatexBuilder`). By adding entries to a rendere's rendering
119
+ table (or modifying exisisting entries), users can customize it's output:
120
+
121
+ [source, ruby]
122
+ ----
123
+ my_rendering_table = AsciiMath::SymbolTableBuilder.new
124
+ AsciiMath::MarkupBuilder.add_default_display_symbols(my_rendering_table)
125
+ my_rendering_table.add('alpha', '\u03b2', :identifier)
126
+
127
+ # Now "alpha + beta" is equivalent to "beta + beta"
128
+ AsciiMath::parse("alpha + beta").to_mathml(my_rendering_table.build)
129
+ ----
83
130
 
84
131
  ## Notes on the HTML Output
85
132
 
@@ -95,6 +142,23 @@ Known issues are as follows:
95
142
  Rendering the HTML output correctly requires the inclusion of `style/math.css` in the html document.
96
143
  There is currently no specific required font for this output, it simply selects a `serif` font family - change the `@font-family` attribute in the `.math-inline` class to select something specific.
97
144
 
145
+ ## Notes on the LaTeX Output
146
+
147
+ All LaTeX commands and environments used in the output are coverved by
148
+ https://ctan.org/pkg/amsmath[`amsmath`] and `amssymb`, with a few exceptions:
149
+
150
+ * `\color`
151
+ * `\cancel`
152
+ * `\mathscr`
153
+ * `\twoheadrightarrowtail`
154
+
155
+ The `\color` command is supported by the
156
+ https://www.ctan.org/pkg/xcolor[`xcolor`] package, which is included in most
157
+ LaTeX distributions. The `\cancel` command is supported by the
158
+ https://www.ctan.org/pkg/cancel[cancel] package, also included in most LaTeX
159
+ distributions. The other commands are supported by the
160
+ https://ctan.org/pkg/stix[`stix`] package.
161
+
98
162
  ## Contributing
99
163
 
100
164
  . Fork it (https://github.com/pepijnve/asciimath/fork)
data/asciimath.gemspec CHANGED
@@ -1,13 +1,14 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
4
5
  require 'asciimath/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
8
  spec.name = "asciimath"
8
9
  spec.version = AsciiMath::VERSION
9
- spec.authors = ["Pepijn Van Eeckhoudt"]
10
- spec.email = ["pepijn@vaneeckhoudt.net"]
10
+ spec.authors = ["Pepijn Van Eeckhoudt", "Gark Garcia"]
11
+ spec.email = ["pepijn@vaneeckhoudt.net", "pablo-ecobar@riseup.net"]
11
12
  spec.summary = %q{AsciiMath parser and converter}
12
13
  spec.description = %q{A pure Ruby AsciiMath parsing and conversion library.}
13
14
  spec.homepage = ""
@@ -17,8 +18,4 @@ Gem::Specification.new do |spec|
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
20
  spec.require_paths = ["lib"]
20
-
21
- spec.add_development_dependency "bundler", "> 0"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "rspec", "~> 3.1.0"
24
21
  end
@@ -0,0 +1,46 @@
1
+ require_relative 'lib/asciimath'
2
+
3
+ def escape_adoc(adoc)
4
+ case adoc
5
+ when nil
6
+ ''
7
+ when '+'
8
+ adoc
9
+ else
10
+ "++#{adoc.gsub('|', '\\|')}++"
11
+ end
12
+ end
13
+
14
+ puts "|==="
15
+ puts '|AsciiMath |Symbol |MathML Value |LaTeX Value'
16
+ puts
17
+
18
+ AsciiMath::Parser::DEFAULT_PARSER_SYMBOL_TABLE.each_pair do |asciimath, value|
19
+ sym = value[:value]
20
+ unless sym.is_a?(Symbol)
21
+ next
22
+ end
23
+
24
+ mathml = AsciiMath::MathMLBuilder.default_display_symbol_table[sym]
25
+
26
+ if mathml
27
+ val = mathml[:value]
28
+ else
29
+ val = "Missing!!!!!"
30
+ end
31
+
32
+ latex = AsciiMath::LatexBuilder::SYMBOLS[sym] || "\\#{sym.to_s}"
33
+
34
+ codepoint = ""
35
+ if val.is_a?(String)
36
+ codepoint = val.codepoints.map do |cp|
37
+ cpstr = sprintf('U+%04X', cp)
38
+ "https://codepoints.net/#{cpstr}[#{cpstr}]"
39
+ end.join(' ')
40
+ end
41
+
42
+ puts "|#{escape_adoc(asciimath)} |:#{sym.to_s} |#{escape_adoc(val.to_s)} (#{codepoint}) |#{escape_adoc(latex)}"
43
+ end
44
+
45
+ puts "|==="
46
+ puts
data/lib/asciimath.rb CHANGED
@@ -1,4 +1,5 @@
1
- require File.join(File.dirname(__FILE__), 'asciimath/version')
2
- require File.join(File.dirname(__FILE__), 'asciimath/parser')
3
- require File.join(File.dirname(__FILE__), 'asciimath/mathml')
4
- require File.join(File.dirname(__FILE__), 'asciimath/html')
1
+ require_relative 'asciimath/version'
2
+ require_relative 'asciimath/parser'
3
+ require_relative 'asciimath/mathml'
4
+ require_relative 'asciimath/html'
5
+ require_relative 'asciimath/latex'
@@ -0,0 +1,456 @@
1
+ module AsciiMath
2
+ module AST
3
+ def expression(*e)
4
+ case e.length
5
+ when 0
6
+ nil
7
+ when 1
8
+ e[0]
9
+ else
10
+ Sequence.new(e)
11
+ end
12
+ end
13
+
14
+ def paren(lparen, e, rparen)
15
+ Paren.new(lparen, e, rparen)
16
+ end
17
+
18
+ def group(lparen, e, rparen)
19
+ Group.new(lparen, e, rparen)
20
+ end
21
+
22
+ def subsup(e, sub, sup)
23
+ SubSup.new(e, sub, sup)
24
+ end
25
+
26
+ def sub(e, sub)
27
+ SubSup.new(e, sub, nil)
28
+ end
29
+
30
+ def sup(e, sup)
31
+ SubSup.new(e, nil, sup)
32
+ end
33
+
34
+ def unary(operator, e)
35
+ UnaryOp.new(operator, e)
36
+ end
37
+
38
+ def binary(operator, e1, e2)
39
+ BinaryOp.new(operator, e1, e2)
40
+ end
41
+
42
+ def infix(e1, operator, e2)
43
+ InfixOp.new(operator, e1, e2)
44
+ end
45
+
46
+ def text(value)
47
+ Text.new(value)
48
+ end
49
+
50
+ def number(value)
51
+ Number.new(value)
52
+ end
53
+
54
+ def symbol(symbol, text)
55
+ Symbol.new(symbol, text)
56
+ end
57
+
58
+ def identifier(value)
59
+ Identifier.new(value)
60
+ end
61
+
62
+ def matrix(lparen, rows, rparen)
63
+ Matrix.new(lparen, rows, rparen)
64
+ end
65
+
66
+ def color(r, g, b, text)
67
+ Color.new(r, g, b, text)
68
+ end
69
+
70
+ class Node
71
+ attr_reader :parent
72
+
73
+ def initialize
74
+ @parent = nil
75
+ end
76
+
77
+ protected
78
+
79
+ attr_writer :parent
80
+ end
81
+
82
+ class InnerNode < Node
83
+ include Enumerable
84
+
85
+ def initialize
86
+ super
87
+ @children = []
88
+ end
89
+
90
+ def [](*args)
91
+ @children[*args]
92
+ end
93
+
94
+ def length
95
+ @children.length
96
+ end
97
+
98
+ def each(&block)
99
+ @children.each(&block)
100
+ end
101
+
102
+ protected
103
+
104
+ def child_nodes
105
+ @children
106
+ end
107
+
108
+ def add(node)
109
+ node.parent.remove(node) if node.parent
110
+ node.parent = self
111
+ child_nodes << node
112
+ end
113
+
114
+ def remove(node)
115
+ node.parent = nil
116
+ child_nodes.delete(node)
117
+ end
118
+ end
119
+
120
+ class Sequence < InnerNode
121
+ def initialize(nodes)
122
+ super()
123
+ nodes.each { |node| add(node) }
124
+ end
125
+
126
+ def to_s
127
+ child_nodes.map { |node| node.to_s }.join(" ")
128
+ end
129
+
130
+ def ==(o)
131
+ o.class == self.class && o.child_nodes == child_nodes
132
+ end
133
+ end
134
+
135
+ class Paren < InnerNode
136
+ attr_reader :lparen
137
+ attr_reader :rparen
138
+
139
+ def initialize(lparen, e, rparen)
140
+ super()
141
+ @lparen = lparen
142
+ @rparen = rparen
143
+ add(e) if e
144
+ end
145
+
146
+ def expression
147
+ child_nodes[0]
148
+ end
149
+
150
+ def to_s
151
+ "#{lparen.nil? ? '' : lparen.text}#{expression}#{rparen.nil? ? '' : rparen.text}"
152
+ end
153
+
154
+ def ==(o)
155
+ o.class == self.class && o.lparen == lparen && o.expression == expression && o.rparen == rparen
156
+ end
157
+ end
158
+
159
+ class Group < InnerNode
160
+ attr_reader :lparen
161
+ attr_reader :rparen
162
+
163
+ def initialize(lparen, e, rparen)
164
+ super()
165
+ @lparen = lparen
166
+ @rparen = rparen
167
+ add(e) if e
168
+ end
169
+
170
+ def expression
171
+ child_nodes[0]
172
+ end
173
+
174
+ def to_s
175
+ "#{lparen.nil? ? '' : lparen.text}#{expression}#{rparen.nil? ? '' : rparen.text}"
176
+ end
177
+
178
+ def ==(o)
179
+ o.class == self.class && o.lparen == lparen && o.expression == expression && o.rparen == rparen
180
+ end
181
+ end
182
+
183
+ class SubSup < InnerNode
184
+ def initialize(e, sub, sup)
185
+ super()
186
+ add(e)
187
+ add(sub || Empty.new)
188
+ add(sup || Empty.new)
189
+ end
190
+
191
+ def base_expression
192
+ child_nodes[0]
193
+ end
194
+
195
+ def sub_expression
196
+ child = child_nodes[1]
197
+ child.is_a?(Empty) ? nil : child
198
+ end
199
+
200
+ def sup_expression
201
+ child = child_nodes[2]
202
+ child.is_a?(Empty) ? nil : child
203
+ end
204
+
205
+ def to_s
206
+ s = ""
207
+ s << base_expression.to_s
208
+ sub = sub_expression
209
+ if sub
210
+ s << "_" << sub.to_s
211
+ end
212
+ sup = sup_expression
213
+ if sup
214
+ s << "^" << sup.to_s
215
+ end
216
+ s
217
+ end
218
+
219
+ def ==(o)
220
+ o.class == self.class && o.base_expression == base_expression && o.sub_expression == sub_expression && o.sup_expression == sup_expression
221
+ end
222
+ end
223
+
224
+ class UnaryOp < InnerNode
225
+ def initialize(operator, e)
226
+ super()
227
+ add(operator)
228
+ add(e)
229
+ end
230
+
231
+ def operator
232
+ child_nodes[0]
233
+ end
234
+
235
+ def operand
236
+ child_nodes[1]
237
+ end
238
+
239
+ def to_s
240
+ "#{operator} #{operand}"
241
+ end
242
+
243
+ def ==(o)
244
+ o.class == self.class && o.operator == operator && o.operand == operand
245
+ end
246
+ end
247
+
248
+ class BinaryOp < InnerNode
249
+ def initialize(operator, e1, e2)
250
+ super()
251
+ add(operator)
252
+ add(e1)
253
+ add(e2)
254
+ end
255
+
256
+
257
+ def operator
258
+ child_nodes[0]
259
+ end
260
+
261
+ def operand1
262
+ child_nodes[1]
263
+ end
264
+
265
+ def operand2
266
+ child_nodes[2]
267
+ end
268
+
269
+ def to_s
270
+ "#{operator} #{operand1} #{operand2}"
271
+ end
272
+
273
+ def ==(o)
274
+ o.class == self.class && o.operator == operator && o.operand1 == operand1 && o.operand2 == operand2
275
+ end
276
+ end
277
+
278
+ class InfixOp < InnerNode
279
+ def initialize(operator, e1, e2)
280
+ super()
281
+ add(operator)
282
+ add(e1)
283
+ add(e2)
284
+ end
285
+
286
+
287
+ def operator
288
+ child_nodes[0]
289
+ end
290
+
291
+ def operand1
292
+ child_nodes[1]
293
+ end
294
+
295
+ def operand2
296
+ child_nodes[2]
297
+ end
298
+
299
+ def to_s
300
+ "#{operand1} #{operator} #{operand2}"
301
+ end
302
+
303
+ def ==(o)
304
+ o.class == self.class && o.operator == operator && o.operand1 == operand1 && o.operand2 == operand2
305
+ end
306
+ end
307
+
308
+ class ValueNode < Node
309
+ attr_reader :value
310
+
311
+ def initialize(value)
312
+ super()
313
+ @value = value
314
+ end
315
+
316
+ def to_s
317
+ value.to_s
318
+ end
319
+
320
+ def ==(o)
321
+ o.class == self.class && o.value == value
322
+ end
323
+ end
324
+
325
+ class Text < ValueNode
326
+ def initialize(value)
327
+ super(value.dup.freeze)
328
+ end
329
+
330
+ def to_s
331
+ '"' + super + '"'
332
+ end
333
+ end
334
+
335
+ class Number < ValueNode
336
+ def initialize(value)
337
+ super(value.dup.freeze)
338
+ end
339
+ end
340
+
341
+ class Symbol < ValueNode
342
+ attr_reader :text
343
+
344
+ def initialize(value, text)
345
+ super(value)
346
+ @text = text.dup.freeze
347
+ end
348
+
349
+ def ==(o)
350
+ super && o.text == text
351
+ end
352
+
353
+ def to_s
354
+ text
355
+ end
356
+ end
357
+
358
+ class Identifier < ValueNode
359
+ def initialize(value)
360
+ super(value.dup.freeze)
361
+ end
362
+ end
363
+
364
+ class Color < ValueNode
365
+ attr_reader :text
366
+
367
+ def initialize(r, g, b, text)
368
+ super({:r => r, :g => g, :b => b}.freeze)
369
+ @text = text.dup.freeze
370
+ end
371
+
372
+ def red
373
+ value[:r]
374
+ end
375
+
376
+ def green
377
+ value[:g]
378
+ end
379
+
380
+ def blue
381
+ value[:b]
382
+ end
383
+
384
+ def ==(o)
385
+ o.class == self.class &&
386
+ o.red == red &&
387
+ o.green == green &&
388
+ o.blue == blue &&
389
+ o.text == text
390
+ end
391
+
392
+ def to_hex_rgb
393
+ sprintf('#%02x%02x%02x', red, green, blue)
394
+ end
395
+
396
+ def to_s
397
+ text
398
+ end
399
+ end
400
+
401
+ class Matrix < InnerNode
402
+ attr_reader :lparen
403
+ attr_reader :rparen
404
+
405
+ def initialize(lparen, rows, rparen)
406
+ super()
407
+ @lparen = lparen
408
+ @rparen = rparen
409
+ rows.map { |row| MatrixRow.new(row) }.each { |row_seq| add(row_seq) }
410
+ end
411
+
412
+ def to_s
413
+ s = ""
414
+ s << (lparen.nil? ? '{:' : lparen.text)
415
+ s << child_nodes.map { |node| node.to_s }.join(",")
416
+ s << (rparen.nil? ? ':}' : rparen.text)
417
+ end
418
+
419
+ def ==(o)
420
+ o.class == self.class &&
421
+ o.lparen == lparen &&
422
+ o.child_nodes == child_nodes &&
423
+ o.rparen == rparen
424
+ end
425
+ end
426
+
427
+ class MatrixRow < InnerNode
428
+ def initialize(nodes)
429
+ super()
430
+ nodes.each { |node| add(node || Empty.new) }
431
+ end
432
+
433
+ def to_s
434
+ "(" + child_nodes.map { |node| node.to_s }.join(",") + ")"
435
+ end
436
+
437
+ def ==(o)
438
+ o.class == self.class && o.child_nodes == child_nodes
439
+ end
440
+ end
441
+
442
+ class Empty < Node
443
+ def initialize()
444
+ super
445
+ end
446
+
447
+ def to_s
448
+ ''
449
+ end
450
+
451
+ def ==(o)
452
+ o.class == self.class
453
+ end
454
+ end
455
+ end
456
+ end