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