rley 0.0.13 → 0.0.14

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NTdmZDFiY2U1OTE3NDVkOTQ5NDU5OTY4YjAyZWY5MTZhNmE4ZmU5OQ==
4
+ ODY2NWVkNGUzYzNlZDM0MmNhYWIyNGFkYjA2ZGU1NGYyZmNmZTkxMA==
5
5
  data.tar.gz: !binary |-
6
- ZDdjZTA5ZjEwMGNhM2ZlZjIyYjg1ZDliM2E5NjNmZWIzYmU0YjRmZA==
6
+ N2RlYjRlNDc5OWIxZWU1YmIwMzA4MWY0MDBhYmFjYzJlZWIyZTVlNg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- YmNlNWU1Y2NkOGE1NmI4Y2YyYzkzMzRiYjY1NGNhNDNiMDQyMzMzNTI3ZWY2
10
- ZjdjZGRlZjY4ZTI2NDdkNTViYTA5ZDUxOGE3YTY3NzZkMDQ5YjFjNzlmMTU1
11
- YmRlMGVjNmU3ODMzN2MxMzRlYTU0YTVhMzhiYWY4NzcwZmQyYTQ=
9
+ NTBkMTExZWZiZGYxMWZjYTQ0MmUwMDA1ZDE4ZTBlNzYxMDhmZmRkOTdiMWNk
10
+ MzkwNWE0MmMzYjQzZGM5MjBiYjA3NTc3MmVkZDNmNmMzNTkxZGJiYjgxNDVl
11
+ ZGY1N2QwYTY3NzgxNDJjYzc4ZjU1YWRkMmYwNmVmN2JkZGE0OGY=
12
12
  data.tar.gz: !binary |-
13
- MzA2ODE1MWZjN2I3ZDAxNTAzZmQ4YzNhYzJlZGU4NmU0OGEzYjI0MzEzZjBh
14
- ZmI4NzMyYzEyMDlkZDcwNDRkMzk1YzM2NTc4YjhmNzUzZDEwYmRmNDQxMmIw
15
- OTI4N2NmZmVjNzM1OGVhNDY4MzFmMWJhNWE3NWZjZDM3Y2QxZjQ=
13
+ Zjc0YzVlNGU1N2YzNmQ3ZDRlM2VmYmYyNTJhZTQxNDdkZDBkZWYyYWVlZjcy
14
+ NGQwODhmZTcwYzQ1YmY2Y2EzOWEyMmRlMDFlMGUyZjM3NjA4MmU0OGU1YWJj
15
+ MjUwYmNjOWQ3NTAxYmY4ZjNiNWVlNWQ4ZmNjOThkMmQ0NGFkZDA=
@@ -1,3 +1,8 @@
1
+ ### 0.0.14 / 2014-11-20
2
+ * [NEW] `EarleyParser` now supports grammar with empty productions (i.e. nullable nonterminals).
3
+ * [CHANGE] (private) method `EarleyParser#prediction` updated with Ayock-Horspool improvement.
4
+ * [CHANGE] Moved class `DottedItem` under the `Parser` module.
5
+
1
6
  ### 0.0.13 / 2014-11-19
2
7
  * [NEW] (private) method `Grammar#compute_nullable` added.
3
8
  * [CHANGE] `Grammar#initialize` constructor calls the method `Grammar#compute_nullable`
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Rley # Module used as a namespace
5
5
  # The version number of the gem.
6
- Version = '0.0.13'
6
+ Version = '0.0.14'
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = "Ruby implementation of the Earley's parsing algorithm"
@@ -13,71 +13,73 @@ module Rley # This module is used as a namespace
13
13
  # An item with the dot at the end (i.e. after all rhs symbols)
14
14
  # is called a reduce item.
15
15
  # An item with a dot in front of a terminal is called a shift item.
