mathemagical 0.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.
@@ -0,0 +1,1123 @@
1
+ require "mathemagical/latex/builtin"
2
+
3
+ module Mathemagical
4
+ module LaTeX
5
+ MBEC = /\\.|[^\\]/m
6
+
7
+ module RE
8
+ SPACE = /(?:\s|%.*$)/
9
+ NUMERICS = /(?:\.\d+)|(?:\d+(\.\d+)?)/
10
+ OPERATORS = /[,\.\+\-\*=\/\(\)\[\]<>"|;:!]/
11
+ ALPHABETS = /[a-zA-Z]/
12
+ BLOCK = /\A\{(.*?)\}\z/m
13
+ OPTION = /\A\[(.*)\]\z/m
14
+ COMMANDS = /\\([a-zA-Z]+|[^a-zA-Z])/
15
+ WBSLASH = /\\\\/
16
+ BRACES = /\A([.|\[\]\(\)<>])\z/
17
+ end
18
+
19
+ module Font
20
+ NORMAL = 0
21
+ BOLD = 1
22
+ BLACKBOLD = 2
23
+ SCRIPT = 3
24
+ FRAKTUR = 4
25
+ ROMAN = 5
26
+ BOLD_ITALIC = 6
27
+ end
28
+
29
+ class BlockNotClosed < StandardError; end
30
+ class NotEnvironment < StandardError; end
31
+ class EnvironmentNotEnd < StandardError; end
32
+ class NeedParameter < StandardError; end
33
+ class EndMismatchToBegin < StandardError; end
34
+ class OptionNotClosed < StandardError; end
35
+
36
+ class Scanner < StringScanner
37
+ def done
38
+ self.string[0, pos]
39
+ end
40
+
41
+ def scan_space
42
+ _scan(/#{RE::SPACE}+/)
43
+ end
44
+
45
+ def skip_space_and(check_mode)
46
+ opos = pos
47
+ scan_space
48
+ r = yield
49
+ self.pos = opos if check_mode || !r
50
+ r
51
+ end
52
+
53
+ unless instance_methods.include?("_eos?")
54
+ alias :_eos? :eos?
55
+ alias :_check :check
56
+ alias :_scan :scan
57
+ end
58
+
59
+ def check(re)
60
+ skip_space_and(true){_check(re)}
61
+ end
62
+
63
+ def scan(re)
64
+ skip_space_and(false){_scan(re)}
65
+ end
66
+
67
+ def eos?
68
+ _eos? || _check(/#{RE::SPACE}+\z/)
69
+ end
70
+
71
+ def check_command
72
+ check(RE::COMMANDS)
73
+ end
74
+
75
+ def scan_command
76
+ scan(RE::COMMANDS)
77
+ end
78
+
79
+ def peek_command
80
+ check_command ? self[1] : nil
81
+ end
82
+
83
+ def check_block
84
+ skip_space_and(true){scan_block}
85
+ end
86
+
87
+ def scan_block
88
+ return nil unless scan(/\{/)
89
+ block = "{"
90
+ bpos = pos-1
91
+ nest = 1
92
+ while _scan(/(#{MBEC}*?)([\{\}])/)
93
+ block << matched
94
+ case self[2]
95
+ when "{"
96
+ nest+=1
97
+ when "}"
98
+ nest-=1
99
+ break if nest==0
100
+ end
101
+ end
102
+ if nest>0
103
+ self.pos = bpos
104
+ raise BlockNotClosed
105
+ end
106
+ self.pos = bpos
107
+ _scan(/\A\{(#{Regexp.escape(block[RE::BLOCK, 1].to_s)})\}/)
108
+ end
109
+
110
+ def check_any(remain_space=false)
111
+ skip_space_and(true){scan_any(remain_space)}
112
+ end
113
+
114
+ def scan_any(remain_space=false)
115
+ p = pos
116
+ scan_space
117
+ r = remain_space ? matched.to_s : ""
118
+ case
119
+ when s = scan_block
120
+ when s = scan_command
121
+ else
122
+ unless _scan(/./) || remain_space
123
+ self.pos = p
124
+ return nil
125
+ end
126
+ s = matched.to_s
127
+ end
128
+ r << s
129
+ end
130
+
131
+ def scan_option
132
+ return nil unless scan(/\[/)
133
+ opt = "["
134
+ p = pos-1
135
+ until (s=scan_any(true)) =~ /\A#{RE::SPACE}*\]\z/
136
+ opt << s
137
+ if eos?
138
+ self.pos = p
139
+ raise OptionNotClosed
140
+ end
141
+ end
142
+ opt << s
143
+ self.pos = p
144
+ _scan(/\A\[(#{Regexp.escape(opt[RE::OPTION, 1].to_s)})\]/)
145
+ end
146
+
147
+ def check_option
148
+ skip_space_and(true){scan_option}
149
+ end
150
+ end
151
+
152
+ class ParseError < StandardError
153
+ attr_accessor :rest, :done
154
+ def initialize(message, rest = "", done = "")
155
+ @done = done
156
+ @rest = rest
157
+ super(message)
158
+ end
159
+
160
+ def inspect
161
+ "#{message} : '#{@done}' / '#{@rest}'\n"+backtrace[0..5].join("\n")
162
+ end
163
+ end
164
+
165
+ class Macro
166
+ class Command
167
+ attr_reader :num, :body, :option
168
+ def initialize(n, b, o)
169
+ @num = n
170
+ @body = b
171
+ @option = o
172
+ end
173
+ end
174
+
175
+ class Environment
176
+ attr_reader :num, :beginning, :ending, :option
177
+ def initialize(n, b, e, o)
178
+ @num = n
179
+ @beginning = b
180
+ @ending = e
181
+ @option = o
182
+ end
183
+ end
184
+
185
+ def initialize
186
+ @commands = Hash.new
187
+ @environments = Hash.new
188
+ end
189
+
190
+ def parse_error(message, rest="", whole=nil)
191
+ rest = whole[/\A.*?(#{Regexp.escape(rest)}.*\z)/, 1] if whole
192
+ rest << @scanner.rest
193
+ done = @scanner.string[0, @scanner.string.size-rest.size]
194
+ ParseError.new(message, rest, done)
195
+ end
196
+
197
+ def parse(src)
198
+ @scanner = Scanner.new(src)
199
+ until @scanner.eos?
200
+ unless @scanner.scan_command
201
+ @scanner.scan_space
202
+ raise parse_error("Syntax error.")
203
+ end
204
+ case @scanner[1]
205
+ when "newcommand"
206
+ parse_newcommand
207
+ when "newenvironment"
208
+ parse_newenvironment
209
+ else
210
+ raise parse_error("Syntax error.", @scanner.matched)
211
+ end
212
+ end
213
+ rescue BlockNotClosed => e
214
+ raise parse_error("Block not closed.")
215
+ rescue OptionNotClosed => e
216
+ raise parse_error("Option not closed.")
217
+ end
218
+
219
+ def scan_num_of_parameter
220
+ if @scanner.scan_option
221
+ raise parse_error("Need positive number.", @scanner[1]+"]") unless @scanner[1]=~/\A#{RE::SPACE}*\d+#{RE::SPACE}*\z/
222
+ @scanner[1].to_i
223
+ else
224
+ 0
225
+ end
226
+ end
227
+
228
+ def check_parameter_numbers(src, opt, whole)
229
+ s = Scanner.new(src)
230
+ until s.eos?
231
+ case
232
+ when s.scan(/#{MBEC}*?\#(\d+|.)/)
233
+ raise parse_error("Need positive number.") unless s[1]=~/\d+/
234
+ raise parse_error("Parameter \# too large.", s[1]+s.rest, whole) if s[1].to_i>opt
235
+ else
236
+ return nil
237
+ end
238
+ end
239
+ end
240
+
241
+ def parse_newcommand
242
+ case
243
+ when @scanner.scan_block
244
+ s = Scanner.new(@scanner[1])
245
+ raise parse_error("Need newcommand.", s.rest+"}") unless s.scan_command
246
+ com = s[1]
247
+ raise parse_error("Syntax error." ,s.rest+"}") unless s.eos?
248
+ when @scanner.scan_command
249
+ s = Scanner.new(@scanner[1])
250
+ com = s.scan_command
251
+ else
252
+ raise parse_error("Need newcommand.")
253
+ end
254
+
255
+ optnum = scan_num_of_parameter
256
+ opt = @scanner.scan_option ? @scanner[1] : nil
257
+
258
+ case
259
+ when @scanner.scan_block
260
+ body = @scanner[1]
261
+ when @scanner.scan_command
262
+ body = @scanner.matched
263
+ else
264
+ body = @scanner.scan(/./)
265
+ end
266
+
267
+ raise parse_error("Need parameter.") unless body
268
+
269
+ check_parameter_numbers(body, optnum, @scanner.matched)
270
+
271
+ optnum-=1 if opt
272
+ @commands[com] = Command.new(optnum, body, opt)
273
+ end
274
+
275
+ def parse_newenvironment
276
+ case
277
+ when @scanner.scan_block
278
+ env = @scanner[1]
279
+ when @scanner.scan_command
280
+ raise ParseError.new
281
+ when @scanner.scan(/./)
282
+ env = @scanner.matched
283
+ end
284
+ raise parse_error("Syntax error.", env[/\A.*?(\\.*\z)/, 1], @scanner.matched) if env=~/\\/
285
+
286
+ optnum = scan_num_of_parameter
287
+ opt = @scanner.scan_option ? @scanner[1] : nil
288
+
289
+ b = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
290
+ raise parse_error("Need begin block.") unless b
291
+ check_parameter_numbers(b, optnum, @scanner.matched)
292
+ e = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
293
+ raise parse_error("Need end block.") unless e
294
+ check_parameter_numbers(e, optnum, @scanner.matched)
295
+
296
+ optnum -= 1 if opt
297
+ @environments[env] = Environment.new(optnum, b, e, opt)
298
+ end
299
+
300
+ def commands(com)
301
+ @commands[com]
302
+ end
303
+
304
+ def expand_command(com, params, opt=nil)
305
+ return nil unless @commands.has_key?(com)
306
+ c = @commands[com]
307
+ opt = c.option if c.option && !opt
308
+ params.unshift(opt) if c.option
309
+ raise ParseError.new("Need more parameter.") if params.size < c.num
310
+
311
+ c.body.gsub(/(#{MBEC}*?)\#(\d+)/) do
312
+ $1.to_s << params[$2.to_i-1]
313
+ end
314
+ end
315
+
316
+ def environments(env)
317
+ @environments[env]
318
+ end
319
+
320
+ def expand_environment(env, body, params, opt=nil)
321
+ return nil unless @environments.has_key?(env)
322
+ e = @environments[env]
323
+ opt = e.option if e.option && !opt
324
+ params.unshift(opt) if e.option
325
+ raise ParseError.new("Need more parameter.") if params.size < e.num
326
+
327
+ bg = e.beginning.gsub(/(#{MBEC}*?)\#(\d+)/) do
328
+ $1.to_s << params[$2.to_i-1]
329
+ end
330
+
331
+ en = e.ending.gsub(/(#{MBEC}*?)\#(\d+)/) do
332
+ $1.to_s << params[$2.to_i-1]
333
+ end
334
+
335
+ " #{bg} #{body} #{en} "
336
+ end
337
+ end
338
+
339
+ module BuiltinCommands; end
340
+ module BuiltinGroups; end
341
+ module BuiltinEnvironments; end
342
+
343
+ class Parser
344
+ class CircularReferenceCommand < StandardError; end
345
+
346
+ include LaTeX
347
+
348
+ include BuiltinEnvironments
349
+ include BuiltinGroups
350
+ include BuiltinCommands
351
+
352
+ BUILTIN_MACRO = <<'EOS'
353
+ \newenvironment{smallmatrix}{\begin{matrix}}{\end{matrix}}
354
+ \newenvironment{pmatrix}{\left(\begin{matrix}}{\end{matrix}\right)}
355
+ \newenvironment{bmatrix}{\left[\begin{matrix}}{\end{matrix}\right]}
356
+ \newenvironment{Bmatrix}{\left\{\begin{matrix}}{\end{matrix}\right\}}
357
+ \newenvironment{vmatrix}{\left|\begin{matrix}}{\end{matrix}\right|}
358
+ \newenvironment{Vmatrix}{\left\|\begin{matrix}}{\end{matrix}\right\|}
359
+ EOS
360
+
361
+ attr_accessor :unsecure_entity
362
+ attr_reader :macro
363
+ attr_reader :symbol_table
364
+
365
+ def initialize(opt={})
366
+ @unsecure_entity = false
367
+ @entities = Hash.new
368
+ @commands = Hash.new
369
+ @symbols = Hash.new
370
+ @delimiters = Array.new
371
+ @group_begins = Hash.new
372
+ @group_ends = Hash.new
373
+ @macro = Macro.new
374
+ @macro.parse(BUILTIN_MACRO)
375
+ @expanded_command = Array.new
376
+ @expanded_environment = Array.new
377
+ @symbol_table = opt[:symbol] || Mathemagical::Symbol::Default
378
+ @symbol_table = Mathemagical::Symbol::MAP[@symbol_table] if @symbol_table.is_a?(::Symbol)
379
+
380
+ super()
381
+ end
382
+
383
+ def add_entity(list)
384
+ list.each do |i|
385
+ @entities[i] = true
386
+ end
387
+ end
388
+
389
+ def parse(src, displaystyle=false)
390
+ @ds = displaystyle
391
+ @math = Math.new(@ds)
392
+ begin
393
+ parse_into(src, @math, Font::NORMAL)
394
+ rescue ParseError => e
395
+ e.done = src[0...(src.size - e.rest.size)]
396
+ raise
397
+ end
398
+ end
399
+
400
+ def push_container(container, scanner=@scanner, font=@font)
401
+ data = [@container, @scanner, @font]
402
+ @container, @scanner, @font = [container, scanner, font]
403
+ begin
404
+ yield container
405
+ container
406
+ ensure
407
+ @container, @scanner, @font = data
408
+ end
409
+ end
410
+
411
+ def add_plugin(plugin)
412
+ self.extend(plugin)
413
+ end
414
+
415
+ def add_commands(*a)
416
+ if a.size==1 && Hash===a[0]
417
+ @commands.merge!(a[0])
418
+ else
419
+ a.each{|i| @commands[i] = false}
420
+ end
421
+ end
422
+
423
+ def add_multi_command(m, *a)
424
+ a.each{|i| @commands[i] = m}
425
+ end
426
+
427
+ def add_sym_cmd(hash)
428
+ @symbols.merge!(hash)
429
+ end
430
+
431
+ def add_delimiter(list)
432
+ @delimiters.concat(list)
433
+ end
434
+
435
+ def add_group(begin_name, end_name, method=nil)
436
+ @group_begins[begin_name] = method
437
+ @group_ends[end_name] = begin_name
438
+ end
439
+
440
+ private
441
+ def parse_into(src, parent, font=nil)
442
+ orig = [@scanner, @container, @font, @ds]
443
+ @scanner = Scanner.new(src)
444
+ @container = parent
445
+ @font = font if font
446
+ begin
447
+ until @scanner.eos?
448
+ @container << parse_to_element(true)
449
+ end
450
+ @container
451
+ rescue BlockNotClosed => e
452
+ raise ParseError.new("Block not closed.", @scanner.rest)
453
+ rescue NotEnvironment => e
454
+ raise ParseError.new("Not environment.", @scanner.rest)
455
+ rescue EnvironmentNotEnd => e
456
+ raise ParseError.new("Environment not end.", @scanner.rest)
457
+ rescue OptionNotClosed => e
458
+ raise ParseError.new("Option not closed.", @scanner.rest)
459
+ rescue ParseError => e
460
+ e.rest << @scanner.rest.to_s
461
+ raise
462
+ ensure
463
+ @scanner, @container, @font, @ds = orig
464
+ end
465
+ end
466
+
467
+ def parse_any(message = "Syntax error.")
468
+ raise ParseError.new(message) unless @scanner.scan_any
469
+ s = @scanner
470
+ @scanner = Scanner.new(@scanner.matched)
471
+ begin
472
+ parse_to_element
473
+ ensure
474
+ @scanner = s
475
+ end
476
+ end
477
+
478
+ def parse_to_element(whole_group = false)
479
+ if whole_group && @group_begins.has_key?(@scanner.peek_command)
480
+ @scanner.scan_command
481
+ parse_group
482
+ else
483
+ case
484
+ when @scanner.scan(RE::NUMERICS)
485
+ parse_num
486
+ when @scanner.scan(RE::ALPHABETS)
487
+ parse_char
488
+ when @scanner.scan(RE::OPERATORS)
489
+ parse_operator
490
+ when @scanner.scan_block
491
+ parse_block
492
+ when @scanner.scan(/_/)
493
+ parse_sub
494
+ when @scanner.scan(/'+|\^/)
495
+ parse_sup
496
+ when @scanner.scan(/~/)
497
+ Space.new("1em")
498
+ when @scanner.scan_command
499
+ parse_command
500
+ else
501
+ raise ParseError.new('Syntax error.')
502
+ end
503
+ end
504
+ end
505
+
506
+ def parse_num
507
+ n = Number.new
508
+ n.extend(Variant).variant = Variant::BOLD if @font==Font::BOLD
509
+ n << @scanner.matched
510
+ end
511
+
512
+ def parse_char
513
+ c = @scanner.matched
514
+ i = Identifier.new
515
+ case @font
516
+ when Font::ROMAN
517
+ i.extend(Variant).variant = Variant::NORMAL
518
+ when Font::BOLD
519
+ i.extend(Variant).variant = Variant::BOLD
520
+ when Font::BOLD_ITALIC
521
+ i.extend(Variant).variant = Variant::BOLD_ITALIC
522
+ when Font::BLACKBOLD
523
+ c = symbol_table.convert("#{c}opf")
524
+ when Font::SCRIPT
525
+ c = symbol_table.convert("#{c}scr")
526
+ when Font::FRAKTUR
527
+ c = symbol_table.convert("#{c}fr")
528
+ end
529
+ i << c
530
+ end
531
+
532
+ def parse_operator
533
+ o = @scanner.matched
534
+ Operator.new.tap{|op| op[:stretchy]="false"} << o
535
+ end
536
+
537
+ def parse_block
538
+ os = @scanner
539
+ @scanner = Scanner.new(@scanner[1])
540
+ begin
541
+ push_container(Row.new) do |r|
542
+ r << parse_to_element(true) until @scanner.eos?
543
+ end
544
+ rescue ParseError => e
545
+ e.rest << '}'
546
+ raise
547
+ ensure
548
+ @scanner = os
549
+ end
550
+ end
551
+
552
+ def parse_sub
553
+ e = @container.pop
554
+ e = None.new unless e
555
+ e = SubSup.new(@ds && e.display_style, e) unless e.is_a?(SubSup)
556
+ raise ParseError.new("Double subscript.", "_") if e.sub
557
+ e.sub = parse_any("Subscript not exist.")
558
+ e
559
+ end
560
+
561
+ def parse_sup
562
+ e = @container.pop
563
+ e = None.new unless e
564
+ e = SubSup.new(@ds && e.display_style, e) unless e.is_a?(SubSup)
565
+ raise ParseError.new("Double superscript.", @scanner[0]) if e.sup
566
+ if /'+/=~@scanner[0]
567
+ prime = Operator.new
568
+ @scanner[0].size.times do
569
+ prime << symbol_table.convert("prime")
570
+ end
571
+ unless @scanner.scan(/\^/)
572
+ e.sup = prime
573
+ return e
574
+ end
575
+ end
576
+ sup = parse_any("Superscript not exist.")
577
+
578
+ if prime
579
+ unless sup.is_a?(Row)
580
+ r = Row.new
581
+ r << sup
582
+ sup = r
583
+ end
584
+ sup.children.insert(0, prime)
585
+ end
586
+
587
+ e.sup = sup
588
+ e
589
+ end
590
+
591
+ def entitize(str)
592
+ Mathemagical.encode(str.sub(/^(.*)$/){"&#{$1};"})
593
+ end
594
+
595
+ def parse_symbol_command(com, plain=false)
596
+ unless @symbols.include?(com)
597
+ @scanner.pos = @scanner.pos-(com.size+1)
598
+ raise ParseError.new("Undefined command: #{com}")
599
+ end
600
+ data = @symbols[com]
601
+ return nil unless data
602
+
603
+ data, s = data
604
+ su = data[0]
605
+ el = data[1]
606
+ el = :o unless el
607
+ s = com.dup.untaint.to_sym unless s
608
+ s = com if s.is_a?(String) && s.length==0
609
+
610
+ case el
611
+ when :I
612
+ el = Identifier.new
613
+ when :i
614
+ el = Identifier.new
615
+ el.extend(Variant).variant = Variant::NORMAL unless s.is_a?(String)&&s.length>1
616
+ when :o
617
+ el = Operator.new
618
+ el[:stretchy] = "false"
619
+ when :n
620
+ el = Number.new
621
+ else
622
+ raise ParseError.new("Inner data broken.")
623
+ end
624
+
625
+ case s
626
+ when Fixnum
627
+ s = Mathemagical.encode("&\#x#{s.to_s(16)};")
628
+ when ::Symbol
629
+ s = symbol_table.convert(s)
630
+ else
631
+ Mathemagical.encode(s)
632
+ end
633
+
634
+ return s if plain
635
+ el << s
636
+ el.as_display_style if su==:u
637
+ el
638
+ end
639
+
640
+ def parse_command
641
+ com = @scanner[1]
642
+ matched = @scanner.matched
643
+ pos = @scanner.pos-matched.size
644
+ macro = @macro.commands(com)
645
+ if macro
646
+ begin
647
+ flg = @expanded_command.include?(com)
648
+ @expanded_command.push(com)
649
+ raise CircularReferenceCommand if flg
650
+ option = (macro.option && @scanner.scan_option) ? @scanner[1] : nil
651
+ params = Array.new
652
+ (1..macro.num).each do
653
+ params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
654
+ raise ParseError.new("Need more parameter.") unless params.last
655
+ end
656
+ r = parse_into(@macro.expand_command(com, params, option), Array.new)
657
+ return r
658
+ rescue CircularReferenceCommand
659
+ if @expanded_command.size>1
660
+ raise
661
+ else
662
+ @scanner.pos = pos
663
+ raise ParseError.new("Circular reference.")
664
+ end
665
+ rescue ParseError => e
666
+ if @expanded_command.size>1
667
+ raise
668
+ else
669
+ @scanner.pos = pos
670
+ raise ParseError.new(%[Error in macro(#{e.message} "#{e.rest.strip}").])
671
+ end
672
+ ensure
673
+ @expanded_command.pop
674
+ end
675
+ elsif @commands.key?(com)
676
+ m = @commands[com]
677
+ m = com unless m
678
+ return __send__("cmd_#{m.to_s}")
679
+ end
680
+ parse_symbol_command(com)
681
+ end
682
+
683
+ def parse_mathfont(font)
684
+ f = @font
685
+ @font = font
686
+ begin
687
+ push_container(Row.new){|r| r << parse_any}
688
+ ensure
689
+ @font = f
690
+ end
691
+ end
692
+
693
+ def parse_group
694
+ font = @font
695
+ begin
696
+ g = @group_begins[@scanner[1]]
697
+ g = @scanner[1] unless g
698
+ __send__("grp_#{g.to_s}")
699
+ ensure
700
+ @font = font
701
+ end
702
+ end
703
+ end
704
+
705
+ module BuiltinCommands
706
+ OVERS = {'hat'=>'circ', 'breve'=>'smile', 'grave'=>'grave',
707
+ 'acute'=>'acute', 'dot'=>'sdot', 'ddot'=>'nldr', 'dddot'=>'mldr', 'tilde'=>'tilde',
708
+ 'bar'=>'macr', 'vec'=>'rightarrow', 'check'=>'vee', 'widehat'=>'circ',
709
+ 'overline'=>'macr', 'widetilde'=>'tilde', 'overbrace'=>'OverBrace'}
710
+ UNDERS = {'underbrace'=>'UnderBrace', 'underline'=>'macr'}
711
+
712
+ def initialize
713
+ add_commands("\\"=>:backslash)
714
+ add_commands("entity", "stackrel", "frac", "sqrt", "mbox")
715
+ add_multi_command(:hat_etc, *OVERS.keys)
716
+ add_multi_command(:underbrace_etc, *UNDERS.keys)
717
+ add_multi_command(:quad_etc, " ", "quad", "qquad", ",", ":", ";", "!")
718
+ add_multi_command(:it_etc, "it", "rm", "bf")
719
+ add_multi_command(:mathit_etc, "mathit", "mathrm", "mathbf", "bm", "mathbb", "mathscr", "mathfrak")
720
+ add_sym_cmd(Builtin::Symbol::MAP)
721
+ add_delimiter(Builtin::Symbol::DELIMITERS)
722
+
723
+ super
724
+ end
725
+
726
+ def cmd_backslash
727
+ @ds ? nil : Break.new
728
+ end
729
+
730
+ def cmd_hat_etc
731
+ com = @scanner[1]
732
+ Over.new(parse_any, Operator.new << entitize(OVERS[com]))
733
+ end
734
+
735
+ def cmd_underbrace_etc
736
+ com = @scanner[1]
737
+ Under.new(parse_any, Operator.new << entitize(UNDERS[com]))
738
+ end
739
+
740
+ def cmd_entity
741
+ param = @scanner.scan_block ? @scanner[1] : @scanner.scan(/./)
742
+ raise ParseError.new("Need parameter.") unless param
743
+ unless @unsecure_entity || @entities[param]
744
+ param =@scanner.matched[/\A\{#{RE::SPACE}*(.*\})\z/, 1] if @scanner.matched=~RE::BLOCK
745
+ @scanner.pos = @scanner.pos-(param.size)
746
+ raise ParseError.new("Unregistered entity.")
747
+ end
748
+ Operator.new << entitize(param)
749
+ end
750
+
751
+ def cmd_stackrel
752
+ o = parse_any; b = parse_any
753
+ Over.new(b, o)
754
+ end
755
+
756
+ def cmd_quad_etc
757
+ case @scanner[1]
758
+ when ' '
759
+ Space.new("1em")
760
+ when 'quad'
761
+ Space.new("1em")
762
+ when 'qquad'
763
+ Space.new("2em")
764
+ when ','
765
+ Space.new("0.167em")
766
+ when ':'
767
+ Space.new("0.222em")
768
+ when ';'
769
+ Space.new("0.278em")
770
+ when '!'
771
+ Space.new("-0.167em")
772
+ end
773
+ end
774
+
775
+ def cmd_it_etc
776
+ case @scanner[1]
777
+ when 'it'
778
+ @font = Font::NORMAL
779
+ when 'rm'
780
+ @font = Font::ROMAN
781
+ when 'bf'
782
+ @font = Font::BOLD
783
+ end
784
+ nil
785
+ end
786
+
787
+ def cmd_mathit_etc
788
+ case @scanner[1]
789
+ when 'mathit'
790
+ parse_mathfont(Font::NORMAL)
791
+ when 'mathrm'
792
+ parse_mathfont(Font::ROMAN)
793
+ when 'mathbf'
794
+ parse_mathfont(Font::BOLD)
795
+ when 'bm'
796
+ parse_mathfont(Font::BOLD_ITALIC)
797
+ when 'mathbb'
798
+ parse_mathfont(Font::BLACKBOLD)
799
+ when 'mathscr'
800
+ parse_mathfont(Font::SCRIPT)
801
+ when 'mathfrak'
802
+ parse_mathfont(Font::FRAKTUR)
803
+ end
804
+ end
805
+
806
+ def cmd_frac
807
+ n = parse_any; d = parse_any
808
+ Frac.new(n, d)
809
+ end
810
+
811
+ def cmd_sqrt
812
+ if @scanner.scan_option
813
+ i = parse_into(@scanner[1], Array.new)
814
+ i = i.size==1 ? i[0] : (Row.new << i)
815
+ b = parse_any
816
+ Root.new(i, b)
817
+ else
818
+ Sqrt.new << parse_any
819
+ end
820
+ end
821
+
822
+ def cmd_mbox
823
+ @scanner.scan_any
824
+ Text.new << (@scanner.matched =~ RE::BLOCK ? @scanner[1] : @scanner.matched)
825
+ end
826
+ end
827
+
828
+ module BuiltinGroups
829
+ class CircularReferenceEnvironment < StandardError; end
830
+
831
+ def initialize
832
+ add_group("begin", "end")
833
+ add_group("left", "right", :left_etc)
834
+ add_group("bigg", "bigg", :left_etc)
835
+ @environments = Hash.new
836
+
837
+ super
838
+ end
839
+
840
+ def add_environment(*a)
841
+ @environments = Hash.new unless @environments
842
+ if a.size==1 && Hash===a[0]
843
+ @environments.merge!(hash)
844
+ else
845
+ a.each{|i| @environments[i] = false}
846
+ end
847
+ end
848
+
849
+ def grp_begin
850
+ matched = @scanner.matched
851
+ begin_pos = @scanner.pos-matched.size
852
+ en = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
853
+ raise ParseError.new('Environment name not exist.') unless en
854
+
855
+ macro = @macro.environments(en)
856
+ if macro
857
+ begin
858
+ flg = @expanded_environment.include?(en)
859
+ @expanded_environment.push(en)
860
+ raise CircularReferenceEnvironment if flg
861
+
862
+ pos = @scanner.pos
863
+ option = (macro.option && @scanner.scan_option) ? @scanner[1] : nil
864
+ params = Array.new
865
+ (1..macro.num).each do
866
+ params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
867
+ raise ParseError.new("Need more parameter.") unless params.last
868
+ end
869
+ body = ""
870
+ grpnest = 0
871
+ until @scanner.peek_command=="end" && grpnest==0
872
+ if @scanner.eos?
873
+ @scanner.pos = pos
874
+ raise ParseError.new('Matching \end not exist.')
875
+ end
876
+ com = @scanner.peek_command
877
+ grpnest += 1 if @group_begins.has_key?(com)
878
+ grpnest -=1 if @group_ends.has_key?(com) && @group_begins[com]
879
+ raise ParseError.new("Syntax error.") if grpnest<0
880
+
881
+ body << @scanner.scan_any(true)
882
+ end
883
+ @scanner.scan_command
884
+ raise ParseError.new("Environment mismatched.", @scanner.matched) unless en==(@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
885
+ begin
886
+ return parse_into(@macro.expand_environment(en, body, params, option), Array.new)
887
+ rescue CircularReferenceEnvironment
888
+ if @expanded_environment.size>1
889
+ raise
890
+ else
891
+ @scanner.pos = begin_pos
892
+ raise ParseError.new("Circular reference.")
893
+ end
894
+ rescue ParseError => e
895
+ if @expanded_environment.size>1
896
+ raise
897
+ else
898
+ @scanner.pos = begin_pos
899
+ raise ParseError.new(%[Error in macro(#{e.message} "#{e.rest.strip}").])
900
+ end
901
+ end
902
+ ensure
903
+ @expanded_environment.pop
904
+ end
905
+ end
906
+
907
+ raise ParseError.new("Undefined environment.") unless @environments.has_key?(en)
908
+ e = @environments[en]
909
+ e = en unless e # default method name
910
+
911
+ __send__("env_#{e.to_s}")
912
+ end
913
+
914
+ def grp_left_etc
915
+ right =
916
+ case @scanner[1]
917
+ when "left"
918
+ "right"
919
+ when "bigg"
920
+ "bigg"
921
+ end
922
+
923
+ f = Fenced.new
924
+ p = @scanner.pos
925
+ o = @scanner.scan_any
926
+ raise ParseError.new('Need brace here.') unless o && (o=~RE::BRACES || @delimiters.include?(o[RE::COMMANDS, 1]))
927
+ f.open = (o=~RE::BRACES ? o : parse_symbol_command(o[RE::COMMANDS, 1], true))
928
+ f << push_container(Row.new) do |r|
929
+ until @scanner.peek_command==right
930
+ if @scanner.eos?
931
+ @scanner.pos = p
932
+ raise ParseError.new('Brace not closed.')
933
+ end
934
+ r << parse_to_element(true)
935
+ end
936
+ end
937
+ @scanner.scan_command # skip right
938
+ c = @scanner.scan_any
939
+ raise ParseError.new('Need brace here.') unless c=~RE::BRACES || @delimiters.include?(c[RE::COMMANDS, 1])
940
+ f.close = (c=~RE::BRACES ? c : parse_symbol_command(c[RE::COMMANDS, 1], true))
941
+ f
942
+ end
943
+ end
944
+
945
+ module BuiltinEnvironments
946
+ def initialize
947
+ add_environment("array", "matrix", "equation")
948
+
949
+ super
950
+ end
951
+
952
+ def env_equation
953
+ until @scanner.peek_command=="end"
954
+ raise ParseError.new('Matching \end not exist.') if @scanner.eos?
955
+
956
+ push_container(@math) do |e|
957
+ e << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.eos?
958
+ end
959
+ end
960
+
961
+ raise ParseError.new("Need \\end{equation}.") unless @scanner.peek_command=="end"
962
+ @scanner.scan_command
963
+ raise ParseError.new("Environment mismatched.") unless @scanner.check_block && @scanner[1]=="equation"
964
+ @scanner.scan_block
965
+ end
966
+
967
+ def env_array
968
+ layout = @scanner.scan_block ? @scanner.matched : @scanner.scan(/./)
969
+ l = Scanner.new(layout=~RE::BLOCK ? layout[RE::BLOCK, 1] : layout)
970
+ t = Table.new
971
+ aligns = Array.new
972
+ vlines = Array.new
973
+ vlined = l.check(/\|/)
974
+ columned = false
975
+ until l.eos?
976
+ c = l.scan_any
977
+ raise ParseError.new("Syntax error.", layout[/\A.*(#{Regexp.escape(c+l.rest)}.*\z)/m, 1]) unless c=~/[clr\|@]/
978
+
979
+ if c=='|'
980
+ aligns << Align::CENTER if vlined
981
+ vlines << Line::SOLID
982
+ vlined = true
983
+ columned = false
984
+ else
985
+ vlines << Line::NONE if columned
986
+ vlined = false
987
+ columned = true
988
+ case c
989
+ when 'l'
990
+ aligns << Align::LEFT
991
+ when 'c'
992
+ aligns << Align::CENTER
993
+ when 'r'
994
+ aligns << Align::RIGHT
995
+ when '@'
996
+ aligns << Align::CENTER
997
+ l.scan_any
998
+ end
999
+ end
1000
+ end
1001
+ t.aligns = aligns
1002
+ t.vlines = vlines
1003
+
1004
+ layout = layout[RE::BLOCK, 1] if layout=~RE::BLOCK
1005
+ raise ParseError.new('Need parameter here.') if layout==""
1006
+
1007
+ hlines = Array.new
1008
+ row_parsed = false
1009
+ hlined = false
1010
+ until @scanner.peek_command=="end"
1011
+ raise ParseError.new('Matching \end not exist.') if @scanner.eos?
1012
+ if @scanner.peek_command=="hline"
1013
+ @scanner.scan_command
1014
+ t << Tr.new unless row_parsed
1015
+ hlines << Line::SOLID
1016
+ row_parsed = false
1017
+ hlined = true
1018
+ else
1019
+ hlines << Line::NONE if row_parsed
1020
+ t << env_array_row(l.string)
1021
+ @scanner.scan(RE::WBSLASH)
1022
+ row_parsed = true
1023
+ hlined = false
1024
+ end
1025
+ end
1026
+ t.hlines = hlines
1027
+
1028
+ if hlined
1029
+ tr = Tr.new
1030
+ (0..vlines.size).each {|i| tr << Td.new}
1031
+ t << tr
1032
+ end
1033
+
1034
+ @scanner.scan_command
1035
+ raise ParseError.new("Environment mismatched.") unless @scanner.check_block && @scanner[1]=="array"
1036
+ @scanner.scan_block
1037
+ t
1038
+ end
1039
+
1040
+ def env_array_row(layout)
1041
+ l = Scanner.new(layout)
1042
+ r = Tr.new
1043
+ first_column = true
1044
+ vlined = l.check(/\|/)
1045
+ until l.eos?
1046
+ c = l.scan(/./)
1047
+ if c=='|'
1048
+ r << Td.new if vlined
1049
+ vlined = true
1050
+ next
1051
+ else
1052
+ vlined = false
1053
+ case c
1054
+ when 'r', 'l', 'c'
1055
+ when '@'
1056
+ r << parse_into(l.scan_any, Td.new)
1057
+ next
1058
+ end
1059
+ if first_column
1060
+ first_column = false
1061
+ else
1062
+ raise ParseError.new("Need more column.", @scanner.matched.to_s) unless @scanner.scan(/&/)
1063
+ end
1064
+ r << push_container(Td.new) do |td|
1065
+ td << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1066
+ end
1067
+ end
1068
+ end
1069
+ r << Td.new if vlined
1070
+ raise ParseError.new("Too many column.") if @scanner.check(/&/)
1071
+ r
1072
+ end
1073
+
1074
+ def env_matrix
1075
+ t = Table.new
1076
+ hlines = Array.new
1077
+ hlined = false
1078
+ row_parsed = false
1079
+ until @scanner.peek_command=="end"
1080
+ raise ParseError.new('Matching \end not exist.') if @scanner.eos?
1081
+ if @scanner.peek_command=="hline"
1082
+ @scanner.scan_command
1083
+ t << Tr.new unless row_parsed
1084
+ hlines << Line::SOLID
1085
+ row_parsed = false
1086
+ hlined = true
1087
+ else
1088
+ hlines << Line::NONE if row_parsed
1089
+ t << (r = Tr.new)
1090
+ r << (td=Td.new)
1091
+ until @scanner.check(RE::WBSLASH) || @scanner.peek_command=="end" || @scanner.eos?
1092
+ push_container(td) do |e|
1093
+ e << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1094
+ end
1095
+ r << (td=Td.new) if @scanner.scan(/&/)
1096
+ end
1097
+ @scanner.scan(RE::WBSLASH)
1098
+ row_parsed = true
1099
+ hlined = false
1100
+ end
1101
+ end
1102
+ t.hlines = hlines
1103
+
1104
+ t << Tr.new if hlined
1105
+
1106
+ raise ParseError.new("Need \\end{array}.") unless @scanner.peek_command=="end"
1107
+ @scanner.scan_command
1108
+ raise ParseError.new("Environment mismatched.") unless @scanner.check_block && @scanner[1]=="matrix"
1109
+ @scanner.scan_block
1110
+ t
1111
+ end
1112
+
1113
+ def env_matrix_row
1114
+ r = Tr.new
1115
+ until @scanner.check(RE::WBSLASH) || @scanner.peek_command=="end"
1116
+ r << push_container(Td.new) do |td|
1117
+ td << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1118
+ end
1119
+ end
1120
+ end
1121
+ end
1122
+ end
1123
+ end