rley 0.7.08 → 0.8.03
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 +4 -4
- data/.rubocop.yml +29 -5
- data/CHANGELOG.md +28 -4
- data/README.md +4 -5
- data/examples/NLP/nano_eng/nano_en_demo.rb +7 -11
- data/examples/NLP/nano_eng/nano_grammar.rb +18 -18
- data/examples/data_formats/JSON/json_ast_builder.rb +9 -18
- data/examples/data_formats/JSON/json_demo.rb +1 -2
- data/examples/data_formats/JSON/json_grammar.rb +11 -11
- data/examples/general/calc_iter1/calc_grammar.rb +5 -4
- data/examples/general/calc_iter2/calc_grammar.rb +9 -9
- data/examples/general/left.rb +1 -1
- data/examples/general/right.rb +1 -1
- data/lib/rley/base/dotted_item.rb +5 -0
- data/lib/rley/base/grm_items_builder.rb +6 -0
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/engine.rb +2 -2
- data/lib/rley/interface.rb +16 -0
- data/lib/rley/notation/all_notation_nodes.rb +4 -0
- data/lib/rley/notation/ast_builder.rb +185 -0
- data/lib/rley/notation/ast_node.rb +44 -0
- data/lib/rley/notation/ast_visitor.rb +115 -0
- data/lib/rley/notation/grammar.rb +49 -0
- data/lib/rley/notation/grammar_builder.rb +505 -0
- data/lib/rley/notation/grouping_node.rb +23 -0
- data/lib/rley/notation/parser.rb +56 -0
- data/lib/rley/notation/sequence_node.rb +35 -0
- data/lib/rley/notation/symbol_node.rb +29 -0
- data/lib/rley/notation/tokenizer.rb +180 -0
- data/lib/rley/parse_rep/ast_base_builder.rb +44 -0
- data/lib/rley/parser/gfg_chart.rb +101 -6
- data/lib/rley/parser/gfg_earley_parser.rb +1 -1
- data/lib/rley/parser/gfg_parsing.rb +5 -3
- data/lib/rley/parser/parse_entry_set.rb +1 -1
- data/lib/rley/syntax/{grammar_builder.rb → base_grammar_builder.rb} +53 -15
- data/lib/rley/syntax/grm_symbol.rb +1 -1
- data/lib/rley/syntax/match_closest.rb +43 -0
- data/lib/rley/syntax/production.rb +6 -0
- data/lib/rley.rb +1 -1
- data/spec/rley/engine_spec.rb +6 -6
- data/spec/rley/gfg/grm_flow_graph_spec.rb +2 -2
- data/spec/rley/notation/grammar_builder_spec.rb +302 -0
- data/spec/rley/notation/parser_spec.rb +183 -0
- data/spec/rley/notation/tokenizer_spec.rb +364 -0
- data/spec/rley/parse_rep/ast_builder_spec.rb +0 -1
- data/spec/rley/parse_rep/groucho_spec.rb +1 -1
- data/spec/rley/parse_rep/parse_forest_builder_spec.rb +1 -1
- data/spec/rley/parse_rep/parse_forest_factory_spec.rb +2 -2
- data/spec/rley/parse_rep/parse_tree_factory_spec.rb +1 -1
- data/spec/rley/parser/dangling_else_spec.rb +447 -0
- data/spec/rley/parser/gfg_earley_parser_spec.rb +118 -10
- data/spec/rley/parser/gfg_parsing_spec.rb +2 -1
- data/spec/rley/parser/parse_walker_factory_spec.rb +2 -2
- data/spec/rley/support/ambiguous_grammar_helper.rb +2 -2
- data/spec/rley/support/grammar_abc_helper.rb +2 -2
- data/spec/rley/support/grammar_ambig01_helper.rb +2 -2
- data/spec/rley/support/grammar_arr_int_helper.rb +2 -2
- data/spec/rley/support/grammar_b_expr_helper.rb +2 -2
- data/spec/rley/support/grammar_int_seq_helper.rb +51 -0
- data/spec/rley/support/grammar_l0_helper.rb +2 -2
- data/spec/rley/support/grammar_pb_helper.rb +2 -2
- data/spec/rley/support/grammar_sppf_helper.rb +2 -2
- data/spec/rley/syntax/{grammar_builder_spec.rb → base_grammar_builder_spec.rb} +29 -11
- data/spec/rley/syntax/match_closest_spec.rb +46 -0
- data/spec/rley/syntax/production_spec.rb +4 -0
- metadata +29 -14
- data/lib/rley/parser/parse_state.rb +0 -78
- data/lib/rley/parser/parse_state_tracker.rb +0 -59
- data/lib/rley/parser/state_set.rb +0 -100
- data/spec/rley/parser/parse_state_spec.rb +0 -125
- data/spec/rley/parser/parse_tracer_spec.rb +0 -200
- data/spec/rley/parser/state_set_spec.rb +0 -130
@@ -51,7 +51,7 @@ module Rley # This module is used as a namespace
|
|
51
51
|
result.chart[index].each do |entry|
|
52
52
|
# Is entry of the form? [A => alpha . B beta, k]...
|
53
53
|
next_symbol = entry.next_symbol
|
54
|
-
if next_symbol
|
54
|
+
if next_symbol.kind_of?(Syntax::NonTerminal)
|
55
55
|
# ...apply the Call rule
|
56
56
|
call_rule(result, entry, index)
|
57
57
|
end
|
@@ -280,8 +280,8 @@ module Rley # This module is used as a namespace
|
|
280
280
|
|
281
281
|
def apply_rule(antecedentEntry, aVertex, anOrigin, aPosition, aRuleId)
|
282
282
|
consequent = push_entry(aVertex, anOrigin, aPosition, aRuleId)
|
283
|
-
|
284
|
-
|
283
|
+
if consequent
|
284
|
+
antecedence[consequent] << antecedentEntry
|
285
285
|
|
286
286
|
=begin
|
287
287
|
# Invariant checks
|
@@ -319,7 +319,9 @@ module Rley # This module is used as a namespace
|
|
319
319
|
end
|
320
320
|
end
|
321
321
|
=end
|
322
|
-
|
322
|
+
consequent.add_antecedent(antecedentEntry)
|
323
|
+
end
|
324
|
+
|
323
325
|
consequent
|
324
326
|
end
|
325
327
|
|
@@ -82,7 +82,7 @@ module Rley # This module is used as a namespace
|
|
82
82
|
|
83
83
|
# Group parse entries by lhs symbol and origin
|
84
84
|
groupings = complete_entries.group_by do |entry|
|
85
|
-
entry.vertex.
|
85
|
+
entry.vertex.dotted_item.lhs.object_id.to_s
|
86
86
|
end
|
87
87
|
|
88
88
|
# Retain the groups having more than one element.
|
@@ -13,7 +13,7 @@ module Rley # This module is used as a namespace
|
|
13
13
|
# Builder GoF pattern. Builder builds a complex object
|
14
14
|
# (say, a grammar) from simpler objects (terminals and productions)
|
15
15
|
# and using a step by step approach.
|
16
|
-
class
|
16
|
+
class BaseGrammarBuilder
|
17
17
|
# @return [Hash{String, GrmSymbol}] The mapping of grammar symbol names
|
18
18
|
# to the matching grammar symbol object.
|
19
19
|
attr_reader(:symbols)
|
@@ -56,6 +56,14 @@ module Rley # This module is used as a namespace
|
|
56
56
|
symbols.merge!(new_symbs)
|
57
57
|
end
|
58
58
|
|
59
|
+
# Add the given marker symbol to the grammar of the language
|
60
|
+
# @param aMarkerSymbol [Syntax::Marker] A mazker symbol
|
61
|
+
# @return [void]
|
62
|
+
def add_marker(aMarkerSymbol)
|
63
|
+
new_symb = build_symbol(Marker, aMarkerSymbol)
|
64
|
+
symbols[aMarkerSymbol.name] = new_symb
|
65
|
+
end
|
66
|
+
|
59
67
|
# Add a production rule in the grammar given one
|
60
68
|
# key-value pair of the form: String => Array.
|
61
69
|
# Where the key is the name of the non-terminal appearing in the
|
@@ -72,13 +80,13 @@ module Rley # This module is used as a namespace
|
|
72
80
|
# @return [Production] The created Production instance
|
73
81
|
def add_production(aProductionRepr)
|
74
82
|
aProductionRepr.each_pair do |(lhs_name, rhs_repr)|
|
75
|
-
lhs =
|
83
|
+
lhs = get_grm_symbol(lhs_name)
|
76
84
|
case rhs_repr
|
77
85
|
when Array
|
78
|
-
rhs_members = rhs_repr.map { |name|
|
86
|
+
rhs_members = rhs_repr.map { |name| get_grm_symbol(name) }
|
79
87
|
when String
|
80
88
|
rhs_lexemes = rhs_repr.scan(/\S+/)
|
81
|
-
rhs_members = rhs_lexemes.map { |name|
|
89
|
+
rhs_members = rhs_lexemes.map { |name| get_grm_symbol(name) }
|
82
90
|
when Terminal
|
83
91
|
rhs_members = [rhs_repr]
|
84
92
|
end
|
@@ -122,6 +130,20 @@ module Rley # This module is used as a namespace
|
|
122
130
|
return @grammar
|
123
131
|
end
|
124
132
|
|
133
|
+
# When a symbol, say symb, in a rhs is followed by a '+' modifier,
|
134
|
+
# then a rule will be generated with a lhs named symb + suffix_plus
|
135
|
+
def suffix_plus
|
136
|
+
'_plus'
|
137
|
+
end
|
138
|
+
|
139
|
+
def suffix_plus_more
|
140
|
+
'base_plus_more'
|
141
|
+
end
|
142
|
+
|
143
|
+
def suffix_plus_last
|
144
|
+
'base_plus_last'
|
145
|
+
end
|
146
|
+
|
125
147
|
alias rule add_production
|
126
148
|
|
127
149
|
private
|
@@ -149,24 +171,40 @@ module Rley # This module is used as a namespace
|
|
149
171
|
# @param aSymbolArg [GrmSymbol-like or String]
|
150
172
|
# @return [Array] list of grammar symbols
|
151
173
|
def build_symbol(aClass, aSymbolArg)
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
return a_symbol
|
174
|
+
if aSymbolArg.kind_of?(GrmSymbol)
|
175
|
+
aSymbolArg
|
176
|
+
else
|
177
|
+
aClass.new(aSymbolArg)
|
178
|
+
end
|
159
179
|
end
|
160
180
|
|
161
181
|
# Retrieve the non-terminal symbol with given name.
|
162
182
|
# If it doesn't exist yet, then it is created on the fly.
|
163
183
|
# @param aSymbolName [String] the name of the grammar symbol to retrieve
|
164
184
|
# @return [NonTerminal]
|
165
|
-
def
|
166
|
-
unless
|
167
|
-
|
185
|
+
def get_grm_symbol(aSymbolName)
|
186
|
+
unless aSymbolName.end_with?('+') && aSymbolName.length > 1
|
187
|
+
name = aSymbolName
|
188
|
+
else
|
189
|
+
name = aSymbolName.chop
|
190
|
+
case aSymbolName[-1]
|
191
|
+
when '+'
|
192
|
+
name_modified = "#{name}#{suffix_plus}"
|
193
|
+
unless symbols.include? name_modified
|
194
|
+
symbols[name_modified] = NonTerminal.new(name_modified)
|
195
|
+
rule(name_modified => [name_modified, name]).as suffix_plus_more
|
196
|
+
rule(name_modified => name).as suffix_plus_last
|
197
|
+
end
|
198
|
+
name = name_modified
|
199
|
+
else
|
200
|
+
err_msg = "Unknown symbol modifier #{aSymbolName[-1]}"
|
201
|
+
raise NotImplementedError, err_msg
|
202
|
+
end
|
168
203
|
end
|
169
|
-
|
204
|
+
|
205
|
+
symbols[name] = NonTerminal.new(name) unless symbols.include? name
|
206
|
+
|
207
|
+
symbols[name]
|
170
208
|
end
|
171
209
|
end # class
|
172
210
|
end # module
|
@@ -5,7 +5,7 @@ module Rley # This module is used as a namespace
|
|
5
5
|
# Abstract class for grammar symbols.
|
6
6
|
# A grammar symbol is an element that appears in grammar rules.
|
7
7
|
class GrmSymbol
|
8
|
-
# The name of the grammar symbol
|
8
|
+
# @return [String] The name of the grammar symbol
|
9
9
|
attr_reader(:name)
|
10
10
|
|
11
11
|
# An indicator that tells whether the grammar symbol can generate a
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rley # This module is used as a namespace
|
4
|
+
module Syntax # This module is used as a namespace
|
5
|
+
# A constraint that indicates that a given rhs member must
|
6
|
+
# match the closest given terminal symbol in that rhs
|
7
|
+
class MatchClosest
|
8
|
+
# @return [Integer] index of constrained symbol to match
|
9
|
+
attr_reader(:idx_symbol)
|
10
|
+
|
11
|
+
# @return [String] name of closest preceding symbol to pair
|
12
|
+
attr_reader(:closest_symb)
|
13
|
+
|
14
|
+
# @return [NilClass, Array<Parser::ParseEntry>] set of entries with closest symbol
|
15
|
+
attr_accessor(:entries)
|
16
|
+
|
17
|
+
# @param aSymbolSeq [Rley::Syntax::SymbolSeq] a sequence of grammar symbols
|
18
|
+
# @param idxSymbol [Integer] index of symbol
|
19
|
+
# @param nameClosest [String] Terminal symbol name
|
20
|
+
def initialize(aSymbolSeq, idxSymbol, nameClosest)
|
21
|
+
@idx_symbol = valid_idx_symbol(idxSymbol, aSymbolSeq)
|
22
|
+
@closest_symb = valid_name_closest(nameClosest)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Check that the provided index is within plausible bounds
|
28
|
+
def valid_idx_symbol(idxSymbol, aSymbolSeq)
|
29
|
+
bounds = 0..aSymbolSeq.size - 1
|
30
|
+
err_msg_outbound = 'Index of symbol out of bound'
|
31
|
+
raise StandardError, err_msg_outbound unless bounds.include? idxSymbol
|
32
|
+
|
33
|
+
idxSymbol
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_name_closest(nameClosest)
|
37
|
+
nameClosest
|
38
|
+
end
|
39
|
+
end # class
|
40
|
+
end # module
|
41
|
+
end # module
|
42
|
+
|
43
|
+
# End of file
|
@@ -31,6 +31,9 @@ module Rley # This module is used as a namespace
|
|
31
31
|
# rhs members are nullable.
|
32
32
|
attr_writer(:nullable)
|
33
33
|
|
34
|
+
# @return [Array<Syntax::MatchClosest>] A list of constraints between rhs members
|
35
|
+
attr_accessor(:constraints)
|
36
|
+
|
34
37
|
# Provide common alternate names to lhs and rhs accessors
|
35
38
|
|
36
39
|
alias body rhs
|
@@ -42,6 +45,7 @@ module Rley # This module is used as a namespace
|
|
42
45
|
def initialize(aNonTerminal, theSymbols)
|
43
46
|
@lhs = valid_lhs(aNonTerminal)
|
44
47
|
@rhs = valid_rhs(theSymbols)
|
48
|
+
@constraints = []
|
45
49
|
end
|
46
50
|
|
47
51
|
# Is the rhs empty?
|
@@ -81,6 +85,8 @@ module Rley # This module is used as a namespace
|
|
81
85
|
@name = aName
|
82
86
|
end
|
83
87
|
|
88
|
+
alias tag as
|
89
|
+
|
84
90
|
private
|
85
91
|
|
86
92
|
# Validation method. Return the validated input argument or
|
data/lib/rley.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# for a Rley client.
|
6
6
|
|
7
7
|
require_relative './rley/constants'
|
8
|
-
require_relative './rley/
|
8
|
+
require_relative './rley/interface'
|
9
9
|
require_relative './rley/lexical/token'
|
10
10
|
require_relative './rley/parser/gfg_earley_parser'
|
11
11
|
require_relative './rley/parse_rep/ast_base_builder'
|
data/spec/rley/engine_spec.rb
CHANGED
@@ -35,9 +35,9 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
35
35
|
it 'should build grammar' do
|
36
36
|
subject.build_grammar do
|
37
37
|
add_terminals('a', 'b', 'c')
|
38
|
-
add_production('S' =>
|
39
|
-
add_production('A' =>
|
40
|
-
add_production('A' =>
|
38
|
+
add_production('S' => 'A')
|
39
|
+
add_production('A' => 'a A c')
|
40
|
+
add_production('A' => 'b')
|
41
41
|
end
|
42
42
|
|
43
43
|
expect(subject.grammar).to be_kind_of(Rley::Syntax::Grammar)
|
@@ -71,9 +71,9 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
71
71
|
def add_sample_grammar(anEngine)
|
72
72
|
anEngine.build_grammar do
|
73
73
|
add_terminals('a', 'b', 'c')
|
74
|
-
add_production('S' =>
|
75
|
-
add_production('A' =>
|
76
|
-
add_production('A' =>
|
74
|
+
add_production('S' => 'A')
|
75
|
+
add_production('A' => 'a A c')
|
76
|
+
add_production('A' => 'b')
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
@@ -112,7 +112,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
112
112
|
end
|
113
113
|
|
114
114
|
it 'should handle empty productions' do
|
115
|
-
builder = Rley::Syntax::
|
115
|
+
builder = Rley::Syntax::BaseGrammarBuilder.new
|
116
116
|
builder.add_terminals('a')
|
117
117
|
builder.add_production('S' => 'A')
|
118
118
|
builder.add_production('A' => 'a')
|
@@ -162,7 +162,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
162
162
|
let(:problematic_grammar) do
|
163
163
|
# Based on grammar example in book
|
164
164
|
# C. Fisher, R. LeBlanc, "Crafting a Compiler"; page 98
|
165
|
-
builder = Rley::Syntax::
|
165
|
+
builder = Rley::Syntax::BaseGrammarBuilder.new
|
166
166
|
builder.add_terminals('a', 'b', 'c')
|
167
167
|
builder.add_production('S' => 'A')
|
168
168
|
builder.add_production('S' => 'B')
|
@@ -0,0 +1,302 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../spec_helper'
|
4
|
+
|
5
|
+
# Load the class under test
|
6
|
+
require_relative '../../../lib/rley/notation/grammar_builder'
|
7
|
+
|
8
|
+
module Rley # Open this namespace to avoid module qualifier prefixes
|
9
|
+
module Notation # Open this namespace to avoid module qualifier prefixes
|
10
|
+
describe GrammarBuilder do
|
11
|
+
context 'Initialization without argument:' do
|
12
|
+
it 'could be created without argument' do
|
13
|
+
expect { GrammarBuilder.new }.not_to raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should have no grammar symbols at start' do
|
17
|
+
expect(subject.symbols).to be_empty
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should have no productions at start' do
|
21
|
+
expect(subject.productions).to be_empty
|
22
|
+
end
|
23
|
+
end # context
|
24
|
+
|
25
|
+
context 'Initialization with argument:' do
|
26
|
+
it 'could be created with a block argument' do
|
27
|
+
expect do
|
28
|
+
GrammarBuilder.new { nil }
|
29
|
+
end.not_to raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'could have grammar symbols from block argument' do
|
33
|
+
instance = GrammarBuilder.new do
|
34
|
+
add_terminals('a', 'b', 'c')
|
35
|
+
end
|
36
|
+
expect(instance.symbols.size).to eq(3)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should have no productions at start' do
|
40
|
+
expect(subject.productions).to be_empty
|
41
|
+
end
|
42
|
+
end # context
|
43
|
+
|
44
|
+
context 'Adding symbols:' do
|
45
|
+
it 'should build terminals from their names' do
|
46
|
+
subject.add_terminals('a', 'b', 'c')
|
47
|
+
expect(subject.symbols.size).to eq(3)
|
48
|
+
expect(subject.symbols['a']).to be_kind_of(Syntax::Terminal)
|
49
|
+
expect(subject.symbols['a'].name).to eq('a')
|
50
|
+
expect(subject.symbols['b']).to be_kind_of(Syntax::Terminal)
|
51
|
+
expect(subject.symbols['b'].name).to eq('b')
|
52
|
+
expect(subject.symbols['c']).to be_kind_of(Syntax::Terminal)
|
53
|
+
expect(subject.symbols['c'].name).to eq('c')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should accept already built terminals' do
|
57
|
+
a = Syntax::Terminal.new('a')
|
58
|
+
b = Syntax::VerbatimSymbol.new('b')
|
59
|
+
c = Syntax::Literal.new('c', /c/)
|
60
|
+
|
61
|
+
subject.add_terminals(a, b, c)
|
62
|
+
expect(subject.symbols.size).to eq(3)
|
63
|
+
expect(subject.symbols['a']).to eq(a)
|
64
|
+
expect(subject.symbols['b']).to eq(b)
|
65
|
+
expect(subject.symbols['c']).to eq(c)
|
66
|
+
end
|
67
|
+
end # context
|
68
|
+
|
69
|
+
context 'Adding productions:' do
|
70
|
+
subject do
|
71
|
+
instance = GrammarBuilder.new
|
72
|
+
instance.add_terminals('a', 'b', 'c')
|
73
|
+
instance
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should add a valid production' do
|
77
|
+
# Case of a rhs representation that consists of one name only
|
78
|
+
expect { subject.add_production('S' => 'A') }.not_to raise_error
|
79
|
+
expect(subject.productions.size).to eq(1)
|
80
|
+
new_prod = subject.productions[0]
|
81
|
+
expect(new_prod.lhs).to eq(subject['S'])
|
82
|
+
expect(new_prod.rhs[0]).not_to be_nil
|
83
|
+
expect(new_prod.rhs[0]).to eq(subject['A'])
|
84
|
+
|
85
|
+
# Case of rhs with multiple symbols
|
86
|
+
subject.add_production('A' => 'a A c')
|
87
|
+
expect(subject.productions.size).to eq(2)
|
88
|
+
new_prod = subject.productions.last
|
89
|
+
expect(new_prod.lhs).to eq(subject['A'])
|
90
|
+
expect_rhs = [subject['a'], subject['A'], subject['c']]
|
91
|
+
expect(new_prod.rhs.members).to eq(expect_rhs)
|
92
|
+
|
93
|
+
# GrammarBuilder#rule is an alias of add_production
|
94
|
+
subject.rule('A' => 'b')
|
95
|
+
expect(subject.productions.size).to eq(3)
|
96
|
+
new_prod = subject.productions.last
|
97
|
+
expect(new_prod.lhs).to eq(subject['A'])
|
98
|
+
expect(new_prod.rhs[0]).to eq(subject['b'])
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should accept annotated terminals' do
|
102
|
+
subject.rule('A' => "a b {match_closest: 'IF' } c")
|
103
|
+
expect(subject.productions.size).to eq(1)
|
104
|
+
new_prod = subject.productions.last
|
105
|
+
expect(new_prod.lhs).to eq(subject['A'])
|
106
|
+
expect(new_prod.rhs[0].name).to eq('a')
|
107
|
+
expect(new_prod.rhs[0]).to eq(subject['a'])
|
108
|
+
expect(new_prod.rhs[1].name).to eq('b')
|
109
|
+
expect(new_prod.rhs[2].name).to eq('c')
|
110
|
+
expect(new_prod.constraints.size).to eq(1)
|
111
|
+
expect(new_prod.constraints[0]).to be_kind_of(Syntax::MatchClosest)
|
112
|
+
expect(new_prod.constraints[0].idx_symbol).to eq(1) # b is on position 1
|
113
|
+
expect(new_prod.constraints[0].closest_symb).to eq('IF')
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should support optional symbol' do
|
117
|
+
instance = GrammarBuilder.new
|
118
|
+
instance.add_terminals('LPAREN', 'RPAREN')
|
119
|
+
|
120
|
+
instance.rule 'argument_list' => 'LPAREN arguments? RPAREN'
|
121
|
+
instance.grammar_complete!
|
122
|
+
|
123
|
+
# implicitly called: rule('arguments_qmark' => 'arguments').tag suffix_qmark_one
|
124
|
+
# implicitly called: rule('arguments_qmark' => '').tag suffix_qmark_none
|
125
|
+
expect(instance.productions.size).to eq(3)
|
126
|
+
prod_star = instance.productions.select { |prod| prod.lhs.name == 'arguments_qmark' }
|
127
|
+
expect(prod_star.size).to eq(2)
|
128
|
+
first_prod = instance.productions.first
|
129
|
+
expect(first_prod.lhs.name).to eq('argument_list')
|
130
|
+
expect(first_prod.rhs.members[1].name).to eq('arguments_qmark')
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should support Kleene's star" do
|
134
|
+
instance = GrammarBuilder.new
|
135
|
+
instance.add_terminals('EOF')
|
136
|
+
|
137
|
+
instance.rule 'program' => 'declaration* EOF'
|
138
|
+
instance.grammar_complete!
|
139
|
+
|
140
|
+
# implicitly called: rule('declaration_star' => 'declaration_star declaration').tag suffix_star_more
|
141
|
+
# implicitly called: rule('declaration_star' => '').tag suffix_star_last
|
142
|
+
expect(instance.productions.size).to eq(3)
|
143
|
+
prod_star = instance.productions.select { |prod| prod.lhs.name == 'declaration_star' }
|
144
|
+
expect(prod_star.size).to eq(2)
|
145
|
+
first_prod = instance.productions.first
|
146
|
+
expect(first_prod.lhs.name).to eq('program')
|
147
|
+
expect(first_prod.rhs.members[0].name).to eq('declaration_star')
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should support symbols decorated with Kleene's plus" do
|
151
|
+
instance = GrammarBuilder.new
|
152
|
+
instance.add_terminals('plus', 'minus', 'digit')
|
153
|
+
|
154
|
+
instance.rule 'integer' => 'value'
|
155
|
+
instance.rule 'integer' => 'sign value'
|
156
|
+
instance.rule 'sign' => 'plus'
|
157
|
+
instance.rule 'sign' => 'minus'
|
158
|
+
instance.rule 'value' => 'digit+'
|
159
|
+
expect(instance.productions.size).to eq(5)
|
160
|
+
instance.grammar_complete!
|
161
|
+
expect(instance.productions.size).to eq(7)
|
162
|
+
|
163
|
+
# implicitly called: rule('digit_plus' => 'digit_plus digit').tag suffix_plus_more
|
164
|
+
# implicitly called: rule('digit_plus' => 'digit').tag suffix_plus_last
|
165
|
+
expect(instance.productions.size).to eq(7) # Two additional rules generated
|
166
|
+
prod_plus = instance.productions.select { |prod| prod.lhs.name == 'digit_plus' }
|
167
|
+
expect(prod_plus.size).to eq(2)
|
168
|
+
val_prod = instance.productions[4]
|
169
|
+
expect(val_prod.lhs.name).to eq('value')
|
170
|
+
expect(val_prod.rhs.members[0].name).to eq('digit_plus')
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should support optional grouping' do
|
174
|
+
instance = GrammarBuilder.new
|
175
|
+
instance.add_terminals('EQUAL', 'IDENTIFIER', 'VAR')
|
176
|
+
|
177
|
+
instance.rule 'var_decl' => 'VAR IDENTIFIER (EQUAL expression)?'
|
178
|
+
instance.grammar_complete!
|
179
|
+
|
180
|
+
# implicitly called: rule('seq_EQUAL_expression' => 'EQUAL expression').tag 'return_children'
|
181
|
+
# implicitly called: rule('seq_EQUAL_expression_qmark' => 'seq_EQUAL_expression').tag suffix_qmark_one
|
182
|
+
# implicitly called: rule('seq_EQUAL_expression_qmark' => '').tag suffix_qmark_none
|
183
|
+
expect(instance.productions.size).to eq(4)
|
184
|
+
first_prod = instance.productions.first
|
185
|
+
expect(first_prod.lhs.name).to eq('var_decl')
|
186
|
+
expect(first_prod.rhs.members[2].name).to eq('seq_EQUAL_expression_qmark')
|
187
|
+
(p0, p1, p2) = instance.productions[1..3]
|
188
|
+
expect(p0.lhs.name).to eq('seq_EQUAL_expression')
|
189
|
+
expect(p0.rhs[0].name).to eq('EQUAL')
|
190
|
+
expect(p0.rhs[1].name).to eq('expression')
|
191
|
+
expect(p0.name).to eq('return_children')
|
192
|
+
|
193
|
+
expect(p1.lhs.name).to eq('seq_EQUAL_expression_qmark')
|
194
|
+
expect(p1.rhs[0].name).to eq('seq_EQUAL_expression')
|
195
|
+
expect(p1.name).to eq('_qmark_one')
|
196
|
+
|
197
|
+
expect(p2.lhs.name).to eq('seq_EQUAL_expression_qmark')
|
198
|
+
expect(p2.rhs).to be_empty
|
199
|
+
expect(p2.name).to eq('_qmark_none')
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'should support grouping with star modifier' do
|
203
|
+
instance = GrammarBuilder.new
|
204
|
+
instance.add_terminals('OR')
|
205
|
+
|
206
|
+
instance.rule 'logic_or' => 'logic_and (OR logic_and)*'
|
207
|
+
instance.grammar_complete!
|
208
|
+
|
209
|
+
# implicitly called: rule('seq_OR_logic_and' => 'OR logic_and').tag 'return_children'
|
210
|
+
# implicitly called: rule('seq_EQUAL_expression_star' => 'seq_EQUAL_expression_star seq_EQUAL_expression').tag suffix_star_more
|
211
|
+
# implicitly called: rule('seq_EQUAL_expression_star' => '').tag suffix_star_none
|
212
|
+
expect(instance.productions.size).to eq(4)
|
213
|
+
first_prod = instance.productions.first
|
214
|
+
expect(first_prod.lhs.name).to eq('logic_or')
|
215
|
+
expect(first_prod.rhs.members[1].name).to eq('seq_OR_logic_and_star')
|
216
|
+
|
217
|
+
(p0, p1, p2) = instance.productions[1..3]
|
218
|
+
expect(p0.lhs.name).to eq('seq_OR_logic_and')
|
219
|
+
expect(p0.rhs[0].name).to eq('OR')
|
220
|
+
expect(p0.rhs[1].name).to eq('logic_and')
|
221
|
+
expect(p0.name).to eq('return_children')
|
222
|
+
|
223
|
+
expect(p1.lhs.name).to eq('seq_OR_logic_and_star')
|
224
|
+
expect(p1.rhs[0].name).to eq('seq_OR_logic_and_star')
|
225
|
+
expect(p1.rhs[1].name).to eq('seq_OR_logic_and')
|
226
|
+
expect(p1.name).to eq('_star_more')
|
227
|
+
|
228
|
+
expect(p2.lhs.name).to eq('seq_OR_logic_and_star')
|
229
|
+
expect(p2.rhs).to be_empty
|
230
|
+
expect(p2.name).to eq('_star_none')
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'should support grouping with plus modifier' do
|
234
|
+
instance = GrammarBuilder.new
|
235
|
+
instance.add_terminals('POINT TO SEMI_COLON')
|
236
|
+
|
237
|
+
instance.rule 'path' => 'POINT (TO POINT)+ SEMI_COLON'
|
238
|
+
instance.grammar_complete!
|
239
|
+
|
240
|
+
# implicitly called: rule('seq_TO_POINT' => 'TO POINT').tag 'return_children'
|
241
|
+
# implicitly called: rule('seq_TO_POINT_plus' => 'seq_TO_POINT_plus seq_TO_POINT').tag suffix_plus_more
|
242
|
+
# implicitly called: rule('seq_TO_POINT_plus' => 'seq_TO_POINT').tag suffix_plus_one
|
243
|
+
expect(instance.productions.size).to eq(4)
|
244
|
+
first_prod = instance.productions.first
|
245
|
+
expect(first_prod.lhs.name).to eq('path')
|
246
|
+
expect(first_prod.rhs.members[1].name).to eq('seq_TO_POINT_plus')
|
247
|
+
|
248
|
+
(p0, p1, p2) = instance.productions[1..3]
|
249
|
+
expect(p0.lhs.name).to eq('seq_TO_POINT')
|
250
|
+
expect(p0.rhs[0].name).to eq('TO')
|
251
|
+
expect(p0.rhs[1].name).to eq('POINT')
|
252
|
+
expect(p0.name).to eq('return_children')
|
253
|
+
|
254
|
+
expect(p1.lhs.name).to eq('seq_TO_POINT_plus')
|
255
|
+
expect(p1.rhs[0].name).to eq('seq_TO_POINT_plus')
|
256
|
+
expect(p1.rhs[1].name).to eq('seq_TO_POINT')
|
257
|
+
expect(p1.name).to eq('_plus_more')
|
258
|
+
|
259
|
+
expect(p2.lhs.name).to eq('seq_TO_POINT_plus')
|
260
|
+
expect(p2.rhs[0].name).to eq('seq_TO_POINT')
|
261
|
+
expect(p2.name).to eq('_plus_one')
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'should support grouping with nested annotation' do
|
265
|
+
instance = GrammarBuilder.new
|
266
|
+
instance.add_terminals('IF ELSE LPAREN RPAREN')
|
267
|
+
st = "IF LPAREN expr RPAREN stmt (ELSE { match_closest: 'IF' } stmt)?"
|
268
|
+
instance.rule('if_stmt' => st)
|
269
|
+
instance.grammar_complete!
|
270
|
+
|
271
|
+
# implicitly called: rule('seq_ELSE_stmt' => 'ELSE stmt').tag 'return_children'
|
272
|
+
# implicitly called: rule('seq_ELSE_stmt_qmark' => 'seq_ELSE_stmt ').tag suffix_plus_more
|
273
|
+
# implicitly called: rule('seq_ELSE_stmt_qmark' => '').tag suffix_plus_one
|
274
|
+
expect(instance.productions.size).to eq(4)
|
275
|
+
first_prod = instance.productions.first
|
276
|
+
expect(first_prod.lhs.name).to eq('if_stmt')
|
277
|
+
expect(first_prod.rhs.members[5].name).to eq('seq_ELSE_stmt_qmark')
|
278
|
+
|
279
|
+
(p0, p1, p2) = instance.productions[1..3]
|
280
|
+
expect(p0.lhs.name).to eq('seq_ELSE_stmt')
|
281
|
+
expect(p0.rhs[0].name).to eq('ELSE')
|
282
|
+
expect(p0.rhs[1].name).to eq('stmt')
|
283
|
+
expect(p0.name).to eq('return_children')
|
284
|
+
expect(p0.constraints.size).to eq(1)
|
285
|
+
expect(p0.constraints[0]).to be_kind_of(Syntax::MatchClosest)
|
286
|
+
expect(p0.constraints[0].idx_symbol).to eq(0) # ELSE is on position 0
|
287
|
+
expect(p0.constraints[0].closest_symb).to eq('IF')
|
288
|
+
|
289
|
+
expect(p1.lhs.name).to eq('seq_ELSE_stmt_qmark')
|
290
|
+
expect(p1.rhs[0].name).to eq('seq_ELSE_stmt')
|
291
|
+
expect(p1.name).to eq('_qmark_one')
|
292
|
+
|
293
|
+
expect(p2.lhs.name).to eq('seq_ELSE_stmt_qmark')
|
294
|
+
expect(p2.rhs).to be_empty
|
295
|
+
expect(p2.name).to eq('_qmark_none')
|
296
|
+
end
|
297
|
+
end # context
|
298
|
+
end # describe
|
299
|
+
end # module
|
300
|
+
end # module
|
301
|
+
|
302
|
+
# End of file
|