asciimath 1.0.6 → 2.0.1

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 (57) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -8
  3. data/AST.adoc +457 -0
  4. data/CHANGELOG.adoc +44 -1
  5. data/README.adoc +66 -3
  6. data/asciimath.gemspec +7 -5
  7. data/dump_symbol_table.rb +46 -0
  8. data/lib/asciimath.rb +5 -4
  9. data/lib/asciimath/ast.rb +456 -0
  10. data/lib/asciimath/cli.rb +4 -1
  11. data/lib/asciimath/color_table.rb +21 -0
  12. data/lib/asciimath/html.rb +148 -119
  13. data/lib/asciimath/latex.rb +400 -0
  14. data/lib/asciimath/markup.rb +479 -0
  15. data/lib/asciimath/mathml.rb +196 -83
  16. data/lib/asciimath/parser.rb +531 -318
  17. data/lib/asciimath/symbol_table.rb +25 -0
  18. data/lib/asciimath/version.rb +1 -1
  19. data/spec/ast.rb +144 -0
  20. data/spec/customisation_spec.rb +28 -0
  21. data/spec/parser_spec.rb +598 -147
  22. data/spec/schema/mathml2/common/common-attribs.xsd +41 -0
  23. data/spec/schema/mathml2/common/math.xsd +126 -0
  24. data/spec/schema/mathml2/common/xlink-href.xsd +20 -0
  25. data/spec/schema/mathml2/content/arith.xsd +90 -0
  26. data/spec/schema/mathml2/content/calculus.xsd +146 -0
  27. data/spec/schema/mathml2/content/common-attrib.xsd +30 -0
  28. data/spec/schema/mathml2/content/constants.xsd +83 -0
  29. data/spec/schema/mathml2/content/constructs.xsd +260 -0
  30. data/spec/schema/mathml2/content/elementary-functions.xsd +117 -0
  31. data/spec/schema/mathml2/content/functions.xsd +73 -0
  32. data/spec/schema/mathml2/content/linear-algebra.xsd +173 -0
  33. data/spec/schema/mathml2/content/logic.xsd +53 -0
  34. data/spec/schema/mathml2/content/relations.xsd +55 -0
  35. data/spec/schema/mathml2/content/semantics.xsd +85 -0
  36. data/spec/schema/mathml2/content/sets.xsd +236 -0
  37. data/spec/schema/mathml2/content/statistics.xsd +136 -0
  38. data/spec/schema/mathml2/content/tokens.xsd +120 -0
  39. data/spec/schema/mathml2/content/vector-calculus.xsd +88 -0
  40. data/spec/schema/mathml2/mathml2.xsd +59 -0
  41. data/spec/schema/mathml2/presentation/action.xsd +44 -0
  42. data/spec/schema/mathml2/presentation/characters.xsd +37 -0
  43. data/spec/schema/mathml2/presentation/common-attribs.xsd +113 -0
  44. data/spec/schema/mathml2/presentation/common-types.xsd +103 -0
  45. data/spec/schema/mathml2/presentation/error.xsd +40 -0
  46. data/spec/schema/mathml2/presentation/layout.xsd +195 -0
  47. data/spec/schema/mathml2/presentation/scripts.xsd +186 -0
  48. data/spec/schema/mathml2/presentation/space.xsd +52 -0
  49. data/spec/schema/mathml2/presentation/style.xsd +69 -0
  50. data/spec/schema/mathml2/presentation/table.xsd +216 -0
  51. data/spec/schema/mathml2/presentation/tokens.xsd +124 -0
  52. data/spec/schema/mathml3/mathml3-common.xsd +99 -0
  53. data/spec/schema/mathml3/mathml3-content.xsd +684 -0
  54. data/spec/schema/mathml3/mathml3-presentation.xsd +2151 -0
  55. data/spec/schema/mathml3/mathml3-strict-content.xsd +186 -0
  56. data/spec/schema/mathml3/mathml3.xsd +9 -0
  57. metadata +108 -11
@@ -1,6 +1,7 @@
1
1
  require_relative 'parser'
2
2
  require_relative 'mathml'
3
3
  require_relative 'html'
4
+ require_relative 'latex'
4
5
 
5
6
  module AsciiMath
6
7
  module CLI
@@ -11,8 +12,10 @@ module AsciiMath
11
12
  output = AsciiMath.parse(asciimath).to_mathml
