depager 0.2.3 → 0.3.0.b20250423

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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +44 -0
  3. data/.simplecov +5 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE.gpl +339 -0
  6. data/Manifest.txt +73 -0
  7. data/README.en +7 -21
  8. data/README.ja +19 -99
  9. data/Rakefile +31 -0
  10. data/bin/depager +7 -45
  11. data/examples/action_pl0d/pl0d.action.dr +421 -0
  12. data/examples/action_pl0d/test.pl0ds +48 -0
  13. data/examples/c89/c89.dr +493 -496
  14. data/examples/c89/test.c89 +10 -10
  15. data/examples/extension/astdf.rb +9 -0
  16. data/examples/extension/atree.dr +55 -0
  17. data/examples/{sample_calc → extension}/calc.atree.dr +42 -43
  18. data/examples/{sample_calc/calc.action.dr → extension/calc.simple_action.dr} +33 -33
  19. data/examples/extension/paction.dr +16 -15
  20. data/examples/extension/pactiontest.dr +14 -14
  21. data/examples/extension/simple_action.rb +46 -0
  22. data/examples/pl0d/pl0ds.dr +337 -334
  23. data/examples/pl0d/test.pl0ds +33 -33
  24. data/examples/rie_calc/calc.rie.dr +57 -0
  25. data/examples/rie_calc/test.calc +4 -0
  26. data/examples/rie_dcuse/dcuse.rie.dr +71 -0
  27. data/examples/rie_dcuse/test.dcuse +1 -0
  28. data/examples/rie_pl0/orig_ex/exerrdg.pl0 +44 -0
  29. data/examples/rie_pl0/orig_ex/exerrm.pl0 +19 -0
  30. data/examples/rie_pl0/orig_ex/exerrmre.pl0 +20 -0
  31. data/examples/rie_pl0/orig_ex/exerrtok.pl0 +18 -0
  32. data/examples/rie_pl0/orig_ex/exmdg.pl0 +40 -0
  33. data/examples/rie_pl0/orig_ex/exmdgwwl.pl0 +43 -0
  34. data/examples/rie_pl0/orig_ex/exmrw.pl0 +22 -0
  35. data/examples/rie_pl0/orig_ex/exmwwl.pl0 +18 -0
  36. data/examples/rie_pl0/orig_ex/exnorw.pl0 +17 -0
  37. data/examples/rie_pl0/pl0.rie.dr +450 -0
  38. data/examples/rie_pl0/test.pl0 +10 -0
  39. data/examples/slex_test/divreg.slex.dr +29 -29
  40. data/examples/slex_test/ljoin.slex.dr +36 -36
  41. data/examples/slex_test/test.divreg +1 -1
  42. data/examples/slex_test/test.ljoin +3 -3
  43. data/examples/{sample_calc/calc.nvaction.dr → tiny_calc/calc.action.dr} +33 -33
  44. data/examples/{sample_calc → tiny_calc}/calc.ast.action.dr +76 -66
  45. data/examples/{sample_calc → tiny_calc}/calc.ast.dr +67 -55
  46. data/examples/tiny_calc/calc.cst.dr +50 -0
  47. data/examples/{sample_calc → tiny_calc}/calc.dr +43 -43
  48. data/examples/{sample_calc → tiny_calc}/calc.lex.dr +29 -29
  49. data/examples/{sample_calc/calc_prec.nvaction.dr → tiny_calc/calc_prec.action.dr} +31 -31
  50. data/lib/depager/cli.rb +44 -0
  51. data/lib/depager/grammar.rb +253 -291
  52. data/lib/depager/lr.rb +589 -579
  53. data/lib/depager/parser.rb +269 -277
  54. data/lib/depager/plugins/_rie_debug.rb +63 -0
  55. data/lib/depager/plugins/action.rb +47 -0
  56. data/lib/depager/plugins/ast.dr +367 -0
  57. data/lib/depager/plugins/ast.rb +1329 -0
  58. data/lib/depager/{ruby/plugins → plugins}/cst.dr +174 -180
  59. data/lib/depager/plugins/cst.rb +591 -0
  60. data/lib/depager/{ruby/plugins → plugins}/lex.dr +85 -89
  61. data/lib/depager/plugins/lex.rb +313 -0
  62. data/lib/depager/plugins/rie.dr +725 -0
  63. data/lib/depager/plugins/rie.rb +1614 -0
  64. data/lib/depager/{ruby/plugins → plugins}/slex.dr +201 -200
  65. data/lib/depager/plugins/slex.rb +769 -0
  66. data/lib/depager/plugins/srp.rb +46 -0
  67. data/lib/depager/ruby/templates/extension_lalr_master.erb +40 -51
  68. data/lib/depager/ruby/templates/extension_lalr_slave.erb +113 -107
  69. data/lib/depager/ruby/templates/single_lalr_parser.erb +124 -117
  70. data/lib/depager/utils.rb +158 -318
  71. data/lib/depager/version.rb +3 -3
  72. data/lib/depager.rb +572 -670
  73. metadata +77 -80
  74. data/ChangeLog +0 -16
  75. data/data/depager/pre-setup.rb +0 -3
  76. data/examples/c89/c89.tab.rb +0 -7127
  77. data/examples/pl0d/pl0ds.tab.rb +0 -2698
  78. data/examples/sample_calc/calc.action.tab.rb +0 -457
  79. data/examples/sample_calc/calc.ast.action.tab.rb +0 -749
  80. data/examples/sample_calc/calc.ast.tab.rb +0 -665
  81. data/examples/sample_calc/calc.astdf.dr +0 -54
  82. data/examples/sample_calc/calc.astdf.tab.rb +0 -672
  83. data/examples/sample_calc/calc.atree.tab.rb +0 -451
  84. data/examples/sample_calc/calc.cst.dr +0 -45
  85. data/examples/sample_calc/calc.cst.tab.rb +0 -644
  86. data/examples/sample_calc/calc.lex.tab.rb +0 -374
  87. data/examples/sample_calc/calc.nvaction.tab.rb +0 -465
  88. data/examples/sample_calc/calc.tab.rb +0 -365
  89. data/examples/sample_calc/calc_prec.nvaction.tab.rb +0 -431
  90. data/examples/slex_test/divreg.slex.tab.rb +0 -303
  91. data/examples/slex_test/ljoin.slex.tab.rb +0 -370
  92. data/lib/depager/ruby/plugins/_ast_tmpl.rb +0 -73
  93. data/lib/depager/ruby/plugins/action.rb +0 -43
  94. data/lib/depager/ruby/plugins/ast.dr +0 -269
  95. data/lib/depager/ruby/plugins/ast.rb +0 -1308
  96. data/lib/depager/ruby/plugins/astdf.rb +0 -6
  97. data/lib/depager/ruby/plugins/atree.dr +0 -55
  98. data/lib/depager/ruby/plugins/atree.rb +0 -347
  99. data/lib/depager/ruby/plugins/cst.rb +0 -626
  100. data/lib/depager/ruby/plugins/lex.rb +0 -336
  101. data/lib/depager/ruby/plugins/nvaction.rb +0 -19
  102. data/lib/depager/ruby/plugins/slex.rb +0 -817
  103. data/lib/depager/ruby/plugins/srp.rb +0 -51
  104. data/lib/depager/ruby/templates/simple.erb +0 -23
  105. data/setup.rb +0 -1585
  106. /data/examples/{sample_calc → tiny_calc}/test.calc +0 -0
