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