rley 0.0.13 → 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
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