rley 0.0.08 → 0.0.09

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
- MzI5OGY5NWE3MzQ1OGQ4NjFmOGIyMjQ4ZTM4Y2M0NzE4YTg2ZmRhMQ==
4
+ NjMyOTZiYzU4MjllMDU0NjlmYTZjZWIxNWY1Y2U0YTlmYjQzOWQxMg==
5
5
  data.tar.gz: !binary |-
6
- MmNhYWMxODI3YjQ1NTMyYzk4NmE5NDk3Yzc4YTc4NTQzNjk0MzNkZg==
6
+ NzlhZDNkZGIwZTMzMzJhZmQyYWVlODE5MzUzODQ4NWNmMzFiY2M2ZQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MDZmZGFhMDVjMGI1YjQ0ZGUwNjIyMmMwNzBkYWRkOTVkMGU4ZGJhNGQ3ZDFi
10
- YzRmMTdlMDBhZTlhOWZlMWVlNjM1ZWRhZWY5NzQ2Y2EyNDJhYzg2YWJhYjU3
11
- YjNmMTQ5MzgwNmRlYTEyNmEyNTc1YjdhZjgxNTQ4YTNmYTA2MjA=
9
+ MjU2MjE0ZGQwZDE2MjU1OGIzYzRkZjc4ZDZmNzI4ODQ3YmY2NGQ4MWY4YWFk
10
+ NTQzZGY0YjNhMjUwNWUzOWYwMjM0ZTk2ODRlMjQ3M2RhNWViMWU4ZGEwNjEx
11
+ ODQzOTk5MzM1NzE3MTY3ZWRiOGNmZWIwOWY2YTE4MzVkM2ViOGU=
12
12
  data.tar.gz: !binary |-
13
- ZGE3MDM5OTNlNDEwN2EwMTQzZGQ2ZmMzNmUyNjVmMGY2MmQ0MjRlMWJkMmM1
14
- NDI3YWEzNTlhYTQzMDZlNDZjYmI1NzNiNTYwMDI0NGY4NmI4NGQ4Yjc3OGQ5
15
- ZmE4MDQ3ZjZmYTljYTg0YjEyOWYzMmU5ODUyOTU4Yjk5MWJjZGY=
13
+ YzczYTY4Y2FlMWUyZWNhMjEyYjhhM2RkMzk0YzNmNjQ0MTE4NGZiMjQ2MDY2
14
+ Y2IzOGI1YjNjNzgyZTNlMmMyNzAyYjc3MDQzYjY2YzkzZmVjZDE5OThkZTc5
15
+ ZTMzMGVmNGUwYjk0Y2QxN2E3OGJjNGI0MWE0ZDRlZTRkYmY2NGM=
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
+ ### 0.0.09 / 2014-11-15
2
+ * [NEW] New class `GrammarBuilder` added and tested, its purpose is
3
+ to simplify the construction of grammars.
4
+
5
+
1
6
  ### 0.0.08 / 2014-11-14
2
7
  * [CHANGE] `EarleyParser#parse` method: Initial API documentation.
