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