12
13
  elsif args.first == "html"
13
14
  output = AsciiMath.parse(asciimath).to_html
15
+ elsif args.first == "latex"
16
+ output = AsciiMath.parse(asciimath).to_latex
14
17
  end
15
18
  puts output
16
19
  end
17
20
  end
18
- end
21
+ end
@@ -0,0 +1,21 @@
1
+ module AsciiMath
2
+ class ColorTableBuilder
3
+ def initialize()
4
+ @table = {}
5
+ end
6
+
7
+ def add(*names, r, g, b)
8
+ entry = {
9
+ :r => r,
10
+ :g => g,
11
+ :b => b
12
+ }.freeze
13
+
14
+ names.each { |name| @table[name.freeze] = entry }
15
+ end
16
+
17
+ def build
18
+ @table.dup.freeze
19
+ end
20
+ end
21
+ end
@@ -1,7 +1,12 @@
1
+ require_relative 'markup'
2
+
1
3
  module AsciiMath
2
- class HTMLBuilder
3
- def initialize(prefix)
4
- @prefix = prefix
4
+ class HTMLBuilder < ::AsciiMath::MarkupBuilder
5
+
6
+ def initialize(opts = {})
7
+ super(opts[:symbol_table] || DEFAULT_DISPLAY_SYMBOL_TABLE)
8
+ @prefix = opts[:prefifx] || ''
9
+ @inline = opts[:inline]
5
10
  @html = ''
6
11
  end
7
12
 
@@ -9,168 +14,180 @@ module AsciiMath
9
14
  @html
10
15
  end
11
16
 
12
- def append_expression(expression, inline, attrs = {})
13
- if inline
17
+ def append_expression(expression, attrs = {})
18
+ if @inline
14
19
  inline('', attrs) do
15
- append(expression, :single_child => true)
20
+ append(expression, :row => :omit)
16
21
  end
17
22
  else
18
23
  block('', attrs) do
19
- append(expression, :single_child => true)
24
+ append(expression, :row => :omit)
20
25
  end
21
26
  end
22
27
  end
23
28
 
24
29
  private
25
30
 
26
- def append(expression, opts = {})
27
- case expression
28
- when Array
29
- row do
30
- expression.each { |e| append(e) }
31
- end
32
- when Hash
33
- case expression[:type]
34
- when :operator
35
- operator(expression[:c])
36
- when :identifier
37
- identifier(expression[:c])
38
- when :number
39
- number(expression[:c])
40
- when :text
41
- text(expression[:c])
42
- when :paren
43
- paren = !opts[:strip_paren]
44
- if paren
45
- if opts[:single_child]
46
- brace(expression[:lparen]) if expression[:lparen]
47
- append(expression[:e], :single_child => true)
48
- brace(expression[:rparen]) if expression[:rparen]
49
- else
50
- row do
51
- brace(expression[:lparen]) if expression[:lparen]
52
- append(expression[:e], :single_child => true)
53
- brace(expression[:rparen]) if expression[:rparen]
54
- end
55
- end
56
- else
57
- append(expression[:e])
58
- end
59
- when :font
60
- #TODO - currently ignored
61
- when :unary
62
- operator = expression[:operator]
63
- tag(operator) do
64
- append(expression[:s], :single_child => true, :strip_paren => true)
65
- end
66
- when :binary
67
- operator = expression[:operator]
68
- if operator == :frac
69
- append_fraction(expression[:s1],expression[:s2])
70
- elsif operator == :sub
71
- append_subsup(expression[:s1],expression[:s2],nil)
72
- elsif operator == :sup
73
- append_subsup(expression[:s1],nil,expression[:s2])
74
- elsif operator == :under
75
- append_underover(expression[:s1],expression[:s2],nil)
76
- elsif operator == :over
77
- append_underover(expression[:s1],nil,expression[:s2])
78
- else
79
- tag(operator) do
80
- append(expression[:s1], :strip_paren => true)
81
- append(expression[:s2], :strip_paren => true)
82
- end
83
- end
84
- when :ternary
85
- operator = expression[:operator]
86
- if operator == :subsup
87
- append_subsup(expression[:s1],expression[:s2],expression[:s3])
88
- elsif operator == :underover
89
- # TODO: Handle over/under braces in some way? SVG maybe?
90
- append_underover(expression[:s1],expression[:s2],expression[:s3])
91
- end
92
- when :matrix
31
+ ZWJ = "\u200D"
32
+
33
+ def append_row(expressions)
34
+ row do
35
+ expressions.each { |e| append(e) }
36
+ end
37
+ end
38
+
39
+ def append_operator(operator)
40
+ operator(operator)
41
+ end
42
+
43
+ def append_identifier(identifier)
44
+ identifier(identifier)
45
+ end
46
+
47
+ def append_text(text)
48
+ text(text)
49
+ end
50
+
51
+ def append_number(number)
52
+ number(number)
53
+ end
54
+
55
+ def append_sqrt(expression)
56
+ tag("sqrt") do
57
+ append(child, :row => :omit)
58
+ end
59
+ end
60
+
61
+ def append_cancel(expression)
62
+ #TODO - currently ignored
63
+ append(expression)
64
+ end
65
+
66
+ def append_root(base, index)
67
+ tag("sqrt") do
68
+ append(base)
69
+ append(index)
70
+ end
71
+ end
72
+
73
+ def append_font(style, e)
74
+ #TODO - currently ignored
75
+ append(e)
76
+ end
77
+
78
+ def append_color(color, expression)
79
+ #TODO - currently ignored
80
+ append(expression)
81
+ end
82
+
83
+ def append_matrix(lparen, rows, rparen)
84
+ row do
85
+ # Figures out a font size for the braces, based on the height of the matrix.
86
+ # NOTE: This does not currently consider the size of each element within the matrix.
87
+ brace_height = "font-size: " + rows.length.to_s + "00%;"
88
+
89
+ if lparen
90
+ brace(lparen, {:style => brace_height})
91
+ else
92
+ blank(ZWJ)
93
+ end
94
+ matrix_width = "grid-template-columns:repeat(" + rows[0].length.to_s + ",1fr);"
95
+ matrix_height = "grid-template-rows:repeat(" + rows.length.to_s + ",1fr);"
96
+
97
+ matrix({:style => (matrix_width + matrix_height)}) do
98
+ rows.each do |row|
99
+ row.each do |col|
93
100
  row do