16
- class DottedItem
17
- # Production rule
18
- attr_reader(:production)
16
+ module Parser # This module is used as a namespace
17
+ class DottedItem
18
+ # Production rule
19
+ attr_reader(:production)
19
20
 
20
- # Index of the next symbol (from the rhs) after the 'dot'.
21
- # If the dot is at the end of the rhs (i.e.) there is no next
22
- # symbol, then the position takes the value -1.
23
- # It the rhs is empty, then the postion is -2
24
- attr_reader(:position)
21
+ # Index of the next symbol (from the rhs) after the 'dot'.
22
+ # If the dot is at the end of the rhs (i.e.) there is no next
23
+ # symbol, then the position takes the value -1.
24
+ # It the rhs is empty, then the postion is -2
25
+ attr_reader(:position)
25
26
 
26
- # @param aProduction
27
- def initialize(aProduction, aPosition)
28
- @production = aProduction
29
- @position = valid_position(aPosition)
30
- end
31
-
32
- # Return true if the dot position is at the start of the rhs.
33
- def at_start?()
34
- return position == 0 || position == -2
35
- end
27
+ # @param aProduction
28
+ def initialize(aProduction, aPosition)
29
+ @production = aProduction
30
+ @position = valid_position(aPosition)
31
+ end
32
+
33
+ # Return true if the dot position is at the start of the rhs.
34
+ def at_start?()
35
+ return position == 0 || position == -2
36
+ end
36
37
 
37
- # An item with the dot at the beginning is called
38
- # predicted item
39
- alias_method :predicted_item?, :at_start?
38
+ # An item with the dot at the beginning is called
39
+ # predicted item
40
+ alias_method :predicted_item?, :at_start?
40
41
 
41
- # A dotted item is called a reduce item if the dot is at the end.
42
- def reduce_item?()
43
- return position < 0 # Either -1 or -2
44
- end
45
-
46
- # The non-terminal symbol that is on the left-side of the production
47
- def lhs()
48
- return production.lhs
49
- end
50
-
51
- # Return the symbol after the dot.
52
- # nil is returned if the dot is at the end
53
- def next_symbol()
54
- return (position < 0) ? nil : production.rhs[position]
55
- end
42
+ # A dotted item is called a reduce item if the dot is at the end.
43
+ def reduce_item?()
44
+ return position < 0 # Either -1 or -2
45
+ end
46
+
47
+ # The non-terminal symbol that is on the left-side of the production
48
+ def lhs()
49
+ return production.lhs
50
+ end
51
+
52
+ # Return the symbol after the dot.
53
+ # nil is returned if the dot is at the end
54
+ def next_symbol()
55
+ return (position < 0) ? nil : production.rhs[position]
56
+ end
56
57
 
57
- # An item with the dot in front of a terminal is called a shift item
58
- def shift_item?()
59
- end
58
+ # An item with the dot in front of a terminal is called a shift item
59
+ def shift_item?()
60
+ end
60
61
 
61
- private
62
+ private
62
63
 
63
- # Return the given after its validation.
64
- def valid_position(aPosition)
65
- rhs_size = production.rhs.size
66
- if aPosition < 0 || aPosition > rhs_size
67
- fail StandardError, 'Out of bound index'
68
- end
64
+ # Return the given after its validation.
65
+ def valid_position(aPosition)
66
+ rhs_size = production.rhs.size
67
+ if aPosition < 0 || aPosition > rhs_size
68
+ fail StandardError, 'Out of bound index'
69
+ end
69
70
 
70
- if rhs_size == 0
71
- index = -2 # Minus 2 at start/end of empty production
72
- elsif aPosition == rhs_size
73
- index = -1 # Minus 1 at end of non-empty production
74
- else
75
- index = aPosition
76
- end
71
+ if rhs_size == 0
72
+ index = -2 # Minus 2 at start/end of empty production
73
+ elsif aPosition == rhs_size
74
+ index = -1 # Minus 1 at end of non-empty production
75
+ else
76
+ index = aPosition
77
+ end
77
78
 