data/lib/depager.rb CHANGED
@@ -1,670 +1,572 @@
1
- require 'erb'
2
- require "depager/parser.rb"
3
- require "depager/grammar.rb"
4
- require "depager/utils.rb"
5
-
6
- module Depager
7
- LIBDIR = File.expand_path "#{File.dirname(__FILE__)}/depager/ruby"
8
- Tmpldir = "#{LIBDIR}/templates"
9
- $LOAD_PATH.unshift LIBDIR
10
-
11
- #
12
- # file manager
13
- #
14
- class FileManager
15
- def initialize f=nil
16
- @files = []
17
- @files << f if f
18
- end
19
- def __file= f
20
- # @file = f
21
- end
22
- def eof?
23
- ceof = current.eof?
24
- if !ceof
25
- ceof
26
- elsif @files.size == 1
27
- ceof
28
- else
29
- @files.pop
30
- self.eof?
31
- end
32
- end
33
- # get file name
34
- def fname
35
- File.basename current.path
36
- end
37
- # get file path
38
- def path
39
- current.path
40
- end
41
- # get current line no
42
- def lineno
43
- current.lineno
44
- end
45
- # add file to files
46
- def add_file f
47
- @files << f
48
- end
49
- #current file
50
- def current
51
- @files.last
52
- end
53
-
54
- # get next line
55
- def getline
56
- current.gets unless eof?
57
- end
58
- alias gets getline
59
- end
60
-
61
- #
62
- # = declaration part parser
63
- #
64
- class DeclParser
65
- attr_accessor :target_name, :g_parser, :generator
66
- def initialize
67
- end
68
- def parse(file, fname=nil)
69
- @files = FileManager.new(file)
70
- @target_name = nil
71
-
72
- parse_decl
73
- end
74
- def files
75
- @files
76
- end
77
- alias file files
78
- def parse_decl
79
- until files.eof?
80
- case line = files.getline
81
- when /^\s*(#.*)?$/
82
- #skip space and comment.
83
- when /^%class\s+(\S+)\s+based_on\s+(\S+)(\s+\(\s*'(.+)'\s*\))?\s*$/
84
- @target_name = $1
85
- require $4 if $4
86
- ns = $2.split(/::/)
87
- ge = ns.pop
88
- m = ns.inject(Object){|c,name| c.const_get(name) }
89
- @generator = m.const_get("#{ge}Generator").new(self)
90
- break
91
- when /^%class\s+(\S+)\s*$/
92
- require 'depager/lr.rb'
93
- @target_name = $1
94
- @generator = LALR::SingleParserGenerator.new(self)
95
- break
96
- when /^%defext\s+(\S+)\s*$/
97
- require 'depager/lr.rb'
98
- @target_name = $1
99
- @generator = LALR::ExtensionParserGenerator.new(self)
100
- break
101
- else
102
- error_exit "%class not found."
103
- end
104
- end
105
- @generator.parse_decl
106
- end
107
- def parsing_method
108
- @generator.parsing_method
109
- end
110
- def error_exit msg, lineno=nil
111
- lineno ||= files.lineno
112
- warn "#{files.path}:#{lineno}: #{msg}"
113
- exit 1
114
- end
115
- def warning msg, lineno=nil
116
- lineno ||= files.lineno
117
- warn "#{files.path}:#{lineno}: warning: #{msg}"
118
- end
119
- end
120
-
121
- #
122
- # = grammar part parser
123
- #
124
- class GrammarParser
125
- include FileUtils
126
-
127
- HOOK_KEYS = [
128
- :pre_rule_list, :post_rule_list,
129
- :pre_rule, :post_rule,
130
- :post_lhs,
131
- :post_rhs_list, :pre_rhs_list,
132
- :pre_rhs, :post_rhs, :post_symbol
133
- ] #:nodoc:
134
- def self.hook_name? s
135
- HOOK_KEYS.include? s.to_sym
136
- end
137
-
138
- def files
139
- @d_parser.files
140
- end
141
-
142
- private
143
- GToken = Struct.new(:tag, :value, :name, :lineno)
144
- def _GToken tag, value, name=nil
145
- @token = GToken[tag, value, name, lineno]
146
- return @token
147
- end
148
- def parse_grammar
149
- gettoken
150
- do_hook :pre_rule_list
151
- parse_rulelist
152
- do_hook :post_rule_list
153
- unless @token.tag == nil
154
- error_exit "syntax error(grammar). "<<
155
- "unexpected '#{@token.tag}' expect '%%'"
156
- end
157
- end
158
- def parse_rulelist
159
- do_hook :pre_rule
160
- parse_rule
161
- do_hook :post_rule
162
- if @token.tag == :NT
163
- parse_rulelist
164
- end
165
- end
166
- def parse_rule
167
- if @token[0] == :NT
168
- @lhs = @token.value
169
-
170
- gettoken
171
- do_hook :post_lhs
172
-
173
- unless @token.tag == ':'
174
- error_exit "syntax error(grammar). "<<
175
- "unexpected '#{@token.tag}' expecting ':'"
176
- end
177
- gettoken
178
-
179
- @nrhs = 0
180
- do_hook :pre_rhs_list
181
- parse_rhslist
182
- do_hook :post_rhs_list
183
-
184
- unless @token.tag == ';'
185
- error_exit "syntax error(grammar). " <<
186
- "unexpected '#{@token.tag}' expecting ';'"
187
- end
188
- gettoken
189
- end
190
- end
191
- def parse_rhslist
192
- @rhs = []; @rhsni = {}; @prec = nil
193
- @optval = Array.new(@nparam)
194
- do_hook :pre_rhs
195
- parse_syms
196
- add_rule(@lhs, @rhs, @prec)
197
- do_hook :post_rhs
198
- @nrhs += 1
199
-
200
- if @token.tag == '|'
201
- gettoken
202
- parse_rhslist
203
- end
204
- end
205
- def parse_syms
206
- if @token.tag == :NT || @token.tag == :T
207
- rhs_insert_sym(-1, @token.value, @token.name)
208
- gettoken
209
-
210
- do_hook :post_symbol
211
- parse_syms
212
- end
213
- end
214
-
215
- def gettoken
216
- while !@line.empty? || !eof?
217
- if @line.empty?
218
- getline
219
- if @line =~ /^%include\s+'(.+?)'\s*$/
220
- files.add_file File.open($1)
221
- @line = ''
222
- end
223
- end
224
- @oldline = @line
225
- tag = s = name = val = nil
226
- until @line.empty? do
227
- case @line
228
- when /^\s+/, /^\#.*/ ;
229
- # skip
230
- when /^=([a-zA-Z]+)/
231
- @prec = s = $1.intern
232
- val = @terms[s] ||= - (@terms.size + 1)
233
- when /^([a-z][a-z0-9_]*)(-([a-z][_a-z0-9]*))?/
234
- tag = :NT
235
- s = $1.intern
236
- name = $3 ? $3 : $1
237
- val = @nonterms[s] ||= @nonterms.size
238
- when /^([A-Z][A-Z0-9_]*)(-([a-z][_a-z0-9]*))?/
239
- tag = :T
240
- s = $1.intern
241
- name = $3 ? $3 : $1
242
- val = @terms[s] ||= - (@terms.size + 1)
243
- when /^'(.+?)'/
244
- tag = :T
245
- name = s = $1
246
- val = @terms[s] ||= - (@terms.size + 1)
247
- when /^%%/
248
- return _GToken(nil, nil)
249
- when /^([:;|])/
250
- tag = $&
251
- val = $&
252
- else
253
- return _GToken(:UNKNOWN, @line[0].chr)
254
- end
255
- @oldline = @line; @line = $'
256
- @i2s[val] ||= s if val && s
257
- return _GToken(tag, val, name) if tag
258
- end
259
- end
260
- return _GToken(nil, nil)
261
- end
262
-
263
- private
264
- def do_hook hook
265
- @hooks[hook].each{|o, m| o.send m}
266
- end
267
-
268
- public
269
- # update line and get token
270
- def update_context line
271
- @line = line
272
- gettoken
273
- end
274
-
275
- # get number of param
276
- def getnparam a = nil
277
- return nil unless a
278
- return @nparams[a] if @nparams[a]
279
- @nparam += 1
280
- @nparams[a] = @nparam
281
- end
282
-
283
- def int_to_sym n
284
- @i2s[n]
285
- end
286
- alias i2s int_to_sym
287
-
288
- # add nonterm
289
- # sym:: symbol
290
- def add_nonterm sym
291
- sym = sym.to_s.intern
292
- isym = @nonterms[sym] = @nonterms.size
293
- @i2s[isym] = sym
294
- return isym
295
- end
296
-
297
- #
298
- # add rule
299
- #
300
- def add_rule lhs, rhs, prec=nil
301
- @lhs_syms[lhs] = true
302
- rule = @parsing_method::Rule[lhs, rhs, prec]
303
- @rules.push rule
304
- return rule
305
- end
306
-
307
- # get rhs index by name
308
- # name :: name
309
- def name_to_rhs_index name
310
- @rhsni[name]
311
- end
312
- alias n2ri name_to_rhs_index
313
-
314
- # insert sym into rhs
315
- def rhs_insert_sym pos, token_value, token_name
316
- if pos < 0
317
- @rhs.push token_value
318
- @rhsni[token_name] = @rhs.size - 1
319
- @rhs_syms[token_value] = true
320
- else
321
- @rhsni.each{|k, v| @rhsni[k] += 1 if v >= pos }
322
- @rhs.insert(pos, token_value)
323
- @rhsni[token_name] = pos
324
- @rhs_syms[token_value] = true
325
- end
326
- end
327
-
328
- attr_accessor :rules, :terms, :nonterms, :precs, :table
329
- attr_accessor :line, :line0, :oldline, :token
330
- attr_accessor :optouter, :optinner, :optmain, :mixin
331
- attr_accessor :nparams, :lhs, :rhs, :nrhs, :rhsni, :nparam, :nrules
332
- attr_reader :d_parser, :target_name, :parsing_method, :hooks
333
-
334
- def initialize d_parser
335
- @yydebug = true
336
- @d_parser = d_parser
337
-
338
- @optinner = []
339
- @optouter = []
340
- @optmain = []
341
- @nparams = {}
342
- @nparam = 1
343
- init_hook
344
- end
345
-
346
- private
347
- # check grammar
348
- def check_grammar
349
- lhs_warns = []
350
- @lhs_syms.each do |s, _|
351
- lhs_warns << s if s != 1 and !@rhs_syms[s]
352
- end
353
- rhs_warns = []
354
- @rhs_syms.each do |s, _|
355
- rhs_warns << s if s > 0 and !@lhs_syms[s]
356
- end
357
- lhs_warns.uniq.each do |s|
358
- warning "the lhs '#{@i2s[s]}' is not used"
359
- end
360
- rhs_warns.uniq.each do |s|
361
- warning "the symbol '#{@i2s[s]}' is undefined"
362
- end
363
- end
364
-
365
- # make grammar object
366
- # precs:: prec infos
367
- def make_grammar precs
368
- ts = @nonterms.size - 1
369
- @terms.each{|k,v| @terms[k] = ts - v}
370
- # @grammar[0].act = Array.new(@nparam-1)
371
-
372
- @precs = {}
373
- precs.each_with_index do |p, x|
374
- p[1].each do |i|
375
- if @terms[i]
376
- @precs[@terms[i]] = [p[0], x]
377
- else
378
- error_exit "prec error '#{i}'."
379
- end
380
- end
381
- end
382
- @rules.each do |rule|
383
- rule.rhs.each_with_index{|i, x| rule.rhs[x] = ts - i if i < 0}
384
- rule.prec &&= @precs[@terms[rule.prec]]
385
- end
386
- end
387
- alias mkg make_grammar
388
-
389
- # initialize hook
390
- def init_hook
391
- @hooks = HOOK_KEYS.inject({}){|r,i| r[i]=[]; r }
392
- end
393
-
394
- def init_grammar
395
- @rules = [@parsing_method::Rule[0 , [1]]]
396
- @terms = { nil => -1, false => -2 }
397
- @nonterms = {'$start' => 0}
398
- @i2s = {}
399
- @rhs_syms = {}
400
- @lhs_syms = {}
401
- end
402
-
403
- public
404
- # extend paser
405
- # extentions:: array of [name, paht]
406
- # options:: hash of options
407
- def extend_paser extentions, options
408
- init_hook
409
- extentions.each do |f,m|
410
- require f if f
411
- e = Object.const_get("#{m}Extension").new
412
- options[m] and options[m].each do |k,v|
413
- begin
414
- e.send("#{k}=", v)
415
- rescue NoMethodError
416
- warning "no such a option '#{m}.#{k}'."
417
- end
418
- end
419
- e.send(:__regext__, self)
420
- end
421
- end
422
-
423
- # get next line
424
- def getline
425
- @line0 = @line = files.getline
426
- end
427
-
428
- # parse and make LALR(1) table
429
- # file:: input file object
430
- # target_name:: class name
431
- # mixin:: mixin modules
432
- # precs:: prec infos
433
- # return:: LALRTable
434
- def parse(target_name = nil, mixin = [], precs = [], pm=nil)
435
- @line = @line0 = ''
436
- @oldline = nil
437
- @target_name = target_name
438
- @mixin = mixin
439
- @parsing_method = pm || @d_parser.generator.parsing_method
440
-
441
- init_grammar
442
- parse_grammar
443
- check_grammar
444
- make_grammar precs
445
- @table = @parsing_method::Table.new(
446
- @parsing_method::Grammar.new(@rules, @terms, @nonterms, @precs))
447
- @table.check_table @d_parser
448
- @table
449
- end
450
- end
451
-
452
- #
453
- # generators
454
- #
455
-
456
- # Generator base
457
- class Generator
458
- include FileUtils
459
- def files
460
- @d_parser.files
461
- end
462
- Tmpldir = Depager::Tmpldir
463
- Tmplfile = "**NOTHING**"
464
- def tmpldir
465
- Generator::Tmpldir
466
- end
467
- def tmplf
468
- self.class::Tmplfile
469
- end
470
- attr_reader :d_parser, :parsing_method
471
- attr_accessor :optouter, :optinner, :optmain
472
- attr_accessor :basis_name, :deco, :req, :options
473
- def initialize d_parser
474
- @d_parser = d_parser
475
- @parsing_method = nil
476
- @basis_name = nil
477
-
478
- @deco = []
479
- @req = []
480
- @ext = []
481
- @options = {}
482
-
483
- @optinner = []
484
- @optouter = []
485
- @optmain = []
486
- @precs = []
487
- @mixin = []
488
-
489
- @dr_option = {}
490
-
491
- @tmpldir = Generator::Tmpldir+'/'
492
- end
493
-
494
- def parse_prec
495
- precs = []
496
- until files.eof?
497
- case line = files.getline
498
- when /^\s*$/, /^\s*#.*$/
499
- # skip
500
- when /^\s*(left|right|nonassoc)\s+(.*)$/
501
- dir = $1.upcase.intern
502
- syms = $2.split.map{|i| (i =~ /'(.+?)'/) ? $1 : i.intern}
503
- precs.push [dir, syms]
504
- when /^%\}\s*$/
505
- break
506
- else
507
- error_exit "syntax error(prec).\n> #{line}"
508
- end
509
- end
510
- return precs
511
- end
512
-
513
- def parse_block
514
- val = ''
515
- until eof?
516
- line = getline
517
- break if line =~ /^%\}/
518
- val.concat line
519
- end
520
- return val
521
- end
522
-
523
- def parse_common line
524
- case line
525
- when /^\s*(#.*)?$/
526
- #skip space and comment.
527
- when /^%decorate\s+(.*?)\s*\((.*)\)\s*$/
528
- @deco.push $1
529
- @req.push $2
530
- when /^%decorate\s+(.*?)\s*$/
531
- @deco.push $1
532
- when /^%extend\s+(.*?)\s*\(\s*'(.*)'\s*\)\s*$/
533
- @ext.push [$2.strip, $1] unless @ext.find{|i| i[1] == $1}
534
- when /^%option\s+(\w+).(\w+)\s+(.*)\s*$/
535
- if $1 == 'Generator'
536
- @dr_option[$2] = $3
537
- else
538
- @options[$1] ||= []
539
- @options[$1] << [$2, $3]
540
- end
541
- when /^%require\s+'(.+?)'\s*$/
542
- @req.push "'#{$1}'"
543
- when /^%mixin\s+(\S+)\s*(\((.+)\)\s*)?$/
544
- @mixin.push $1
545
- @req.push $3 if $3
546
- when /^%inner\s*\{\s*$/
547
- @optinner.push parse_block
548
- when /^%prec\s*\{\s*$/
549
- @precs = parse_prec
550
- else
551
- warning "syntax error(declaration).\n> #{line}"
552
- end
553
- end
554
- end
555
-
556
-
557
- module Simple
558
- Grammar = Depager::ParsingMethod::Grammar
559
- Rule = Depager::ParsingMethod::Rule
560
- class Table
561
- def initialize *args
562
- end
563
- def check_table *args
564
- true
565
- end
566
- end
567
- class SingleGenerator < Generator
568
- Tmplfile = 'simple.erb'
569
- def initialize d_parser
570
- super
571
- @parsing_method = Simple
572
- end
573
- def out_code g_parser
574
- ERB.new(File.read("#{tmpldir}/#{tmplf}")).result(binding)
575
- end
576
- def parse_decl
577
- until eof?
578
- line = getline
579
- case line
580
- when /^%%\s*$/
581
- g_parser = GrammarParser.new(@d_parser)
582
- g_parser.extend_paser @ext, @options
583
- g_parser.parse(@d_parser.target_name, @mixin, @precs)
584
- @optmain.push parse_block
585
- return out_code(g_parser)
586
- else
587
- parse_common(line)
588
- end
589
- end
590
- return nil
591
- end
592
- end
593
-
594
- class ExtensionGenerator < Generator
595
- TmplfileMaster = '**NOTHING**'
596
- TmplfileSlave = '**NOTHING**'
597
- attr_accessor :regs, :paramkey
598
- def initialize d_parser
599
- super
600
- @parsing_method = Simple
601
- @basis_name = 'self'
602
- @paramkey = nil
603
- @regs = { }
604
- end
605
-
606
- def parse_decl
607
- until eof?
608
- line = getline
609
- case line
610
- when /^%hook\s*(.*?)\s*$/
611
- hs = $1
612
- mtsk = if hs =~ /\/(([^\/\\]+|\\.)*)\/\s*(skip)?$/
613
- hs = $`
614
- [$1, $3 ? true : false]
615
- end
616
- hook = hs.split(' ')
617
- hookname = hook.join('__')
618
- hook.each do |i| i = i.to_sym
619
- raise unless GrammarParser.hook_name? i
620
- @regs[i] = "#{@d_parser.target_name}_#{hookname}"
621
- end
622
- @optouter.push(parse_hook(hookname, mtsk))
623
- when /^%param\s*(.*)\s*$/
624
- @paramkey = $1
625
- else
626
- parse_common(line)
627
- end
628
- end
629
- return out_master_code
630
- end
631
-
632
- def parse_hook hookname, mtsk
633
- inner = ''
634
- precs = []
635
- banner = nil
636
- mixin = [].concat(@mixin)
637
- target_name = "#{@d_parser.target_name}_#{hookname}"
638
- until eof?
639
- line = getline
640
- case line
641
- when /^%banner\s*'(([^'\\]+|\\.)*)'\s*$/
642
- banner = $1
643
- when /^%inner\s*\{\s*$/
644
- inner = parse_block
645
- when /^%mixin\s*(.+?)\s*(\((.+)\)\s*)?$/
646
- mixin.push $1
647
- @req.push $3 if $3
648
- when /^%prec\s*(.*?)\s*$/
649
- precs = parse_prec
650
- when /^%%\s*$/
651
- g_parser = GrammarParser.new(@d_parser)
652
- g_parser.extend_paser @ext, @options
653
- g_parser.parse(target_name, mixin, precs)
654
- g_parser.optinner.push inner
655
- return out_slave_code(g_parser, mixin, mtsk, banner)
656
- else
657
- warning "syntax error(declaration).\n> #{line}", lineno
658
- end
659
- end
660
- end
661
-
662
- def out_slave_code g_parser, mixin, mtsk, banner
663
- ERB.new(File.read(self.class::TmplfileSlave)).result(binding)
664
- end
665
- def out_master_code
666
- ERB.new(File.read(self.class::TmplfileMaster)).result(binding)
667
- end
668
- end
669
- end
670
- end
1
+ require "erb"
2
+ require "depager/parser"
3
+ require "depager/grammar"
4
+ require "depager/utils"
5
+
6
+ module Depager
7
+ def self.configuration
8
+ @configuration ||= {}
9
+ end
10
+
11
+ def self.debug_mode?(flag = "")
12
+ value = configuration[:debug] or return false
13
+ !!value.match(/#{flag}/)
14
+ end
15
+
16
+ def self.verbose_mode?
17
+ configuration[:verbose]
18
+ end
19
+
20
+ class DeclarationPartParser
21
+ include Depager::Utils::CommonMethods
22
+
23
+ attr_reader :file, :target_namespace, :target_name, :g_parser, :generator
24
+
25
+ def parse(file)
26
+ @file = file
27
+ @g_parser = GrammarPartParser.new(self)
28
+
29
+ until file.eof?
30
+ case file.gets
31
+ when /^\s*(#.*)?$/
32
+ # skip space and comment.
33
+ when /^%class\s+(\S+)(\s+based_on\s+(\S+)(\s+\(\s*'(.+)'\s*\))?)?\s*$/
34
+ @target_name = $1
35
+ if $2
36
+ require $5 if $5
37
+ nesting = $3.split("::").tap { |it| it[-1] = "#{it[-1]}Generator" }
38
+ generator_class = nesting.inject(Object) { |c, name| c.const_get(name) }
39
+ else
40
+ require "depager/lr"
41
+ generator_class = LALR::ParserGenerator
42
+ end
43
+
44
+ if @target_name =~ /(.+)::([^:]+)/
45
+ @target_namespace = $1
46
+ @target_name = $2
47
+ else
48
+ error_exit "Namespace required"
49
+ end
50
+
51
+ @generator = generator_class.new(self)
52
+ break
53
+ when /^%defext\s+(\S+)\s*$/
54
+ require "depager/lr"
55
+
56
+ @target_namespace = $1
57
+ @target_name = "Master"
58
+
59
+ @generator = LALR::ExtensionGenerator.new(self)
60
+ break
61
+ else
62
+ error_exit "%class not found."
63
+ end
64
+ end
65
+ @generator.parse
66
+ end
67
+
68
+ def parsing_method
69
+ @generator.parsing_method
70
+ end
71
+ end
72
+
73
+ class GrammarPartParser
74
+ include Depager::Utils::CommonMethods
75
+
76
+ Token = Struct.new(:tag, :value, :name, :lineno)
77
+
78
+ HOOK_KEYS = %i[
79
+ pre_rule_list post_rule_list
80
+ pre_rule post_rule
81
+ post_lhs
82
+ post_rhs_list pre_rhs_list
83
+ pre_rhs post_rhs
84
+ post_symbol
85
+ ].freeze # :nodoc:
86
+
87
+ attr_accessor :rules, :terms, :nonterms, :precs, :table, :lhs, :rhs, :nrhs, :rhs_names, :nrules, :line,
88
+ :original_line, :old_line, :token, :outer_code, :inner_code, :mixins
89
+ attr_reader :d_parser, :target_namespace, :target_name, :hooks
90
+
91
+ def initialize(d_parser)
92
+ @d_parser = d_parser
93
+ end
94
+
95
+ # parse and make LALR(1) table
96
+ def parse(full_target_name, precs = [], extensions = [], options = {})
97
+ @original_line = @old_line = @line = ""
98
+ @inner_code = ""
99
+ @outer_code = ""
100
+
101
+ full_target_name.match(/::([^:]+)$/) or raise
102
+ @target_namespace = $`
103
+ @target_name = $1
104
+
105
+ extend_parser(extensions, options)
106
+ init_grammar
107
+ parse_grammar
108
+ check_grammar
109
+ make_grammar precs
110
+
111
+ grammar = d_parser.parsing_method::Grammar.new(@rules, @terms, @nonterms, @precs)
112
+ @table = d_parser.parsing_method::Table.new(grammar)
113
+ @table.check_table d_parser
114
+ @extensions.each { |o| o.send :term_extension }
115
+ @table
116
+ end
117
+
118
+ def hook_name?(s)
119
+ HOOK_KEYS.include? s.to_sym
120
+ end
121
+
122
+ def update_context(line)
123
+ @line = line
124
+ scan_token
125
+ end
126
+
127
+ def int_to_sym(n)
128
+ @int_to_sym[n]
129
+ end
130
+
131
+ def lhs_name
132
+ int_to_sym(lhs)
133
+ end
134
+
135
+ def add_nonterm(sym)
136
+ sym = sym.to_s.intern
137
+ isym = @nonterms[sym] = @nonterms.size
138
+ @int_to_sym[isym] = sym
139
+ isym
140
+ end
141
+
142
+ def add_rule(lhs, rhs, prec = nil, rhs_names = nil)
143
+ @lhs_syms[lhs] = true
144
+ rule = d_parser.parsing_method::Rule[lhs, rhs, prec, rhs_names]
145
+ @rules.push rule
146
+ rule
147
+ end
148
+
149
+ def name_to_rhs_index(name)
150
+ @rhs_names.index(name)
151
+ end
152
+
153
+ def insert_sym_to_rhs(pos, token_value, token_name)
154
+ if pos < 0
155
+ @rhs.push token_value
156
+ @rhs_names[@rhs.size - 1] = token_name
157
+ else
158
+ @rhs.insert(pos, token_value)
159
+ @rhs_names.insert(pos, token_name)
160
+ end
161
+ @rhs_syms[token_value] = true
162
+ end
163
+
164
+ private
165
+
166
+ def parse_grammar
167
+ scan_token
168
+ do_hook :pre_rule_list
169
+ parse_rulelist
170
+ do_hook :post_rule_list
171
+ return if @token.tag.nil?
172
+
173
+ error_exit "syntax error(grammar). unexpected '#{@token.tag}' expect '%%'"
174
+ end
175
+
176
+ def parse_rulelist
177
+ do_hook :pre_rule
178
+ parse_rule
179
+ do_hook :post_rule
180
+ return unless @token.tag == :NT
181
+
182
+ parse_rulelist
183
+ end
184
+
185
+ def parse_rule
186
+ return unless @token[0] == :NT
187
+
188
+ @lhs = @token.value
189
+
190
+ scan_token
191
+ do_hook :post_lhs
192
+
193
+ error_exit "syntax error(grammar). unexpected '#{@token.tag}' expecting ':'" unless @token.tag == ":"
194
+ scan_token
195
+
196
+ @nrhs = 0
197
+ do_hook :pre_rhs_list
198
+ parse_rhslist
199
+ do_hook :post_rhs_list
200
+
201
+ error_exit "syntax error(grammar). unexpected '#{@token.tag}' expecting ';'" unless @token.tag == ";"
202
+ scan_token
203
+ end
204
+
205
+ def parse_rhslist
206
+ @rhs = []
207
+ @rhs_names = []
208
+ @prec = nil
209
+ do_hook :pre_rhs
210
+ parse_syms
211
+ add_rule(@lhs, @rhs, @prec, @rhs_names)
212
+ do_hook :post_rhs
213
+ @nrhs += 1
214
+
215
+ return unless @token.tag == "|"
216
+
217
+ scan_token
218
+ parse_rhslist
219
+ end
220
+
221
+ def parse_syms
222
+ return unless @token.tag == :NT || @token.tag == :T
223
+
224
+ insert_sym_to_rhs(-1, @token.value, @token.name)
225
+ scan_token
226
+
227
+ do_hook :post_symbol
228
+ parse_syms
229
+ end
230
+
231
+ def make_token(tag, value, name = nil)
232
+ @token = Token.new(tag, value, name, file.lineno)
233
+ end
234
+
235
+ def scan_token
236
+ while !@line.empty? || !file.eof?
237
+ @original_line = @old_line = @line = file.gets if @line.empty?
238
+
239
+ tag = s = name = val = nil
240
+ until @line.empty?
241
+ case @line
242
+ when /^\s+/, /^\#.*/
243
+ # skip
244
+ when /^=([a-zA-Z]+)/
245
+ @prec = s = $1.intern
246
+ val = @terms[s] ||= - (@terms.size + 1)
247
+ when /^([a-z][a-z0-9_]*)(-([a-z][_a-z0-9]*))?/
248
+ tag = :NT
249
+ s = $1.intern
250
+ name = $3 || $1
251
+ val = @nonterms[s] ||= @nonterms.size
252
+ when /^([A-Z][A-Z0-9_]*)(-([a-z][_a-z0-9]*))?/
253
+ tag = :T
254
+ s = $1.intern
255
+ name = $3 || $1
256
+ val = @terms[s] ||= - (@terms.size + 1)
257
+ when /^'(.+?)'/
258
+ tag = :T
259
+ name = s = $1
260
+ val = @terms[s] ||= - (@terms.size + 1)
261
+ when /^%%/
262
+ return make_token(nil, nil)
263
+ when /^([:;|])/
264
+ tag = $&
265
+ val = $&
266
+ else
267
+ return make_token(:UNKNOWN, @line[0].chr)
268
+ end
269
+ @old_line = @line
270
+ @line = $'
271
+ @int_to_sym[val] ||= s if val && s
272
+ return make_token(tag, val, name) if tag
273
+ end
274
+ end
275
+ make_token(nil, nil)
276
+ end
277
+
278
+ def extend_parser(extensions, options)
279
+ init_hook
280
+ @extensions = []
281
+ extensions.each do |f, m|
282
+ if f&.match(%r{^\./})
283
+ require File.expand_path(f, File.dirname(file.path))
284
+ elsif f
285
+ require f
286
+ end
287
+
288
+ nesting = m.split("::").tap { |it| it[-1] = "#{it[-1]}Extension" }
289
+ extension = nesting.inject(Object) { |c, name| c.const_get(name) }.new
290
+ options[m]&.each do |k, v|
291
+ extension.send(:"#{k}=", v)
292
+ rescue NoMethodError
293
+ warning "no such a option '#{m}.#{k}'."
294
+ end
295
+ @extensions << extension
296
+ extension.extension_registered self
297
+ extension.init_extension
298
+ end
299
+ end
300
+
301
+ def init_grammar
302
+ @rules = [d_parser.parsing_method::Rule[0, [1]]]
303
+ @terms = { nil => -1, false => -2 }
304
+ @nonterms = { "$start" => 0 }
305
+ @int_to_sym = {}
306
+ @rhs_syms = {}
307
+ @lhs_syms = {}
308
+ end
309
+
310
+ def make_grammar(precs)
311
+ ts = @nonterms.size - 1
312
+ @terms.each { |k, v| @terms[k] = ts - v }
313
+
314
+ @precs = {}
315
+ precs.each_with_index do |p, x|
316
+ p[1].each do |i|
317
+ if @terms[i]
318
+ @precs[@terms[i]] = [p[0], x]
319
+ else
320
+ error_exit "prec error '#{i}'."
321
+ end
322
+ end
323
+ end
324
+ @rules.each do |rule|
325
+ rule.rhs.each_with_index { |i, x| rule.rhs[x] = ts - i if i < 0 }
326
+ rule.prec &&= @precs[@terms[rule.prec]]
327
+ end
328
+ end
329
+
330
+ def check_grammar
331
+ lhs_warns = []
332
+ @lhs_syms.each_key do |s|
333
+ lhs_warns << s if (s != 1) && !@rhs_syms[s]
334
+ end
335
+ rhs_warns = []
336
+ @rhs_syms.each_key do |s|
337
+ rhs_warns << s if (s > 0) && !@lhs_syms[s]
338
+ end
339
+ lhs_warns.uniq.each do |s|
340
+ warning "the lhs '#{@int_to_sym[s]}' is not used"
341
+ end
342
+ rhs_warns.uniq.each do |s|
343
+ warning "the symbol '#{@int_to_sym[s]}' is undefined"
344
+ end
345
+ end
346
+
347
+ def init_hook
348
+ @hooks = HOOK_KEYS.each_with_object({}) do |i, r|
349
+ r[i] = []
350
+ end
351
+ end
352
+
353
+ def do_hook(hook)
354
+ @hooks[hook].each { |o, m| o.send m }
355
+ end
356
+ end
357
+
358
+ class Generator
359
+ include Depager::Utils::CommonMethods
360
+
361
+ TEMPLATES_DIR = File.expand_path("depager/ruby/templates", __dir__)
362
+
363
+ attr_reader :d_parser, :g_parser, :parsing_method
364
+ attr_accessor :outer_code, :inner_code, :setup_code, :decorators, :requirements, :options
365
+
366
+ def initialize(d_parser)
367
+ @d_parser = d_parser
368
+ @g_parser = d_parser.g_parser
369
+ @parsing_method = nil
370
+
371
+ @options = {}
372
+ @decorators = []
373
+ @requirements = []
374
+ @extensions = []
375
+ @inner_code = ""
376
+ @outer_code = ""
377
+ @setup_code = ""
378
+ @precs = []
379
+ @mixins = []
380
+ end
381
+
382
+ def generate_code(template)
383
+ ERB.new(template || "", trim_mode: "-").result(binding)
384
+ end
385
+
386
+ def parse_prec
387
+ precs = []
388
+ until file.eof?
389
+ case line = file.gets
390
+ when /^\s*$/, /^\s*#.*$/
391
+ # skip
392
+ when /^\s*(left|right|nonassoc)\s+(.*)$/
393
+ dir = $1.upcase.intern
394
+ syms = $2.split.map { |i| i =~ /'(.+?)'/ ? $1 : i.intern }
395
+ precs.push [dir, syms]
396
+ when /^%\}\s*$/
397
+ break
398
+ else
399
+ error_exit "syntax error(prec).\n> #{line}"
400
+ end
401
+ end
402
+ precs
403
+ end
404
+
405
+ def parse_block(raw: false)
406
+ lineno = file.lineno
407
+ val = ""
408
+ until file.eof?
409
+ line = file.gets
410
+ break if /^%\}/.match?(line)
411
+
412
+ val.concat line
413
+ end
414
+ return val if raw
415
+
416
+ delimiter = expanded_code_delimiter
417
+ <<~EXPANDED_CODE
418
+ module_eval <<'#{delimiter}', '#{file.path}', #{lineno + 1}
419
+ #{val}
420
+ #{delimiter}
421
+ EXPANDED_CODE
422
+ end
423
+
424
+ def parse_common(line)
425
+ case line
426
+ when /^\s*(#.*)?$/
427
+ # skip space and comment.
428
+ when /^%decorate\s+(.*?)\s*\((.*)\)\s*$/
429
+ @decorators.push $1
430
+ @requirements.push $2
431
+ when /^%decorate\s+(.*?)\s*$/
432
+ @decorators.push $1
433
+ when /^%extend\s+(.*?)\s*\(\s*'(.*)'\s*\)\s*$/
434
+ @extensions.push [$2.strip, $1] unless @extensions.find { |i| i[1] == $1 }
435
+ when /^%option\s+([\w:]+).(\w+)\s+(.*)\s*$/
436
+ if $1 == "Generator"
437
+ Depager.configuration[$2.to_sym] = $3
438
+ else
439
+ @options[$1] ||= []
440
+ @options[$1] << [$2, $3]
441
+ end
442
+ when /^%require\s+'(.+?)'\s*$/
443
+ @requirements.push "'#{$1}'"
444
+ when /^%mixin\s+(\S+)\s*(\((.+)\)\s*)?$/
445
+ @mixins.push $1
446
+ @requirements.push $3 if $3
447
+ when /^%inner\s*\{\s*$/
448
+ @inner_code << parse_block
449
+ when /^%prec\s*\{\s*$/
450
+ @precs = parse_prec
451
+ when /^%expanded_code_delimiter\s+(\S+)\s*$/
452
+ Depager.configuration[:expanded_code_delimiter] = $1
453
+ when /^%abort_behavior\s+(pending\s+)?(.*)$/
454
+ @inner_code << " def do_abort_driver; end\n" if $1
455
+ @inner_code << " def abort_driver; #{$2}; end\n" unless $2.strip.empty?
456
+ else
457
+ warning "syntax error(declaration).\n> #{line}"
458
+ end
459
+ end
460
+ end
461
+
462
+ class ParserGenerator < Generator
463
+ def parse
464
+ until file.eof?
465
+ line = file.gets
466
+ case line
467
+ when /^%%\s*$/
468
+ g_parser.parse(@d_parser.full_target_name, @precs, @extensions, @options)
469
+ @outer_code << parse_block(raw: true)
470
+ return generate_code parser_code_template
471
+ when /^%setup((\s*{\s*)|(\s.*))$/
472
+ @setup_code << ($2 ? parse_block : $3.lstrip)
473
+ else
474
+ parse_common(line)
475
+ end
476
+ end
477
+ nil
478
+ end
479
+
480
+ def parser_code_template; end
481
+ end
482
+
483
+ class ExtensionGenerator < Generator
484
+ attr_accessor :slaves
485
+
486
+ def initialize(d_parser)
487
+ super
488
+ @slaves = {}
489
+ end
490
+
491
+ def parse
492
+ until file.eof?
493
+ line = file.gets
494
+ case line
495
+ when %r{^%hook\s+([\w \t]+?)(/((?:[^/\\]+|\\.)+)/(\s+skip)?)?\s*$}
496
+ hooks = $1.split
497
+ @banner = hooks.join(",")
498
+ @do_parse_re = $3
499
+ @do_parse_skip = $4
500
+
501
+ parts = hooks.map do |i|
502
+ error_exit "Unknown hook: #{i}" unless g_parser.hook_name? i
503
+ i.split("_").map(&:capitalize).join
504
+ end
505
+ target_name = "#{d_parser.target_namespace}::#{parts.join}#{slaves.size}::Parser"
506
+ @slaves[target_name] = hooks
507
+ @outer_code << parse_hook(target_name)
508
+ when /^%%\s*$/
509
+ @outer_code << parse_block(raw: true)
510
+ else
511
+ parse_common(line)
512
+ end
513
+ end
514
+ generate_code master_code_template
515
+ end
516
+
517
+ def parse_hook(target_name)
518
+ inner = ""
519
+ precs = []
520
+ @slave_mixins = @mixins.dup
521
+ until file.eof?
522
+ line = file.gets
523
+ case line
524
+ when /^%banner\s*'(([^'\\]+|\\.)*)'\s*$/
525
+ @banner = $1
526
+ when /^%inner\s*\{\s*$/
527
+ inner = parse_block
528
+ when /^%mixin\s*(.+?)\s*(\((.+)\)\s*)?$/
529
+ @slave_mixins << $1
530
+ @requirements.push $3 if $3
531
+ when /^%prec\s*(.*?)\s*$/
532
+ precs = parse_prec
533
+ when /^%%\s*$/
534
+ g_parser.parse(target_name, precs, @extensions, @options)
535
+ g_parser.inner_code << inner
536
+ return generate_code slave_code_template
537
+ else
538
+ warning "syntax error(declaration).\n> #{line}", file.lineno
539
+ end
540
+ end
541
+ end
542
+
543
+ def slave_code_template; end
544
+
545
+ def master_code_template; end
546
+ end
547
+
548
+ class Extension
549
+ include Depager::Utils::CommonMethods
550
+ include Depager::Utils::CodeGeneratorMethods
551
+
552
+ attr_reader :d_parser, :g_parser
553
+
554
+ def init_extension; end
555
+
556
+ def term_extension; end
557
+
558
+ def extension_registered(g_parser)
559
+ @g_parser = g_parser
560
+ @d_parser = g_parser.d_parser
561
+
562
+ methods.sort.each do |m|
563
+ m = m.to_sym
564
+ g_parser.hooks[m].push [self, m] if g_parser.hook_name? m
565
+ end
566
+ end
567
+
568
+ def decorator_name
569
+ self.class.name.split("::").last.sub(/Extension$/, "")
570
+ end
571
+ end
572
+ end