rley 0.0.08 → 0.0.09

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
- 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