78
- return index
79
- end
80
- end # class
79
+ return index
80
+ end
81
+ end # class
82
+ end # module
81
83
  end # module
82
84
 
83
85
  # End of file
@@ -63,7 +63,7 @@ module Rley # This module is used as a namespace
63
63
  aGrammar.rules.each do |prod|
64
64
  rhs_size = prod.rhs.size
65
65
  if rhs_size == 0
66
- items << DottemItem.new(prod, 0)
66
+ items << DottedItem.new(prod, 0)
67
67
  else
68
68
  items += (0..rhs_size).map { |i| DottedItem.new(prod, i) }
69
69
  end
@@ -48,6 +48,8 @@ module Rley # This module is used as a namespace
48
48
  rhs_constituents = rhs_repr.map { |name| get_nonterminal(name) }
49
49
  when String
50
50
  rhs_constituents = [ get_nonterminal(rhs_repr) ]
51
+ when Terminal
52
+ rhs_constituents = [ rhs_repr ]
51
53
  end
52
54
  new_prod = Production.new(lhs, rhs_constituents)
53
55
  productions << new_prod
@@ -5,6 +5,7 @@ require_relative '../../../lib/rley/syntax/non_terminal'
5
5
  require_relative '../../../lib/rley/syntax/production'
6
6
  require_relative '../../../lib/rley/syntax/grammar_builder'
7
7
  require_relative '../../../lib/rley/parser/token'
8
+ require_relative '../../../lib/rley/parser/dotted_item'
8
9
  # Load the class under test
9
10
  require_relative '../../../lib/rley/parser/earley_parser'
10
11
 
@@ -373,7 +374,10 @@ module Rley # Open this namespace to avoid module qualifier prefixes
373
374
  Token.new('*', t_star),
374
375
  Token.new('4', t_int)
375
376
  ]
376
- parse_result = subject.parse(tokens)
377
+ instance = EarleyParser.new(builder.grammar)
378
+ expect { instance.parse(tokens) }.not_to raise_error
379
+ parse_result = instance.parse(tokens)
380
+ expect(parse_result.success?).to eq(true)
377
381
  end
378
382
 
379
383
 
@@ -432,6 +436,149 @@ module Rley # Open this namespace to avoid module qualifier prefixes
432
436
  state_set_3 = parse_result.chart[3]
433
437
  expect(state_set_3.states).to be_empty # This is an error symptom
434
438
  end