3
- * [INFO] This version was committed to force Travis CI to execute a complete build (failed because Travis couldn't connect to GitHub)
8
+ * [INFO] This version was committed to force Travis CI to execute a complete build
9
+ failed because Travis couldn't connect to GitHub)
4
10
 
5
11
  ### 0.0.07 / 2014-11-14
6
12
  * [CHANGE] spec file of `EarleyParser` class: Test added. Parser works with simple expression grammar.
@@ -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.08'
6
+ Version = '0.0.09'
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = "Ruby implementation of the Earley's parsing algorithm"
@@ -20,8 +20,8 @@ module Rley # This module is used as a namespace
20
20
  # after "advancing" the dot
21
21
  attr_reader(:next_mapping)
22
22
 
23
- # @param aGrammar [Grammar] A context-free grammar that defines the language
24
- # of the input to be parsed.
23
+ # @param aGrammar [Grammar] A context-free grammar that defines the
24
+ # language of the input to be parsed.
25
25
  def initialize(aGrammar)
26
26
  @grammar = aGrammar
27
27
  @dotted_items = build_dotted_items(grammar)
@@ -0,0 +1,114 @@
1
+ require_relative 'verbatim_symbol'
2
+ require_relative 'literal'
3
+ require_relative 'non_terminal'
4
+ require_relative 'production'
5
+ require_relative 'grammar'
6
+
7
+ module Rley # This module is used as a namespace
8
+ module Syntax # This module is used as a namespace
9
+ # Builder pattern. Builder pattern builds a complex object
10
+ # (say, a grammar) from simpler objects (terminals and productions)
11
+ # and using a step by step approach.
12
+ class GrammarBuilder
13
+ # The list of symbols of the language.
14
+ # Grammar symbols are categorized into terminal (symbol)
15
+ # and non-terminal (symbol).
16
+ attr_reader(:symbols)
17
+
18
+ # The list of production rules for the grammar to build
19
+ attr_reader(:productions)
20
+
21
+ def initialize()
22
+ @symbols = {}
23
+ @productions = []
24
+ end
25
+
26
+ # Retrieve a grammar symbol from its name.
27
+ # Raise an exception if not found.
28
+ # @param [aSymbolName] the name of a symbol grammar.
29
+ # @return [GrmSymbol] the retrieved symbol.
30
+ def [](aSymbolName)
31
+ return symbols[aSymbolName]
32
+ end
33
+
34
+ # Add the terminal symbols of the language
35
+ # terminalSymbols [String or Terminal] one or more terminal symbols
36
+ # to add to the grammar.
37
+ def add_terminals(*terminalSymbols)
38
+ new_symbs = build_symbols(Terminal, terminalSymbols)
39
+ symbols.merge!(new_symbs)
40
+ end
41
+
42
+ # Add the non-terminal symbols of the language
43
+ # nonTerminalSymbols [String or NonTerminal] one or more non-terminal
44
+ # symbols to add to the grammar.
45
+ def add_non_terminals(*nonTerminalSymbols)
46
+ new_symbs = build_symbols(NonTerminal, nonTerminalSymbols)
47
+ symbols.merge!(new_symbs)
48
+ end
49
+
50
+ # builder.add_production('A' => ['a', 'A', 'c'])
51
+ def add_production(aProductionRepr)
52
+ aProductionRepr.each_pair do |(lhs_name, rhs_repr)|
53
+ lhs = self[lhs_name]
54
+ case rhs_repr
55
+ when Array
56
+ rhs_constituents = rhs_repr.map { |name| self[name] }
57
+ when String
58
+ rhs_constituents = [ self[rhs_repr] ]
59
+ end
60
+ new_prod = Production.new(lhs, rhs_constituents)
61
+ productions << new_prod
62
+ end
63
+ end
64
+
65
+ # Given the grammar symbols and productions added to the builder,
66
+ # build the resulting grammar.
67
+ def grammar()
68
+ fail StandardError, 'No symbol found for grammar' if symbols.empty?
69
+ if productions.empty?
70
+ fail StandardError, 'No production found for grammar'
71
+ end
72
+
73
+ return Grammar.new(productions.dup)
74
+ end
75
+
76
+ private
77
+
78
+ # Add the given grammar symbols.
79
+ # @param aClass [Class] The class of grammar symbols to instantiate.
80
+ # @param aSymbol [Array] array of elements are treated as follows:
81
+ # if the element is already a grammar symbol, then it added as is,
82
+ # otherwise it is considered as the name of a grammar symbol
83
+ # of the specified class to build.
84
+ def build_symbols(aClass, theSymbols)
85
+ symbs = {}
86
+ theSymbols.each do |s|
87
+ new_symbol = build_symbol(aClass, s)
88
+ symbs[new_symbol.name] = new_symbol
89
+ end
90
+
91
+ return symbs
92
+ end
93
+
94
+
95
+ # If the argument is already a grammar symbol object then it is
96
+ # returned as is. Otherwise, the argument is treated as a name
97
+ # for a new instance of the given class.
98
+ # @param aClass [Class] The class of grammar symbols to instantiate
99
+ # @param aSymbol [GrmSymbol-like or String]
100
+ # @return [Array] list of grammar symbols
101
+ def build_symbol(aClass, aSymbolArg)
102
+ if aSymbolArg.kind_of?(GrmSymbol)
103
+ a_symbol = aSymbolArg
104
+ else
105
+ a_symbol = aClass.new(aSymbolArg)
106
+ end
107
+
108
+ return a_symbol
109
+ end
110
+ end # class
111
+ end # module
112
+ end # module
113
+
114
+ # End of file
@@ -22,7 +22,7 @@ module Rley # This module is used as a namespace
22
22
  alias_method :head, :lhs
23
23
 
24
24
  def initialize(aNonTerminal, theSymbols)
25
- @lhs = aNonTerminal
25
+ @lhs = valid_lhs(aNonTerminal)
26
26
  @rhs = SymbolSeq.new(theSymbols)
27
27
  end
28
28
 
@@ -31,6 +31,20 @@ module Rley # This module is used as a namespace
31
31
  def empty?()
32
32
  return rhs.empty?
33
33
  end
34
+
35
+ private
36
+
37
+ # Validation method. Return the validated input argument or
38
+ # raise an exception.
39
+ def valid_lhs(aNonTerminal)
40
+ unless aNonTerminal.kind_of?(NonTerminal)
41
+ msg_prefix = 'Left side of production must be a non-terminal symbol'
42
+ msg_suffix = ", found a #{aNonTerminal.class} instead."
43
+ fail StandardError, msg_prefix + msg_suffix
44
+ end
45
+
46
+ return aNonTerminal
47
+ end
34
48
  end # class
35
49
  end # module
36
50
  end # module
@@ -282,7 +282,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
282
282
  { origin: 0, production: prod_S2, dot: -1 },
283
283
  { origin: 0, production: prod_M1, dot: 1 },
284
284
  { origin: 0, production: prod_P, dot: -1 },
285
- { origin: 0, production: prod_S1, dot: 1 },
285
+ { origin: 0, production: prod_S1, dot: 1 }
286
286
  ]
