ebnf 1.0.2 → 1.1.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.
@@ -226,8 +226,9 @@ module EBNF::LL1
226
226
  when @options[:validate] then 1
227
227
  end
228
228
  @branch = options[:branch]
229
- @first = options[:first] ||= {}
229
+ @first = options[:first] ||= {}
230
230
  @follow = options[:follow] ||= {}
231
+ @cleanup = options[:cleanup] ||= {}
231
232
  @lexer = input.is_a?(Lexer) ? input : Lexer.new(input, self.class.patterns, @options)
232
233
  @productions = []
233
234
  @parse_callback = block
@@ -241,7 +242,7 @@ module EBNF::LL1
241
242
 
242
243
  @prod_data = [{}]
243
244
  start = start.split('#').last.to_sym unless start.is_a?(Symbol)
244
- todo_stack = [{:prod => start, :terms => nil}]
245
+ todo_stack = [{prod: start, terms: nil}]
245
246
 
246
247
  while !todo_stack.empty?
247
248
  begin
@@ -255,7 +256,7 @@ module EBNF::LL1
255
256
  # to the beginning to avoid excessive growth in the production
256
257
  # stack
257
258
  if options[:reset_on_start] && cur_prod == start
258
- todo_stack = [{:prod => start, :terms => []}]
259
+ todo_stack = [{prod: start, terms: []}]
259
260
  @productions = []
260
261
  @prod_data = [{}]
261
262
  end
@@ -320,7 +321,7 @@ module EBNF::LL1
320
321
  break
321
322
  else
322
323
  # Push term onto stack
323
- todo_stack << {:prod => term, :terms => nil}
324
+ todo_stack << {prod: term, terms: nil}
324
325
  debug("parse(push)") {"term #{term.inspect}, depth #{depth}"}
325
326
  pushed = true
326
327
  break
@@ -348,9 +349,9 @@ module EBNF::LL1
348
349
  end
349
350
 
350
351
  # Get the list of follows for this sequence, this production and the stacked productions.
351
- debug("recovery", "stack follows:", :level => 4)
352
+ debug("recovery", "stack follows:", level: 4)
352
353
  todo_stack.reverse.each do |todo|
353
- debug("recovery", :level => 4) {" #{todo[:prod]}: #{@follow[todo[:prod]].inspect}"}
354
+ debug("recovery", level: 4) {" #{todo[:prod]}: #{@follow[todo[:prod]].inspect}"}
354
355
  end
355
356
 
356
357
  # Find all follows to the top of the stack
@@ -403,7 +404,7 @@ module EBNF::LL1
403
404
  end
404
405
  end
405
406
 
406
- error("parse(eof)", "Finished processing before end of file", :token => @lexer.first) if @lexer.first
407
+ error("parse(eof)", "Finished processing before end of file", token: @lexer.first) if @lexer.first
407
408
 
408
409
  # Continue popping contexts off of the stack
409
410
  while !todo_stack.empty?
@@ -475,7 +476,7 @@ module EBNF::LL1
475
476
  m += ", production = #{options[:production].inspect}" if options[:production]
476
477
  @error_log << m unless @recovering
477
478
  @recovering = true
478
- debug(node, m, options.merge(:level => 0))
479
+ debug(node, m, options.merge(level: 0))
479
480
  if options[:raise] || @options[:validate]
480
481
  raise Error.new(m, lineno: lineno, token: options[:token], production: options[:production])
481
482
  end
@@ -497,7 +498,7 @@ module EBNF::LL1
497
498
  m += " (found #{options[:token].inspect})" if options[:token]
498
499
  m += ", production = #{options[:production].inspect}" if options[:production]
499
500
  @error_log << m unless @recovering
500
- debug(node, m, options.merge(:level => 1))
501
+ debug(node, m, options.merge(level: 1))
501
502
  end
502
503
 
503
504
  ##
@@ -569,10 +570,14 @@ module EBNF::LL1
569
570
  handler.call(@prod_data.last, data, @parse_callback)
570
571
  }
571
572
  rescue ArgumentError, Error => e
572
- error("start", "#{e.class}: #{e.message}", :production => prod)
573
+ error("start", "#{e.class}: #{e.message}", production: prod)
573
574
  @recovering = false
574
575
  end
575
576
  @prod_data << data
577
+ elsif [:merge, :star].include?(@cleanup[prod])
578
+ # Save current data to merge later
579
+ @prod_data << {}
580
+ progress("#{prod}(:start}:#{@prod_data.length}:cleanup:#{@cleanup[prod]}") { get_token.inspect + (@recovering ? ' recovering' : '')}
576
581
  else
577
582
  # Make sure we push as many was we pop, even if there is no
578
583
  # explicit start handler
@@ -595,10 +600,23 @@ module EBNF::LL1
595
600
  handler.call(@prod_data.last, data, @parse_callback)
596
601
  }
597
602
  rescue ArgumentError, Error => e
598
- error("finish", "#{e.class}: #{e.message}", :production => prod)
603
+ error("finish", "#{e.class}: #{e.message}", production: prod)
599
604
  @recovering = false
600
605
  end
601
606
  progress("#{prod}(:finish):#{@prod_data.length}") {@prod_data.last}
607
+ elsif [:merge, :star].include?(@cleanup[prod])
608
+ data = @prod_data.pop
609
+ input = @prod_data.last
610
+
611
+ # Append every element in data to last prod_data
612
+ data.each do |k, v|
613
+ input[k] = case input[k]
614
+ when nil then v.is_a?(Hash) ? v : Array(v)
615
+ when Hash then input[k].merge!(v)
616
+ else Array(input[k]) + Array(v)
617
+ end
618
+ end
619
+ progress("#{prod}(:finish):#{@prod_data.length} cleanup:#{@cleanup[prod]}") {@prod_data.last}
602
620
  else
603
621
  progress("#{prod}(:finish):#{@prod_data.length}") { "recovering" if @recovering }
604
622
  end
@@ -618,15 +636,15 @@ module EBNF::LL1
618
636
  handler.call(parentProd, token, @prod_data.last, @parse_callback)
619
637
  }
620
638
  rescue ArgumentError, Error => e
621
- error("terminal", "#{e.class}: #{e.message}", :token => token, :production => prod)
639
+ error("terminal", "#{e.class}: #{e.message}", token: token, production: prod)
622
640
  @recovering = false
623
641
  end
624
- progress("#{prod}(:terminal)", "", :depth => (depth + 1)) {"#{token}: #{@prod_data.last}"}
642
+ progress("#{prod}(:terminal)", "", depth: (depth + 1)) {"#{token}: #{@prod_data.last}"}
625
643
  else
626
- progress("#{prod}(:terminal)", "", :depth => (depth + 1)) {token.to_s}
644
+ progress("#{prod}(:terminal)", "", depth: (depth + 1)) {token.to_s}
627
645
  end
628
646
  else
629
- error("#{parentProd}(:terminal)", "Terminal has no parent production", :token => token, :production => prod)
647
+ error("#{parentProd}(:terminal)", "Terminal has no parent production", token: token, production: prod)
630
648
  end
631
649
  end
632
650
 
@@ -703,7 +721,7 @@ module EBNF::LL1
703
721
  # @example Raising a parser error
704
722
  # raise Error.new(
705
723
  # "invalid token '%' on line 10",
706
- # :token => '%', :lineno => 9, :production => :turtleDoc)
724
+ # token: '%', lineno: 9, production: :turtleDoc)
707
725
  #
708
726
  # @see http://ruby-doc.org/core/classes/StandardError.html
709
727
  class Error < StandardError
@@ -18,7 +18,24 @@ module EBNF::LL1
18
18
  attr_reader :input
19
19
 
20
20
  ##
21
- # Create a scanner, from an IO or String
21
+ # If we don't have an IO input, simply use StringScanner directly
22
+ # @private
23
+ def self.new(input, options = {})
24
+ input ||= ""
25
+ if input.respond_to?(:read)
26
+ scanner = self.allocate
27
+ scanner.send(:initialize, input, options)
28
+ else
29
+ if input.encoding != Encoding::UTF_8
30
+ input = input.dup if input.frozen?
31
+ input.force_encoding(Encoding::UTF_8)
32
+ end
33
+ StringScanner.new(input)
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Create a scanner, from an IO
22
39
  #