439
+
440
+ it 'should parse a grammar with nullable nonterminals' do
441
+ # Grammar 4: A grammar with nullable nonterminal
442
+ # based on example in "Parsing Techniques" book (D. Grune, C. Jabobs)
443
+ # Z ::= E.
444
+ # E ::= E Q F.
445
+ # E ::= F.
446
+ # F ::= a.
447
+ # Q ::= *.
448
+ # Q ::= /.
449
+ # Q ::=.
450
+ t_a = Syntax::VerbatimSymbol.new('a')
451
+ t_star = Syntax::VerbatimSymbol.new('*')
452
+ t_slash = Syntax::VerbatimSymbol.new('/')
453
+
454
+ builder = Syntax::GrammarBuilder.new
455
+ builder.add_terminals(t_a, t_star, t_slash)
456
+ builder.add_production('Z' => 'E')
457
+ builder.add_production('E' => %w(E Q F))
458
+ builder.add_production('E' => 'F')
459
+ builder.add_production('F' => t_a)
460
+ builder.add_production('Q' => t_star)
461
+ builder.add_production('Q' => t_slash)
462
+ builder.add_production('Q' => []) # Empty production
463
+ tokens = [
464
+ Token.new('a', t_a),
465
+ Token.new('a', t_a),
466
+ Token.new('/', t_slash),
467
+ Token.new('a', t_a)
468
+ ]
469
+ prod_Z = builder.productions[0]
470
+ prod_E1 = builder.productions[1]
471
+ prod_E2 = builder.productions[2]
472
+ prod_F = builder.productions[3]
473
+ prod_Q1 = builder.productions[4]
474
+ prod_Q2 = builder.productions[5]
475
+ prod_Q3 = builder.productions[6]
476
+
477
+ instance = EarleyParser.new(builder.grammar)
478
+ expect { instance.parse(tokens) }.not_to raise_error
479
+ parse_result = instance.parse(tokens)
480
+ expect(parse_result.success?).to eq(true)
481
+
482
+ ###################### S(0) == . a a / a
483
+ # Expectation chart[0]:
484
+ # (1) S -> . E, 0 # start rule
485
+ # (2) E -> . E Q F, 0 # predict from (1)
486
+ # (3) E -> . F, 0 # predict from (1)
487
+ # (4) F -> . a # predict from (3)
488
+ expectations = [
489
+ { origin: 0, production: prod_Z, dot: 0 },
490
+ { origin: 0, production: prod_E1, dot: 0 },
491
+ { origin: 0, production: prod_E2, dot: 0 },
492
+ { origin: 0, production: prod_F, dot: 0 }
493
+ ]
494
+ compare_state_set(parse_result.chart[0], expectations)
495
+
496
+ ###################### S(1) == a . a / a
497
+ # Expectation chart[1]:
498
+ # (1) F -> a ., 0 # scan from S(0) 4
499
+ # (2) E -> F ., 0 # complete from (1) and S(0) 3
500
+ # (3) S -> E ., 0 # complete from (2) and S(0) 1
501
+ # (4) E -> E . Q F, 0 # complete from (2) and S(0) 2
502
+ # (5) Q -> . *, 1 # Predict from (4)
503
+ # (6) Q -> . /, 1 # Predict from (4)
504
+ # (7) Q -> ., 1 # Predict from (4)
505
+ # (8) E -> E Q . F, 0 # Modified predict from (4)
506
+ # (9) F -> . a, 1 # Predict from (8)
507
+ expectations = [
508
+ { origin: 0, production: prod_F, dot: -1 },
509
+ { origin: 0, production: prod_E2, dot: -1 },
510
+ { origin: 0, production: prod_Z, dot: -1 },
511
+ { origin: 0, production: prod_E1, dot: 1 },
512
+ { origin: 1, production: prod_Q1, dot: 0 },
513
+ { origin: 1, production: prod_Q2, dot: 0 },
514
+ { origin: 1, production: prod_Q3, dot: -2 },
515
+ { origin: 0, production: prod_E1, dot: 2 },
516
+ { origin: 1, production: prod_F, dot: 0 }
517
+ ]
518
+ compare_state_set(parse_result.chart[1], expectations)
519
+
520
+ ###################### S(2) == a a . / a
521
+ # Expectation chart[2]:
522
+ # (1) F -> a ., 1 # scan from S(1) 9
523
+ # (2) E -> E Q F ., 0 # complete from (1) and S(1) 8
524
+ # (3) S -> E ., 0 # complete from (1) and S(0) 1
525
+ # (4) E -> E . Q F, 0 # complete from (1) and S(0) 2
526
+ # (5) Q -> . *, 2 # Predict from (4)
527
+ # (6) Q -> . /, 2 # Predict from (4)
528
+ # (7) Q -> ., 2 # Predict from (4)
529
+ # (8) E -> E Q . F, 0 # Complete from (5) and S(1) 4
530
+ # (9) F -> . a, 1 # Predict from (8)
531
+ expectations = [
532
+ { origin: 1, production: prod_F, dot: -1 },
533
+ { origin: 0, production: prod_E1, dot: -1 },
534
+ { origin: 0, production: prod_Z, dot: -1 },
535
+ { origin: 0, production: prod_E1, dot: 1 },
536
+ { origin: 2, production: prod_Q1, dot: 0 },
537
+ { origin: 2, production: prod_Q2, dot: 0 },
538
+ { origin: 2, production: prod_Q3, dot: -2 },
539
+ { origin: 0, production: prod_E1, dot: 2 },
540
+ { origin: 2, production: prod_F, dot: 0 },
541
+ ]
542
+ compare_state_set(parse_result.chart[2], expectations)
543
+
544
+ ###################### S(3) == a a / . a
545
+ # Expectation chart[3]:
546
+ # (1) Q -> / ., 2 # scan from S(2) 6
547
+ # (2) E -> E Q . F, 0 # complete from (1) and S(1) 4
548
+ # (3) F -> . a, 3 # Predict from (2)
549
+ expectations = [
550
+ { origin: 2, production: prod_Q2, dot: -1 },
551
+ { origin: 0, production: prod_E1, dot: 2 },
552
+ { origin: 3, production: prod_F, dot: 0 }
553
+ ]
554
+ compare_state_set(parse_result.chart[3], expectations)
555
+
556
+
557
+ ###################### S(4) == a a / a .
558
+ # Expectation chart[4]:
559
+ # (1) F -> a ., 3 # scan from S(3) 3
560
+ # (2) E -> E Q F ., 0 # complete from (1) and S(3) 2
561
+ # (3) S -> E ., 0 # complete from (2) and S(0) 1
562
+ # (4) E -> E . Q F, 0 # complete from (2) and S(0) 2
563
+ # (5) Q -> . *, 3 # Predict from (4)
564
+ # (6) Q -> . /, 3 # Predict from (4)
565
+ # (7) Q -> ., 3 # Predict from (4)
566
+ # (8) E -> E Q . F, 0 # Modified predict from (4)
567
+ # (9) F -> . a, 4 # Predict from (8)
568
+ expectations = [
569
+ { origin: 3, production: prod_F, dot: -1 },
570
+ { origin: 0, production: prod_E1, dot: -1 },
571
+ { origin: 0, production: prod_Z, dot: -1 },
572
+ { origin: 0, production: prod_E1, dot: 1 },
573
+ { origin: 4, production: prod_Q1, dot: 0 },
574
+ { origin: 4, production: prod_Q2, dot: 0 },
575
+ { origin: 4, production: prod_Q3, dot: -2 },
576
+ { origin: 0, production: prod_E1, dot: 2 },
577
+ { origin: 4, production: prod_F, dot: 0 },
578
+ ]
579
+ compare_state_set(parse_result.chart[4], expectations)
580
+
581
+ end
435
582
  end # context