287
287
  compare_state_set(parse_result.chart[1], expectations)
288
288
 
@@ -297,7 +297,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
297
297
  { origin: 0, production: prod_S1, dot: 2 },
298
298
  { origin: 2, production: prod_M1, dot: 0 },
299
299
  { origin: 2, production: prod_M2, dot: 0 },
300
- { origin: 2, production: prod_T, dot: 0 },
300
+ { origin: 2, production: prod_T, dot: 0 }
301
301
  ]
302
302
  compare_state_set(parse_result.chart[2], expectations)
303
303
 
@@ -324,7 +324,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
324
324
 
325
325
  expectations = [
326
326
  { origin: 2, production: prod_M1, dot: 2 },
327
- { origin: 4, production: prod_T, dot: 0 },
327
+ { origin: 4, production: prod_T, dot: 0 }
328
328
  ]
329
329
  compare_state_set(parse_result.chart[4], expectations)
330
330
 
@@ -0,0 +1,142 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ # Load the class under test
4
+ require_relative '../../../lib/rley/syntax/grammar_builder'
5
+
6
+ module Rley # Open this namespace to avoid module qualifier prefixes
7
+ module Syntax # Open this namespace to avoid module qualifier prefixes
8
+ describe GrammarBuilder do
9
+
10
+ context 'Initialization:' do
11
+ it 'should be created without argument' do
12
+ expect { GrammarBuilder.new }.not_to raise_error
13
+ end
14
+
15
+ it 'should have no grammar symbols at start' do
16
+ expect(subject.symbols).to be_empty
17
+ end
18
+
19
+ it 'should have no productions at start' do
20
+ expect(subject.productions).to be_empty
21
+ end
22
+
23
+ end # context
24
+
25
+ context 'Adding symbols:' do
26
+ it 'should build terminals from their names' do
27
+ subject.add_terminals('a', 'b', 'c')
28
+ expect(subject.symbols.size).to eq(3)
29
+ expect(subject.symbols['a']).to be_kind_of(Terminal)
30
+ expect(subject.symbols['a'].name).to eq('a')
31
+ expect(subject.symbols['b']).to be_kind_of(Terminal)
32
+ expect(subject.symbols['b'].name).to eq('b')
33
+ expect(subject.symbols['c']).to be_kind_of(Terminal)
34
+ expect(subject.symbols['c'].name).to eq('c')
35
+ end
36
+
37
+ it 'should accept already built terminals' do
38
+ a = Terminal.new('a')
39
+ b = VerbatimSymbol.new('b')
40
+ c = Literal.new('c', /c/)
41
+
42
+ subject.add_terminals(a, b, c)
43
+ expect(subject.symbols.size).to eq(3)
44
+ expect(subject.symbols['a']).to eq(a)
45
+ expect(subject.symbols['b']).to eq(b)
46
+ expect(subject.symbols['c']).to eq(c)
47
+ end
48
+
49
+ it 'should build non-terminals from their names' do
50
+ subject.add_non_terminals('PP', 'VP', 'DT')
51
+ expect(subject.symbols.size).to eq(3)
52
+ expect(subject.symbols['PP']).to be_kind_of(NonTerminal)
53
+ expect(subject.symbols['PP'].name).to eq('PP')
54
+ expect(subject.symbols['VP']).to be_kind_of(NonTerminal)
55
+ expect(subject.symbols['VP'].name).to eq('VP')
56
+ expect(subject.symbols['DT']).to be_kind_of(NonTerminal)
57
+ expect(subject.symbols['DT'].name).to eq('DT')
58
+ end
59
+
60
+ it 'should accept already built terminals' do
61
+ a = Terminal.new('a')
62
+ b = VerbatimSymbol.new('b')
63
+ c = Literal.new('c', /c/)
64
+
65
+ subject.add_terminals(a, b, c)
66
+ expect(subject.symbols.size).to eq(3)
67
+ expect(subject.symbols['a']).to eq(a)
68
+ expect(subject.symbols['b']).to eq(b)
69
+ expect(subject.symbols['c']).to eq(c)
70
+ end
71
+ end # context
72
+
73
+ context 'Adding productions:' do
74
+ subject do
75
+ instance = GrammarBuilder.new
76
+ instance.add_terminals('a', 'b', 'c')
77
+ instance.add_non_terminals('S', 'A')
78
+ instance
79
+ end
80
+
81
+ it 'should add a valid production' do
82
+ # case of a rhs representation that consists of one name
83
+ expect { subject.add_production('S' => 'A') }.not_to raise_error
84
+ expect(subject.productions.size).to eq(1)
85
+ new_prod = subject.productions[0]
86
+ expect(new_prod.lhs).to eq(subject['S'])
87
+ expect(new_prod.rhs[0]).to eq(subject['A'])
88
+
89
+ subject.add_production('A' => %w(a A c))
90
+ expect(subject.productions.size).to eq(2)
91
+ new_prod = subject.productions.last
92
+ expect(new_prod.lhs).to eq(subject['A'])
93
+ expect_rhs = [ subject['a'], subject['A'], subject['c'] ]
94
+ expect(new_prod.rhs.members).to eq(expect_rhs)
95
+
96
+ subject.add_production('A' => ['b'])
97
+ expect(subject.productions.size).to eq(3)
98
+ new_prod = subject.productions.last
99
+ expect(new_prod.lhs).to eq(subject['A'])
100
+ expect(new_prod.rhs[0]).to eq(subject['b'])
101
+ end
102
+ end # context
103
+
104
+ context 'Building grammar:' do
105
+ subject do
106
+ instance = GrammarBuilder.new
107
+ instance.add_terminals('a', 'b', 'c')
108
+ instance.add_non_terminals('S', 'A')
109
+ instance.add_production('S' => ['A'])
110
+ instance.add_production('A' => %w(a A c))
111
+ instance.add_production('A' => ['b'])
112
+ instance
113
+ end
114
+
115
+ it 'should build a grammar' do
116
+ expect(subject.grammar).to be_kind_of(Grammar)
117
+ grm = subject.grammar
118
+ expect(grm.rules).to eq(subject.productions)
119
+ end
120
+
121
+ it 'should complain in absence of symbols' do
122
+ instance = GrammarBuilder.new
123
+ err = StandardError
124
+ msg = 'No symbol found for grammar'
125
+ expect { instance.grammar }.to raise_error(err, msg)
126
+ end
127
+
128
+ it 'should complain in absence of productions' do
129
+ instance = GrammarBuilder.new
130
+ instance.add_terminals('a', 'b', 'c')
131
+ instance.add_non_terminals('S', 'A')
132
+ err = StandardError
133
+ msg = 'No production found for grammar'
134
+ expect { instance.grammar }.to raise_error(err, msg)
135
+ end
136
+ end
137
+
138
+ end # describe
139
+ end # module
140
+ end # module
141
+
142
+ # End of file
@@ -39,6 +39,15 @@ module Rley # Open this namespace to avoid module qualifier prefixes
39
39
  instance = Production.new(sentence, [])