94
- # Figures out a font size for the braces, based on the height of the matrix.
95
- # NOTE: This does not currently consider the size of each element within the matrix.
96
- brace_height = "font-size: " + expression[:rows].length.to_s + "00%;"
97
-
98
- if expression[:lparen]
99
- brace(expression[:lparen], {:style => brace_height})
100
- else
101
- blank("&zwj;")
102
- end
103
- matrix_width = "grid-template-columns:repeat(" + expression[:rows][0].length.to_s + ",1fr);"
104
- matrix_height = "grid-template-rows:repeat(" + expression[:rows].length.to_s + ",1fr);"
105
-
106
- matrix({:style => (matrix_width + matrix_height)}) do
107
- expression[:rows].each do |row|
108
- row.each do |col|
109
- row do
110
- append(col)
111
- end
112
- end
113
- end
114
- end
115
- if expression[:rparen]
116
- brace(expression[:rparen], {:style => brace_height})
117
- else
118
- blank("&zwj;")
119
- end
101
+ append(col)
120
102
  end
103
+ end
121
104
  end
105
+ end
106
+ if rparen
107
+ brace(rparen, {:style => brace_height})
108
+ else
109
+ blank(ZWJ)
110
+ end
122
111
  end
123
112
  end
124
-
113
+
114
+ def append_operator_unary(operator, expression)
115
+ tag(operator) do
116
+ append(expression, :row => :omit)
117
+ end
118
+ end
119
+
120
+ def append_identifier_unary(identifier, expression)
121
+ row do
122
+ identifier(identifier)
123
+ append(expression, :row => :omit)
124
+ end
125
+ end
126
+
127
+ def append_paren(lparen, e, rparen, opts = {})
128
+ if opts[:row] == :omit
129
+ brace(lparen) if lparen
130
+ append(e, :row => :omit)
131
+ brace(rparen) if rparen
132
+ else
133
+ row do
134
+ brace(lparen) if lparen
135
+ append(e, :row => :omit)
136
+ brace(rparen) if rparen
137
+ end
138
+ end
139
+ end
140
+
125
141
  def append_subsup(base, sub, sup)
126
142
  append(base)
127
143
  subsup do
128
144
  if sup
129
145
  smaller do
130
- append(sup, :strip_paren => true)
146
+ append(sup)
131
147
  end
132
148
  else
133
- smaller("&zwj;")
149
+ smaller(ZWJ)
134
150
  end
135
151
  if sub
136
152
  smaller do
137
- append(sub, :strip_paren => true)
153
+ append(sub)
138
154
  end
139
155
  else
140
- smaller("&zwj;")
156
+ smaller(ZWJ)
141
157
  end
142
158
  end
143
159
  end
144
-
160
+
145
161
  def append_underover(base, under, over)
146
- blank("&zwj;")
162
+ # TODO: Handle over/under braces in some way? SVG maybe?
163
+ blank(ZWJ)
147
164
  underover do
148
165
  smaller do
149
166
  if over
150
- append(over, :strip_paren => true)
167
+ append(over)
151
168
  else
152
- blank("&zwj;")
169
+ blank(ZWJ)
153
170
  end
154
171
  end
155
172
  append(base)
156
173
  smaller do
157
174
  if under
158
- append(under, :strip_paren => true)
175
+ append(under)
159
176
  else
160
- blank("&zwj;")
177
+ blank(ZWJ)
161
178
  end
162
179
  end
163
180
  end
164
181
  end
165
-
182
+
166
183
  def append_fraction(numerator, denominator)
167
- blank("&zwj;")
184
+ blank(ZWJ)
168
185
  fraction do
169
186
  fraction_row do
170
187
  fraction_cell do
171
188
  smaller do
172
189
  row do
173
- append(numerator, :strip_paren => true)
190
+ append(numerator)
174
191
  end
175
192
  end
176
193
  end
@@ -179,7 +196,7 @@ module AsciiMath
179
196
  fraction_cell do
180
197
  smaller do
181
198
  row do
182
- append(denominator, :strip_paren => true)
199
+ append(denominator)
183
200
  end
184
201
  end
185
202
  end
@@ -190,7 +207,7 @@ module AsciiMath
190
207
  def method_missing(meth, *args, &block)
191
208
  tag(meth, *args, &block)
192
209
  end
193
-
210
+
194
211
  def tag(tag, *args)
195
212
  attrs = args.last.is_a?(Hash) ? args.pop : {}
196
213
  text = args.last.is_a?(String) ? args.pop : ''
@@ -203,7 +220,19 @@ module AsciiMath
203
220
 
204
221
  if block_given? || text
205
222
  @html << '>'
206
- @html << text
223
+ text.each_codepoint do |cp|
224
+ if cp == 38
225
+ @html << "&amp;"
226
+ elsif cp == 60
227
+ @html << "&lt;"
228
+ elsif cp == 62
229
+ @html << "&gt;"
230
+ elsif cp > 127
231
+ @html << "&#x#{cp.to_s(16).upcase};"
232
+ else
233
+ @html << cp
234
+ end
235
+ end
207
236
  yield if block_given?
208
237
  @html << '</span>'
209
238
  else
@@ -214,7 +243,7 @@ module AsciiMath
214
243
 
215
244
  class Expression
216
245
  def to_html(prefix = "", inline = true, attrs = {})
217
- HTMLBuilder.new(prefix).append_expression(@parsed_expression, inline, attrs).to_s
246
+ HTMLBuilder.new(:prefix => prefix, :inline => inline).append_expression(ast, attrs).to_s
218
247
  end
219
248
  end
220
- end
249
+ end
@@ -0,0 +1,400 @@
1
+ require_relative 'ast'
2
+
3
+ module AsciiMath
4
+ class LatexBuilder
5
+ attr_reader :symbol_table
6
+
7
+ def initialize(symbol_table = nil)
8
+ @latex = ''
9
+ @symbol_table = symbol_table.nil? ? DEFAULT_DISPLAY_SYMBOL_TABLE : symbol_table
10
+ end
11
+
12
+ def to_s
13
+ @latex
14
+ end
15
+
16
+ def append_expression(expression)
17
+ append(expression)
18
+ self
19
+ end
20
+
21
+ DEFAULT_DISPLAY_SYMBOL_TABLE = {
22
+ :plus => ?+,
23
+ :minus => ?-,
24
+ :ast => ?*,
25
+ :slash => ?/,
26
+ :eq => ?=,
27
+ :ne => "\\neq",
28
+ :assign => ":=",
29
+ :lt => ?<,
30
+ :gt => ?>,
31
+ :sub => "\\text{–}",
32
+ :sup => "\\text{^}",
33
+ :implies => "\\Rightarrow",
34
+ :iff => "\\Leftrightarrow",
35
+ :if => "\\operatorname{if}",
36
+ :and => "\\operatorname{and}",
37
+ :or => "\\operatorname{or}",
38
+ :lparen => ?(,
39
+ :rparen => ?),
40
+ :lbracket => ?[,
41
+ :rbracket => ?],
42
+ :lbrace => "\\{",
43
+ :rbrace => "\\}",
44
+ :lvert => "\\lVert",
45
+ :rvert => "\\rVert",
46
+ :vbar => ?|,
47
+ nil => ?.,
48
+ :integral => "\\int",
49
+ :dx => "dx",
50
+ :dy => "dy",
51
+ :dz => "dz",
52
+ :dt => "dt",
53
+ :contourintegral => "\\oint",
54
+ :Lim => "\\operatorname{Lim}",
55
+ :Sin => "\\operatorname{Sin}",
56
+ :Cos => "\\operatorname{Cos}",
57
+ :Tan => "\\operatorname{Tan}",
58
+ :Sinh => "\\operatorname{Sinh}",
59
+ :Cosh => "\\operatorname{Cosh}",
60
+ :Tanh => "\\operatorname{Tanh}",
61
+ :Cot => "\\operatorname{Cot}",
62
+ :Sec => "\\operatorname{Sec}",
63
+ :Csc => "\\operatorname{Csc}",
64
+ :sech => "\\operatorname{sech}",
65
+ :csch => "\\operatorname{csch}",
66
+ :Abs => "\\operatorname{Abs}",
67
+ :Log => "\\operatorname{Log}",
68
+ :Ln => "\\operatorname{Ln}",
69
+ :lcm => "\\operatorname{lcm}",
70
+ :lub => "\\operatorname{lub}",
71
+ :glb => "\\operatorname{glb}",
72
+ :partial => "\\del",
73
+ :prime => ?',
74
+ :tilde => "\\~",
75
+ :nbsp => "\\;",
76
+ :lceiling => "\\lceil",
77
+ :rceiling => "\\rceil",
78
+ :dstruck_captial_c => "\\mathbb{C}",
79
+ :dstruck_captial_n => "\\mathbb{N}",
80
+ :dstruck_captial_q => "\\mathbb{Q}",
81
+ :dstruck_captial_r => "\\mathbb{R}",
82
+ :dstruck_captial_z => "\\mathbb{Z}",
83
+ :f => "f",
84
+ :g => "g",
85
+ :to => "\\rightarrow",
86
+ :bold => "\\mathbf",
87
+ :double_struck => "\\mathbb",
88
+ :italic => "\\mathit",
89
+ :bold_italic => "\\mathbf",
90
+ :script => "\\mathscr",
91
+ :bold_script => "\\mathscr",
92
+ :monospace => "\\mathtt",
93
+ :fraktur => "\\mathfrak",
94
+ :bold_fraktur => "\\mathfrak",
95
+ :sans_serif => "\\mathsf",
96
+ :bold_sans_serif => "\\mathsf",
97
+ :sans_serif_italic => "\\mathsf",
98
+ :sans_serif_bold_italic => "\\mathsf",
99
+ }.freeze
100
+
101
+ private
102
+
103
+ SPECIAL_CHARACTERS = [?&, ?%, ?$, ?#, ?_, ?{, ?}, ?~, ?^, ?[, ?]].map(&:ord)
104
+
105
+ COLOURS = {
106
+ [0xFF, 0xFF, 0xFF] => "white",
107
+ [0xFF, 0x00, 0x00] => "red",
108
+ [0x00, 0xFF, 0x00] => "green",
109
+ [0x00, 0x00, 0xFF] => "blue",
110
+ [0xBF, 0x80, 0x40] => "brown",
111
+ [0x00, 0xAD, 0xEF] => "cyan",
112
+ [0x40, 0x40, 0x40] => "darkgray",
113
+ [0x80, 0x80, 0x80] => "gray",
114
+ [0xBF, 0xBF, 0xBF] => "lightgray",
115
+ [0xA4, 0xDB, 0x00] => "lime",
116
+ [0xE9, 0x00, 0x8A] => "magenta",
117
+ [0x8E, 0x86, 0x00] => "olive",
118
+ [0xFF, 0x80, 0x00] => "orange",
119
+ [0xFF, 0xBF, 0xBF] => "pink",
120
+ [0xBF, 0x00, 0x40] => "purple",
121
+ [0x00, 0x80, 0x80] => "teal",
122
+ [0x80, 0x00, 0x80] => "violet",
123
+ [0xFF, 0xF2, 0x00] => "yellow",
124
+ }
125
+
126
+ def append(expression, separator = " ")
127
+ case expression
128
+ when Array
129
+ expression.each { |e| append(e, separator) }
130
+ when String
131
+ @latex << expression
132
+ when Symbol
133
+ @latex << expression.to_s
134
+ when AsciiMath::AST::Sequence, AsciiMath::AST::MatrixRow
135
+ c = expression.length
136
+
137
+ expression.each do |e|
138
+ c -= 1
139
+ append(e)
140
+ @latex << separator if c > 0
141
+ end
142
+
143
+ when AsciiMath::AST::Symbol
144
+ @latex << resolve_symbol(expression.value)
145
+
146
+ when AsciiMath::AST::Identifier
147
+ append_escaped(expression.value)
148
+
149
+ when AsciiMath::AST::Text
150
+ text do
151
+ append_escaped(expression.value)
152
+ end
153
+
154
+ when AsciiMath::AST::Number
155
+ @latex << expression.value
156
+
157
+ when AsciiMath::AST::Paren
158
+ parens(expression.lparen, expression.rparen, expression.expression)
159
+
160
+ when AsciiMath::AST::Group
161
+ append(expression.expression)
162
+
163
+ when AsciiMath::AST::SubSup
164
+ sub = expression.sub_expression
165
+ sup = expression.sup_expression
166
+ e = expression.base_expression
167
+
168
+ curly(e)
169
+
170
+ if sub
171
+ @latex << "_"
172
+ curly(sub)
173
+ end
174
+
175
+ if sup
176
+ @latex << "^"
177
+ curly(sup)
178
+ end
179
+
180
+ when AsciiMath::AST::UnaryOp
181
+ op = expression.operator.value
182
+
183
+ case op
184
+ when :norm
185
+ parens(:lvert, :rvert, expression.operand)
186
+ when :floor
187
+ parens(:lfloor, :rfloor, expression.operand)
188
+ when :ceil
189
+ parens(:lceiling, :rceiling, expression.operand)
190
+ when :overarc
191
+ overset do
192
+ @latex << "\\frown"
193
+ end
194
+
195
+ curly do
196
+ append(expression.operand)
197
+ end
198
+ else
199
+ macro(op) do
200
+ append(expression.operand)
201
+ end
202
+ end
203
+
204
+ when AsciiMath::AST::BinaryOp, AsciiMath::AST::InfixOp
205
+ op = expression.operator.value
206
+
207
+ case op
208
+ when :root
209
+ macro("sqrt", expression.operand1) do
210
+ append(expression.operand2)
211
+ end
212
+
213
+ when :color
214
+ curly do
215
+ color_value = expression.operand1
216
+ red = color_value.red
217
+ green = color_value.green
218
+ blue = color_value.blue
219
+
220
+ if COLOURS.has_key? [red, green, blue]
221
+ color do
222
+ @latex << COLOURS[[red, green, blue]]
223
+ end
224
+ else
225
+ color('RGB') do
226
+ @latex << red.to_s << ',' << green.to_s << ',' << blue.to_s
227
+ end
228
+ end
229
+
230
+ @latex << " "
231
+ append(expression.operand2)
232
+ end
233
+
234
+ else
235
+ @latex << resolve_symbol(op)
236
+
237
+ curly do
238
+ append(expression.operand1)
239
+ end
240
+
241
+ curly do
242
+ append(expression.operand2)
243
+ end
244
+ end
245
+
246
+ when AsciiMath::AST::Matrix
247
+ len = expression.length - 1
248
+
249
+ parens(expression.lparen, expression.rparen) do
250
+ c = expression.length
251
+ @latex << "\\begin{matrix} "
252
+
253
+ expression.each do |row|
254
+ c -= 1
255
+ append(row, " & ")
256
+ @latex << " \\\\ " if c > 0
257
+ end
258
+
259
+ @latex << " \\end{matrix}"
260
+ end
261
+ end
262
+ end
263
+
264
+ def macro(macro, *args)
265
+ @latex << resolve_symbol(macro)
266
+
267
+ if args.length != 0
268
+ @latex << "["
269
+ append(args, "][")
270
+ @latex << "]"
271
+ end
272
+
273
+ if block_given?
274
+ curly do
275
+ yield self
276
+ end
277
+ end
278
+ end
279
+
280
+ def method_missing(meth, *args, &block)
281
+ macro(meth, *args, &block)
282
+ end
283
+
284
+ def parens(lparen, rparen, content = nil, &block)
285
+ l = lparen.is_a?(AsciiMath::AST::Symbol) ? lparen.value : lparen
286
+ r = rparen.is_a?(AsciiMath::AST::Symbol) ? rparen.value : rparen
287
+
288
+ if block_given?
289
+ if l || r
290
+ @latex << "\\left " << resolve_symbol(l) << " "
291
+ yield self
292
+ @latex << " \\right " << resolve_symbol(r)
293
+ else
294
+ yield self
295
+ end
296
+ else
297
+ needs_left_right = !is_small(content)
298
+
299
+ @latex << "\\left " if needs_left_right
300
+ @latex << resolve_symbol(l) << " " if l or needs_left_right
301
+
302
+ append(content)
303
+
304
+ @latex << " \\right" if needs_left_right
305
+ @latex << " " << resolve_symbol(r) if r or needs_left_right
306
+ end
307
+ end
308
+
309
+ def curly(expression = nil, &block)
310
+ if block_given?
311
+ @latex << ?{
312
+ yield self
313
+ @latex << ?}
314
+ else
315
+ case expression
316
+ when AsciiMath::AST::Symbol, AsciiMath::AST::Text
317
+ append(expression)
318
+ return
319
+ when AsciiMath::AST::Identifier, AsciiMath::AST::Number
320
+ if expression.value.length <= 1
321
+ append(expression)
322
+ return
323
+ end
324
+ end
325
+
326
+ @latex << ?{
327
+ append(expression)
328
+ @latex << ?}
329
+ end
330
+ end
331
+
332
+ def append_escaped(text)
333
+ text.each_codepoint do |cp|
334
+ @latex << "\\" if SPECIAL_CHARACTERS.include? cp
335
+ @latex << cp
336
+ end
337
+ end
338
+
339
+ def resolve_symbol(s)
340
+ symbol = @symbol_table[s]
341
+
342
+ case symbol
343
+ when String
344
+ return symbol
345
+ when Hash
346
+ return symbol[:value]
347
+ when nil
348
+ return "\\#{s.to_s}"
349
+ else
350
+ raise "Invalid entry in symbol table"
351
+ end
352
+ end
353
+
354
+ def is_small(e)
355
+ case e
356
+ when AsciiMath::AST::SubSup
357
+ is_very_small(e.sub_expression) and is_very_small(e.sup_expression) and is_very_small(e.base_expression)
358
+ when AsciiMath::AST::Sequence
359
+ e.all? { |s| is_small(s) }
360
+ else
361
+ is_very_small(e)
362
+ end
363
+ end
364
+
365
+ def is_very_small(e)
366
+ case e
367
+ when AsciiMath::AST::Identifier, AsciiMath::AST::Number
368
+ e.value.length <= 1
369
+ when AsciiMath::AST::Symbol
370
+ case e.value
371
+ when :plus, :minus, :cdot, :dx, :dy, :dz, :dt, :f, :g, :mod
372
+ true
373
+ else
374
+ false
375
+ end
376
+ when AsciiMath::AST::UnaryOp
377
+ case e.operator
378
+ when :hat, :overline, :underline, :vec, :dot, :ddot, :color
379
+ is_very_small(e.operand)
380
+ else
381
+ false
382
+ end
383
+ when AsciiMath::AST::Group
384
+ is_very_small(e.expression)
385
+ when AsciiMath::AST::Sequence
386
+ e.all? { |s| is_very_small(e) }
387
+ when nil
388
+ true
389
+ else
390
+ false
391
+ end
392
+ end
393
+ end
394
+
395
+ class Expression
396
+ def to_latex(symbol_table = nil)
397
+ LatexBuilder.new(symbol_table).append_expression(ast).to_s
398
+ end
399
+ end
400
+ end