436
583
 
437
584
  end # describe
@@ -11,7 +11,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
11
11
  it 'should be created without argument' do
12
12
  expect { GrammarBuilder.new }.not_to raise_error
13
13
  end
14
-
14
+
15
15
  it 'should have no grammar symbols at start' do
16
16
  expect(subject.symbols).to be_empty
17
17
  end
@@ -58,14 +58,14 @@ module Rley # Open this namespace to avoid module qualifier prefixes
58
58
  expect(subject.symbols['c']).to eq(c)
59
59
  end
60
60
  end # context
61
-
61
+
62
62
  context 'Adding productions:' do
63
63
  subject do
64
64
  instance = GrammarBuilder.new
65
65
  instance.add_terminals('a', 'b', 'c')
66
66
  instance
67
67
  end
68
-
68
+
69
69
  it 'should add a valid production' do
70
70
  # case of a rhs representation that consists of one name
71
71
  expect { subject.add_production('S' => 'A') }.not_to raise_error
@@ -73,22 +73,22 @@ module Rley # Open this namespace to avoid module qualifier prefixes
73
73
  new_prod = subject.productions[0]
74
74
  expect(new_prod.lhs).to eq(subject['S'])
75
75
  expect(new_prod.rhs[0]).to eq(subject['A'])
76
-
76
+
77
77
  subject.add_production('A' => %w(a A c))