40
40
  expect(instance).to be_empty
41
41
  end
42
+
43
+ it 'should complain if its lhs is not a non-terminal' do
44
+ err = StandardError
45
+ msg_prefix = 'Left side of production must be a non-terminal symbol'
46
+ msg_suffix = ", found a #{String} instead."
47
+ msg = msg_prefix + msg_suffix
48
+ expect { Production.new('wrong', sequence) }.to raise_error(err, msg)
49
+
50
+ end
42
51
  end # context
43
52
 
44
53
  end # describe
@@ -41,7 +41,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
41
41
 
42
42
  context 'Provided services:' do
43
43
  it 'should compare compare with itself' do
44
- me = subject
44
+ me = subject # Use another name to please Rubocop
45
45
  expect(subject == me).to eq(true)
46
46
  end
47
47
 
@@ -55,6 +55,12 @@ module Rley # Open this namespace to avoid module qualifier prefixes
55
55
  unequal_one = SymbolSeq.new([verb, pp, np])
56
56
  expect(subject == unequal_one).not_to eq(true)
57
57
  end
58
+
59
+ it 'should complain when unable to compare' do
60
+ err = StandardError
61
+ msg = 'Cannot compare a SymbolSeq with a Fixnum'
62
+ expect { subject == 1 }.to raise_error(err, msg)
63
+ end
58
64
  end # context
59
65
 
60
66
  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.08
4
+ version: 0.0.09
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-14 00:00:00.000000000 Z
11
+ date: 2014-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -94,6 +94,7 @@ files:
94
94
  - lib/rley/parser/state_set.rb
95
95
  - lib/rley/parser/token.rb
96
96
  - lib/rley/syntax/grammar.rb
97
+ - lib/rley/syntax/grammar_builder.rb
97
98
  - lib/rley/syntax/grm_symbol.rb
98
99
  - lib/rley/syntax/literal.rb
99
100
  - lib/rley/syntax/non_terminal.rb
@@ -108,8 +109,7 @@ files:
108
109
  - spec/rley/parser/parsing_spec.rb
109
110
  - spec/rley/parser/state_set_spec.rb
110
111
  - spec/rley/parser/token_spec.rb
111
- - spec/rley/support/grammar_abc.rb
112
- - spec/rley/support/grammar_helper.rb
112
+ - spec/rley/syntax/grammar_builder_spec.rb
113
113
  - spec/rley/syntax/grammar_spec.rb
114
114
  - spec/rley/syntax/grm_symbol_spec.rb
115
115
  - spec/rley/syntax/literal_spec.rb
@@ -158,6 +158,7 @@ test_files:
158
158
  - spec/rley/parser/parsing_spec.rb
159
159
  - spec/rley/parser/state_set_spec.rb
160
160
  - spec/rley/parser/token_spec.rb
161
+ - spec/rley/syntax/grammar_builder_spec.rb
161
162
  - spec/rley/syntax/grammar_spec.rb
162
163
  - spec/rley/syntax/grm_symbol_spec.rb
163
164
  - spec/rley/syntax/literal_spec.rb
@@ -1,22 +0,0 @@
1
- require_relative '../../../lib/rley/syntax/verbatim_symbol'
2
- require_relative '../../../lib/rley/syntax/non_terminal'
3
- require_relative '../../../lib/rley/syntax/production'
4
- require_relative '../../../lib/rley/parser/token'
5
-
6
-
7
-
8
- # Grammar 1: A very simple language
9
- # (based on example in N. Wirth "Compiler Construction" book, p. 6)
10
- # S ::= A.
11
- # A ::= "a" A "c".
12
- # A ::= "b".
13
- # Let's create the grammar piece by piece
14
- let(:nt_S) { Syntax::NonTerminal.new('S') }
15
- let(:nt_A) { Syntax::NonTerminal.new('A') }
16
- let(:a_) { Syntax::VerbatimSymbol.new('a') }
17
- let(:b_) { Syntax::VerbatimSymbol.new('b') }
18
- let(:c_) { Syntax::VerbatimSymbol.new('c') }
19
- let(:prod_S) { Syntax::Production.new(nt_S, [nt_A]) }
20
- let(:prod_A1) { Syntax::Production.new(nt_A, [a_, nt_A, c_]) }
21
- let(:prod_A2) { Syntax::Production.new(nt_A, [b_]) }
22
- let(:grammar_abc) { Syntax::Grammar.new([prod_S, prod_A1, prod_A2]) }
@@ -1,16 +0,0 @@
1
- require_relative '../../../lib/rley/syntax/verbatim_symbol'
2
- require_relative '../../../lib/rley/syntax/non_terminal'
3
- require_relative '../../../lib/rley/syntax/production'
4
- require_relative '../../../lib/rley/syntax/grammar'
5
-
6
- module Rley # This module is used as a namespace
7
- # Mix-in module that provides factory methods that simplify the construction
8
- # of a grammar from its constituents.
9
- module GrammarHelper
10
- # Create a production
11
- def production()
12
- end
13
- end # module
14
- end # module
15
-
16
- # End of file