23
40
  # @param [String, IO, #read] input
24
41
  # @param [Hash{Symbol => Object}] options
@@ -28,13 +45,10 @@ module EBNF::LL1
28
45
  def initialize(input, options = {})
29
46
  @options = options.merge(high_water: HIGH_WATER, low_water: LOW_WATER)
30
47
 
31
- if input.respond_to?(:read)
32
- @input = input
33
- super("")
34
- feed_me
35
- else
36
- super(encode_utf8 input.to_s)
37
- end
48
+ @input = input
49
+ super("")
50
+ feed_me
51
+ self
38
52
  end
39
53
 
40
54
  ##
@@ -78,7 +78,7 @@ module EBNF
78
78
  num, sym = num_sym.split(']', 2).map(&:strip)
79
79
  num, sym = "", num if sym.nil?
80
80
  num = num[1..-1]
81
- r = Rule.new(sym && sym.to_sym, num, expression(expr).first, :ebnf => self)
81
+ r = Rule.new(sym && sym.to_sym, num, expression(expr).first, ebnf: self)
82
82
  debug("ruleParts") { r.inspect }
83
83
  r
84
84
  end
@@ -54,6 +54,9 @@ module EBNF
54
54
  # @return [Boolean]
55
55
  attr_accessor :start
56
56
 
57
+ # Determines preparation and cleanup rules for reconstituting EBNF ? * + from BNF
58
+ attr_accessor :cleanup
59
+
57
60
  # @param [Integer] id
58
61
  # @param [Symbol] sym
59
62
  # @param [Array] expr
@@ -71,6 +74,7 @@ module EBNF
71
74
  @first = options[:first]
72
75
  @follow = options[:follow]
73
76
  @start = options[:start]
77
+ @cleanup = options[:cleanup]
74
78
  @kind = case
75
79
  when options[:kind] then options[:kind]
76
80
  when sym.to_s == sym.to_s.upcase then :terminal
@@ -97,10 +101,12 @@ module EBNF
97
101
  first = first[1..-1] if first
98
102
  follow = sxp.detect {|e| e.is_a?(Array) && e.first.to_sym == :follow}
99
103
  follow = follow[1..-1] if follow
104
+ cleanup = sxp.detect {|e| e.is_a?(Array) && e.first.to_sym == :cleanup}
105
+ cleanup = cleanup[1..-1] if cleanup
100
106
  start = sxp.any? {|e| e.is_a?(Array) && e.first.to_sym == :start}
101
107
  sym = sxp[1] if sxp[1].is_a?(Symbol)
102
108
  id = sxp[2] if sxp[2].is_a?(String)
103
- Rule.new(sym, id, expr, :kind => sxp.first, :first => first, :follow => follow, :start => start)
109
+ Rule.new(sym, id, expr, kind: sxp.first, first: first, follow: follow, cleanup: cleanup, start: start)
104
110
  end
105
111
 
106
112
  # Build a new rule creating a symbol and numbering from the current rule
@@ -113,9 +119,10 @@ module EBNF
113
119
  def build(expr, options = {})
114
120
  new_sym, new_id = (@top_rule ||self).send(:make_sym_id)
115
121
  Rule.new(new_sym, new_id, expr, {
116
- :kind => options[:kind],
117
- :ebnf => @ebnf,
118
- :top_rule => @top_rule || self,
122
+ kind: options[:kind],
123
+ ebnf: @ebnf,
124
+ top_rule: @top_rule || self,
125
+ cleanup: options[:cleanup],
119
126
  }.merge(options))
120
127
  end
121
128
 
@@ -127,6 +134,7 @@ module EBNF
127
134
  elements << [:start, true] if start
128
135
  elements << first.sort_by(&:to_s).unshift(:first) if first
129
136
  elements << follow.sort_by(&:to_s).unshift(:follow) if follow
137
+ elements << [:cleanup, cleanup] if cleanup
130
138
  elements << expr
131
139
  elements
132
140
  end
@@ -196,16 +204,19 @@ module EBNF
196
204
  this = dup
197
205
  # * Transform (a rule (opt b)) into (a rule (alt _empty b))
198
206
  this.expr = [:alt, :_empty, expr.last]
207
+ this.cleanup = :opt
199
208
  new_rules = this.to_bnf
200
209
  elsif expr.first == :star
201
210
  # * Transform (a rule (star b)) into (a rule (alt _empty (seq b a)))
202
211
  this = dup
203
- new_rule = this.build([:seq, expr.last, this.sym])
212
+ this.cleanup = :star
213
+ new_rule = this.build([:seq, expr.last, this.sym], cleanup: :merge)
204
214
  this.expr = [:alt, :_empty, new_rule.sym]
205
215
  new_rules = [this] + new_rule.to_bnf
206
216
  elsif expr.first == :plus
207
217
  # * Transform (a rule (plus b)) into (a rule (seq b (star b)
208
218
  this = dup
219
+ this.cleanup = :plus
209
220
  this.expr = [:seq, expr.last, [:star, expr.last]]
210
221
  new_rules = this.to_bnf
211
222
  elsif [:alt, :seq].include?(expr.first)
@@ -340,7 +351,7 @@ module EBNF
340
351
 
341
352
  def inspect
342
353
  "#<EBNF::Rule:#{object_id} " +
343
- {:sym => sym, :id => id, :kind => kind, :expr => expr}.inspect +
354
+ {sym: sym, id: id, kind: kind, expr: expr}.inspect +
344
355
  ">"
345
356
  end
346
357
 
@@ -387,7 +398,7 @@ module EBNF
387
398
  private
388
399
  def ttl_expr(expr, pfx, depth, is_obj = true)
389
400
  indent = ' ' * depth
390
- @ebnf.debug("ttl_expr", :depth => depth) {expr.inspect}
401
+ @ebnf.debug("ttl_expr", depth: depth) {expr.inspect}
391
402
  op = expr.shift if expr.is_a?(Array)
392
403
  statements = []
393
404
 
@@ -424,7 +435,7 @@ module EBNF
424
435
  end
425
436
 
426
437
  statements.last << " ." unless is_obj
427
- @ebnf.debug("statements", :depth => depth) {statements.join("\n")}
438
+ @ebnf.debug("statements", depth: depth) {statements.join("\n")}
428
439
  statements
429
440
  end
430
441
 
@@ -461,10 +472,11 @@ module EBNF
461
472
  end
462
473
 
463
474
  # Make a new symbol/number combination
464
- def make_sym_id
475
+ # @param [String] variation added to symbol to aid reconstitution from BNF to EBNF
476
+ def make_sym_id(variation = nil)
465
477
  @id_seq ||= 0
466
478
  @id_seq += 1
467
- ["_#{@sym}_#{@id_seq}".to_sym, "#{@id}.#{@id_seq}"]
479
+ ["_#{@sym}_#{@id_seq}#{variation}".to_sym, "#{@id}.#{@id_seq}#{variation}"]
468
480
  end
469
481
  end
470
482
  end
@@ -94,7 +94,7 @@ module EBNF
94
94
  buffer = if rule.pass?
95
95
  "%-#{lhs_length-2}s" % "@pass"
96
96
  else
97
- lhs_fmt % {:id => "[#{rule.id}]", :sym => rule.sym}
97
+ lhs_fmt % {id: "[#{rule.id}]", sym: rule.sym}
98
98
  end
99
99
  formatted_expr = format(rule.expr)
100
100
  if formatted_expr.length > rhs_length
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ebnf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregg Kellogg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-23 00:00:00.000000000 Z
11
+ date: 2016-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sxp
@@ -169,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
169
  version: '0'
170
170
  requirements: []
171
171
  rubyforge_project:
172
- rubygems_version: 2.5.1
172
+ rubygems_version: 2.6.8
173
173
  signing_key:
174
174
  specification_version: 4
175
175
  summary: EBNF parser and parser generator.