78
78
  expect(subject.productions.size).to eq(2)
79
79
  new_prod = subject.productions.last
80
80
  expect(new_prod.lhs).to eq(subject['A'])
81
81
  expect_rhs = [ subject['a'], subject['A'], subject['c'] ]
82
82
  expect(new_prod.rhs.members).to eq(expect_rhs)
83
-
83
+
84
84
  subject.add_production('A' => ['b'])
85
85
  expect(subject.productions.size).to eq(3)
86
86
  new_prod = subject.productions.last
87
87
  expect(new_prod.lhs).to eq(subject['A'])
88
88
  expect(new_prod.rhs[0]).to eq(subject['b'])
89
- end
89
+ end
90
90
  end # context
91
-
91
+
92
92
  context 'Building grammar:' do
93
93
  subject do
94
94
  instance = GrammarBuilder.new
@@ -98,20 +98,20 @@ module Rley # Open this namespace to avoid module qualifier prefixes
98
98
  instance.add_production('A' => ['b'])
99
99
  instance
100
100
  end
101
-
101
+
102
102
  it 'should build a grammar' do
103
103
  expect(subject.grammar).to be_kind_of(Grammar)
104
104
  grm = subject.grammar
105
105
  expect(grm.rules).to eq(subject.productions)
106
106
  end
107
-
107
+
108
108
  it 'should complain in absence of symbols' do
109
109
  instance = GrammarBuilder.new
110
110
  err = StandardError
111
111
  msg = 'No symbol found for grammar'
112
112
  expect { instance.grammar }.to raise_error(err, msg)
113
113
  end
114
-
114
+
115
115
  it 'should complain in absence of productions' do
116
116
  instance = GrammarBuilder.new
117
117
  instance.add_terminals('a', 'b', 'c')
@@ -119,7 +119,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
119
119
  msg = 'No production found for grammar'
120
120
  expect { instance.grammar }.to raise_error(err, msg)
121
121
  end
122
-
122
+
123
123
  it 'should complain when non-terminal has no production' do
124
124
  instance = GrammarBuilder.new
125
125
  instance.add_terminals('a', 'b', 'c')
@@ -128,6 +128,34 @@ module Rley # Open this namespace to avoid module qualifier prefixes
128
128
  msg = 'Nonterminal A not rewritten'
129
129
  expect { instance.grammar }.to raise_error(err, msg)
130
130
  end
131
+
132
+ it 'should build a grammar with nullable nonterminals' do
133
+ # Grammar 4: A grammar with nullable nonterminal
134
+ # based on example in "Parsing Techniques" book (D. Grune, C. Jabobs)
135
+ # S ::= E.
136
+ # E ::= E Q F.
137
+ # E ::= F.
138
+ # F ::= a.
139
+ # Q ::= *.
140
+ # Q ::= /.
141
+ # Q ::=.
142
+ t_a = VerbatimSymbol.new('a')
143
+ t_star = VerbatimSymbol.new('*')
144
+ t_slash = VerbatimSymbol.new('/')
145
+
146
+ builder = GrammarBuilder.new
147
+ builder.add_terminals(t_a, t_star, t_slash)
148
+ builder.add_production('S' => 'E')
149
+ builder.add_production('E' => %w(E Q F))
150
+ builder.add_production('E' => 'F')
151
+ builder.add_production('F' => t_a)
152
+ builder.add_production('Q' => t_star)
153
+ builder.add_production('Q' => t_slash)
154
+ builder.add_production('Q' => []) # Empty production
155
+
156
+ expect { builder.grammar }.not_to raise_error
157
+ expect(builder.productions.last).to be_empty
158
+ end
131
159
  end
132
160
 
133
161
  end # describe
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rley
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-19 00:00:00.000000000 Z
11
+ date: 2014-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake