rley 0.4.01 → 0.4.02
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/CHANGELOG.md +4 -0
- data/LICENSE.txt +2 -2
- data/README.md +3 -3
- data/examples/NLP/mini_en_demo.rb +1 -1
- data/examples/data_formats/JSON/JSON_demo.rb +1 -0
- data/examples/data_formats/JSON/JSON_lexer.rb +4 -4
- data/examples/general/calc/calc_lexer.rb +2 -2
- data/lib/rley.rb +1 -1
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/formatter/debug.rb +2 -2
- data/lib/rley/formatter/json.rb +4 -4
- data/lib/rley/parse_tree_visitor.rb +9 -9
- data/lib/rley/parser/base_parser.rb +1 -1
- data/lib/rley/parser/gfg_parsing.rb +9 -0
- data/lib/rley/parser/parse_tree_builder.rb +176 -126
- data/lib/rley/parser/parse_tree_factory.rb +57 -0
- data/lib/rley/ptree/non_terminal_node.rb +10 -9
- data/lib/rley/ptree/parse_tree_node.rb +10 -5
- data/lib/rley/ptree/terminal_node.rb +14 -6
- data/lib/rley/sppf/sppf_node.rb +2 -2
- data/lib/rley/{parser → tokens}/token.rb +1 -4
- data/lib/rley/{ptree → tokens}/token_range.rb +1 -1
- data/spec/rley/formatter/debug_spec.rb +16 -16
- data/spec/rley/formatter/json_spec.rb +8 -8
- data/spec/rley/parse_forest_visitor_spec.rb +1 -1
- data/spec/rley/parse_tree_visitor_spec.rb +28 -28
- data/spec/rley/parser/error_reason_spec.rb +3 -3
- data/spec/rley/parser/gfg_chart_spec.rb +2 -2
- data/spec/rley/parser/gfg_earley_parser_spec.rb +2 -2
- data/spec/rley/parser/gfg_parsing_spec.rb +2 -2
- data/spec/rley/parser/groucho_spec.rb +1 -1
- data/spec/rley/parser/parse_tracer_spec.rb +2 -2
- data/spec/rley/parser/parse_tree_builder_spec.rb +213 -140
- data/spec/rley/parser/parse_tree_factory_spec.rb +85 -0
- data/spec/rley/parser/parse_walker_factory_spec.rb +11 -10
- data/spec/rley/ptree/non_terminal_node_spec.rb +23 -20
- data/spec/rley/ptree/terminal_node_spec.rb +7 -12
- data/spec/rley/sppf/alternative_node_spec.rb +2 -2
- data/spec/rley/sppf/non_terminal_node_spec.rb +2 -2
- data/spec/rley/support/ambiguous_grammar_helper.rb +2 -2
- data/spec/rley/support/expectation_helper.rb +1 -1
- data/spec/rley/support/grammar_ambig01_helper.rb +2 -2
- data/spec/rley/support/grammar_b_expr_helper.rb +2 -2
- data/spec/rley/support/grammar_helper.rb +3 -3
- data/spec/rley/support/grammar_l0_helper.rb +2 -2
- data/spec/rley/support/grammar_pb_helper.rb +2 -2
- data/spec/rley/{ptree → tokens}/token_range_spec.rb +2 -2
- data/spec/rley/{parser → tokens}/token_spec.rb +2 -2
- metadata +11 -17
- data/lib/rley/parser/chart.rb +0 -82
- data/lib/rley/parser/earley_parser.rb +0 -203
- data/lib/rley/parser/parsing.rb +0 -265
- data/spec/rley/parser/chart_spec.rb +0 -120
- data/spec/rley/parser/earley_parser_spec.rb +0 -710
- data/spec/rley/parser/parsing_spec.rb +0 -408
data/lib/rley/parser/chart.rb
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
require_relative 'state_set'
|
2
|
-
require_relative 'parse_state'
|
3
|
-
|
4
|
-
module Rley # This module is used as a namespace
|
5
|
-
module Parser # This module is used as a namespace
|
6
|
-
# Also called a parse table
|
7
|
-
# A one-dimensional array with n + 1 entries (n = number of input tokens).
|
8
|
-
class Chart
|
9
|
-
# An array of state sets (one per input token + 1)
|
10
|
-
attr_reader(:state_sets)
|
11
|
-
|
12
|
-
# The level of trace details reported on stdout during the parse.
|
13
|
-
# The possible values are:
|
14
|
-
# 0: No trace output (default case)
|
15
|
-
# 1: Show trace of scanning and completion rules
|
16
|
-
# 2: Same as of 1 with the addition of the prediction rules
|
17
|
-
attr_reader(:tracer)
|
18
|
-
|
19
|
-
# @param startItems [Array] A non-empty Array of dotted items for
|
20
|
-
# the start symbol.
|
21
|
-
# @param tokenCount [Fixnum] The number of lexemes in the input to parse.
|
22
|
-
# @param aTracer [ParseTracer] A tracer object.
|
23
|
-
def initialize(startItems, tokenCount, aTracer)
|
24
|
-
@tracer = aTracer
|
25
|
-
@state_sets = Array.new(tokenCount + 1) { |_| StateSet.new }
|
26
|
-
startItems.each do |startDottedItem|
|
27
|
-
push_state(startDottedItem, 0, 0, :start_rule)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# The dotted item/rule used to seed the parse chart.
|
32
|
-
# It corresponds to the start production and a dot placed
|
33
|
-
# at the beginning of the rhs
|
34
|
-
def start_dotted_rule()
|
35
|
-
return self[0].states.first.dotted_rule
|
36
|
-
end
|
37
|
-
|
38
|
-
# Return the start (non-terminal) symbol of the grammar.
|
39
|
-
def start_symbol()
|
40
|
-
return state_sets.first.states[0].dotted_rule.lhs
|
41
|
-
end
|
42
|
-
|
43
|
-
# Access the state set at given position
|
44
|
-
def [](index)
|
45
|
-
return state_sets[index]
|
46
|
-
end
|
47
|
-
|
48
|
-
# Return the index value of the last non-empty state set.
|
49
|
-
def last_index()
|
50
|
-
first_empty = state_sets.find_index(&:empty?)
|
51
|
-
index = if first_empty.nil?
|
52
|
-
state_sets.size - 1
|
53
|
-
else
|
54
|
-
first_empty.zero? ? 0 : first_empty - 1
|
55
|
-
end
|
56
|
-
|
57
|
-
return index
|
58
|
-
end
|
59
|
-
|
60
|
-
# Push a parse state for the chart entry with given index
|
61
|
-
def push_state(aDottedItem, anOrigin, anIndex, aReason)
|
62
|
-
new_state = ParseState.new(aDottedItem, anOrigin)
|
63
|
-
pushed = self[anIndex].push_state(new_state)
|
64
|
-
return unless pushed && tracer.level > 0
|
65
|
-
case aReason
|
66
|
-
when :start_rule, :prediction
|
67
|
-
tracer.trace_prediction(anIndex, new_state)
|
68
|
-
|
69
|
-
when :scanning
|
70
|
-
tracer.trace_scanning(anIndex, new_state)
|
71
|
-
|
72
|
-
when :completion
|
73
|
-
tracer.trace_completion(anIndex, new_state)
|
74
|
-
else
|
75
|
-
raise NotImplementedError, "Unknown push_state mode #{aReason}"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end # class
|
79
|
-
end # module
|
80
|
-
end # module
|
81
|
-
|
82
|
-
# End of file
|
@@ -1,203 +0,0 @@
|
|
1
|
-
require_relative 'base_parser'
|
2
|
-
|
3
|
-
module Rley # This module is used as a namespace
|
4
|
-
module Parser # This module is used as a namespace
|
5
|
-
# Implementation of a parser that uses the Earley parsing algorithm.
|
6
|
-
class EarleyParser < BaseParser
|
7
|
-
# A Hash that defines the mapping: non-terminal => [start dotted items]
|
8
|
-
attr_reader(:start_mapping)
|
9
|
-
|
10
|
-
# A Hash that defines the mapping: dotted item => next dotted item
|
11
|
-
# In other words, the 'next_mapping' allows to find the dotted item
|
12
|
-
# after "advancing" the dot
|
13
|
-
attr_reader(:next_mapping)
|
14
|
-
|
15
|
-
def initialize(aGrammar)
|
16
|
-
super(aGrammar)
|
17
|
-
@start_mapping = build_start_mapping(dotted_items)
|
18
|
-
@next_mapping = build_next_mapping(dotted_items)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Parse a sequence of input tokens.
|
22
|
-
# @param aTokenSequence [Array] Array of Tokens objects returned by a
|
23
|
-
# tokenizer/scanner/lexer.
|
24
|
-
# @param aTraceLevel [Fixnum] The specified trace level.
|
25
|
-
# The possible values are:
|
26
|
-
# 0: No trace output (default case)
|
27
|
-
# 1: Show trace of scanning and completion rules
|
28
|
-
# 2: Same as of 1 with the addition of the prediction rules
|
29
|
-
# @return [Parsing] an object that embeds the parse results.
|
30
|
-
def parse(aTokenSequence, aTraceLevel = 0)
|
31
|
-
tracer = ParseTracer.new(aTraceLevel, $stdout, aTokenSequence)
|
32
|
-
result = Parsing.new(start_dotted_items, aTokenSequence, tracer)
|
33
|
-
last_token_index = aTokenSequence.size
|
34
|
-
(0..last_token_index).each do |i|
|
35
|
-
handle_error(result) if result.chart[i].empty?
|
36
|
-
predicted = Set.new
|
37
|
-
result.chart[i].each do |state|
|
38
|
-
if state.complete? # End of production reached?
|
39
|
-
completion(result, state, i, tracer)
|
40
|
-
else
|
41
|
-
next_symbol = state.next_symbol
|
42
|
-
if next_symbol.kind_of?(Syntax::NonTerminal)
|
43
|
-
unless predicted.include? next_symbol
|
44
|
-
prediction(result, state, next_symbol, i, tracer)
|
45
|
-
predicted << next_symbol # Avoid repeated predictions
|
46
|
-
end
|
47
|
-
elsif i < last_token_index
|
48
|
-
# Expecting a terminal symbol
|
49
|
-
scanning(result, next_symbol, i, tracer)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
return result
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
# Create a Hash with pairs of the kind:
|
61
|
-
# non-terminal => [start dotted items]
|
62
|
-
def build_start_mapping(theDottedItems)
|
63
|
-
mapping = {}
|
64
|
-
theDottedItems.each do |item|
|
65
|
-
next unless item.at_start?
|
66
|
-
|
67
|
-
lhs_symbol = item.lhs
|
68
|
-
map_entry = mapping.fetch(lhs_symbol, [])
|
69
|
-
map_entry << item
|
70
|
-
mapping[lhs_symbol] = map_entry
|
71
|
-
end
|
72
|
-
|
73
|
-
return mapping
|
74
|
-
end
|
75
|
-
|
76
|
-
# Create a Hash with pairs of the kind:
|
77
|
-
# dotted item => next dotted item
|
78
|
-
# next dotted item uses same production and the dot
|
79
|
-
# position is advanced by one symbol
|
80
|
-
def build_next_mapping(theDottedItems)
|
81
|
-
mapping = {}
|
82
|
-
theDottedItems.each_cons(2) do |(item1, item2)|
|
83
|
-
next if item1.production != item2.production
|
84
|
-
mapping[item1] = item2
|
85
|
-
end
|
86
|
-
|
87
|
-
return mapping
|
88
|
-
end
|
89
|
-
|
90
|
-
# The dotted item for the start production and
|
91
|
-
# with the dot at the beginning of the rhs
|
92
|
-
def start_dotted_items()
|
93
|
-
start_symbol = grammar.start_symbol
|
94
|
-
start_items = dotted_items.select do |anItem|
|
95
|
-
(anItem.lhs == start_symbol) && anItem.at_start?
|
96
|
-
end
|
97
|
-
|
98
|
-
return start_items
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
# This method is called when a parse state for chart entry at position
|
103
|
-
# 'pos' expects as next symbol a non-terminal.
|
104
|
-
# Given a predicted non-terminal 'nt' and a current token position
|
105
|
-
# 'pos':
|
106
|
-
# For each production with 'nt' as lhs, retrieve their corresponding
|
107
|
-
# initial dotted rules nt -> . xxxx
|
108
|
-
# For retrieved dotted rule, add a parse state to the chart entry
|
109
|
-
# at 'pos': <initial dotted rule, pos, pos>
|
110
|
-
# In short, one adds states to chart[pos], one per production that
|
111
|
-
# specifies how to reduce some input into the predicted nt (non-terminal)
|
112
|
-
# A prediction corresponds to a potential expansion of a nonterminal
|
113
|
-
# in a left-most derivation.
|
114
|
-
# @param aParsing [Parsing] the object that encapsulates the results
|
115
|
-
# result of the parsing process
|
116
|
-
# @param aState [ParseState] current parse state being processed
|
117
|
-
# @param aNonTerminal [NonTerminal] a non-terminal symbol that
|
118
|
-
# immediately follows a dot
|
119
|
-
# (= is expected/predicted by the production rule)
|
120
|
-
# @param aPosition [Fixnum] position in the input token sequence.
|
121
|
-
def prediction(aParsing, aState, aNonTerminal, aPosition, aTracer)
|
122
|
-
if aTracer.level > 1
|
123
|
-
puts "Chart[#{aPosition}] Prediction(s) from #{aState}:"
|
124
|
-
end
|
125
|
-
# Retrieve all start dotted items for productions
|
126
|
-
# with aNonTerminal as its lhs
|
127
|
-
items = start_mapping[aNonTerminal]
|
128
|
-
items.each do |an_item|
|
129
|
-
aParsing.push_state(an_item, aPosition, aPosition, :prediction)
|
130
|
-
end
|
131
|
-
|
132
|
-
return unless aNonTerminal.nullable?
|
133
|
-
# Ayock-Horspool trick for nullable rules
|
134
|
-
next_item = next_mapping[aState.dotted_rule]
|
135
|
-
aParsing.push_state(next_item, aState.origin, aPosition, :prediction)
|
136
|
-
end
|
137
|
-
|
138
|
-
# This method is called when a parse state for chart entry at position
|
139
|
-
# 'pos' expects a terminal as next symbol.
|
140
|
-
# If the input token matches the terminal symbol then:
|
141
|
-
# Retrieve all parse states for chart entry at 'aPosition'
|
142
|
-
# that have the given terminal as next symbol.
|
143
|
-
# For each s of the above states, push to chart entry aPosition + 1
|
144
|
-
# a new state like: <next dotted rule, s.origin, aPosition + 1>
|
145
|
-
# In other words, we place the dotted rules in the next state set
|
146
|
-
# such that the dot appears after terminal.
|
147
|
-
# @param aParsing [Parsing] the object that encapsulates the results
|
148
|
-
# result of the parsing process
|
149
|
-
# @param aTerminal [Terminal] a terminal symbol that
|
150
|
-
# immediately follows a dot
|
151
|
-
# @param aPosition [Fixnum] position in the input token sequence.
|
152
|
-
def scanning(aParsing, aTerminal, aPosition, aTracer)
|
153
|
-
if aTracer.level > 1
|
154
|
-
prefix = "Chart[#{aPosition}] Scanning of terminal "
|
155
|
-
suffix = "#{aTerminal.name}:"
|
156
|
-
puts prefix + suffix
|
157
|
-
end
|
158
|
-
aParsing.scanning(aTerminal, aPosition) do |item|
|
159
|
-
next_mapping[item]
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# This method is called when a parse state at chart entry reaches
|
164
|
-
# the end of a production.
|
165
|
-
# For every state in chart[aPosition] that is
|
166
|
-
# complete (i.e. of the form: { dotted_rule: X -> γ •, origin: j}),
|
167
|
-
# Find states s in chart[j] of the
|
168
|
-
# form { dotted_rule: Y -> α • X β, origin: i}
|
169
|
-
# In other words, rules that predicted the non-terminal X.
|
170
|
-
# For each s, add to chart[aPosition] a state of the form
|
171
|
-
# { dotted_rule: Y → α X • β, origin: i})
|
172
|
-
def completion(aParsing, aState, aPosition, aTracer)
|
173
|
-
if aTracer.level > 1
|
174
|
-
puts "Chart[#{aPosition}] Completion of state #{aState}:"
|
175
|
-
end
|
176
|
-
aParsing.completion(aState, aPosition) do |item|
|
177
|
-
next_mapping[item]
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
# Raise an exception to indicate a syntax error.
|
182
|
-
def handle_error(aParsing)
|
183
|
-
# Retrieve the first empty state set
|
184
|
-
pos = aParsing.chart.state_sets.find_index(&:empty?)
|
185
|
-
lexeme_at_pos = aParsing.tokens[pos - 1].lexeme
|
186
|
-
|
187
|
-
terminals = aParsing.chart.state_sets[pos - 1].expected_terminals
|
188
|
-
term_names = terminals.map(&:name)
|
189
|
-
err_msg = "Syntax error at or near token #{pos}"
|
190
|
-
err_msg << ">>>#{lexeme_at_pos}<<<:\nExpected "
|
191
|
-
err_msg << if terminals.size > 1
|
192
|
-
"one of: ['#{term_names.join("', '")}'],"
|
193
|
-
else
|
194
|
-
": #{term_names[0]},"
|
195
|
-
end
|
196
|
-
err_msg << " found a '#{aParsing.tokens[pos - 1].terminal.name}'"
|
197
|
-
raise StandardError, err_msg + ' instead.'
|
198
|
-
end
|
199
|
-
end # class
|
200
|
-
end # module
|
201
|
-
end # module
|
202
|
-
|
203
|
-
# End of file
|
data/lib/rley/parser/parsing.rb
DELETED
@@ -1,265 +0,0 @@
|
|
1
|
-
require_relative 'chart'
|
2
|
-
require_relative 'parse_state_tracker'
|
3
|
-
require_relative 'parse_tree_builder'
|
4
|
-
|
5
|
-
|
6
|
-
module Rley # This module is used as a namespace
|
7
|
-
module Parser # This module is used as a namespace
|
8
|
-
class Parsing
|
9
|
-
attr_reader(:chart)
|
10
|
-
|
11
|
-
# The sequence of input token to parse
|
12
|
-
attr_reader(:tokens)
|
13
|
-
|
14
|
-
# @param aTracer [ParseTracer] An object that traces the parsing.
|
15
|
-
# The possible values are:
|
16
|
-
# 0: No trace output (default case)
|
17
|
-
# 1: Show trace of scanning and completion rules
|
18
|
-
# 2: Same as of 1 with the addition of the prediction rules
|
19
|
-
def initialize(startDottedRules, theTokens, aTracer)
|
20
|
-
@tokens = theTokens.dup
|
21
|
-
@chart = Chart.new(startDottedRules, tokens.size, aTracer)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Return true if the parse was successful (= input tokens
|
25
|
-
# followed the syntax specified by the grammar)
|
26
|
-
def success?()
|
27
|
-
# Success can be detected as follows:
|
28
|
-
# The last chart entry has at least one complete parse state
|
29
|
-
# for the start symbol with an origin == 0
|
30
|
-
last_chart_entry = chart.state_sets[-1]
|
31
|
-
start_symbol = chart.start_symbol
|
32
|
-
|
33
|
-
# Retrieve all the complete states with start symbol in lhs
|
34
|
-
end_states = last_chart_entry.states_rewriting(start_symbol)
|
35
|
-
success_states = end_states.select { |st| st.origin.zero? }
|
36
|
-
|
37
|
-
return !success_states.empty?
|
38
|
-
end
|
39
|
-
|
40
|
-
# Return true if there are more than one complete state
|
41
|
-
# for the same lhs and same origin in any state set.
|
42
|
-
def ambiguous?()
|
43
|
-
found = chart.state_sets.find { |set| !set.ambiguities.empty? }
|
44
|
-
return !found.nil?
|
45
|
-
end
|
46
|
-
|
47
|
-
# Factory method. Builds a ParseTree from the parse result.
|
48
|
-
# @return [ParseTree]
|
49
|
-
# Algorithm:
|
50
|
-
# set state_set_index = index of last state set in chart
|
51
|
-
# Search the completed parse state that corresponds to the full parse
|
52
|
-
def parse_tree()
|
53
|
-
state_tracker = new_state_tracker
|
54
|
-
builder = tree_builder(state_tracker.state_set_index)
|
55
|
-
|
56
|
-
loop do
|
57
|
-
state_tracker.symbol_on_left
|
58
|
-
# match_symbol = state_tracker.symbol_on_left
|
59
|
-
# puts '--------------------'
|
60
|
-
# puts "Active parse state: #{state_tracker.parse_state}"
|
61
|
-
# puts "Matching symbol: #{match_symbol}"
|
62
|
-
# puts 'Parse tree:'
|
63
|
-
# puts builder.root.to_string(0)
|
64
|
-
|
65
|
-
# Place the symbol on left of the dot in the parse tree
|
66
|
-
done = insert_matched_symbol(state_tracker, builder)
|
67
|
-
break if done
|
68
|
-
end
|
69
|
-
|
70
|
-
return builder.parse_tree
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
# Push a parse state (dotted item + origin) to the
|
75
|
-
# chart entry with given index if it isn't yet in the chart entry.
|
76
|
-
def push_state(aDottedItem, anOrigin, aChartIndex, aReason)
|
77
|
-
raise StandardError, 'Dotted item may not be nil' if aDottedItem.nil?
|
78
|
-
chart.push_state(aDottedItem, anOrigin, aChartIndex, aReason)
|
79
|
-
end
|
80
|
-
|
81
|
-
|
82
|
-
# This method is called when a parse state for chart entry at position
|
83
|
-
# 'pos' expects a terminal as next symbol.
|
84
|
-
# If the input token matches the terminal symbol then:
|
85
|
-
# Retrieve all parse states for chart entry at 'aPosition'
|
86
|
-
# that have the given terminal as next symbol.
|
87
|
-
# For each s of the above states, push to chart entry aPosition + 1
|
88
|
-
# a new state like: <next dotted rule, s.origin, aPosition + 1>
|
89
|
-
# In other words, we place the dotted rules in the next state set
|
90
|
-
# such that the dot appears after terminal.
|
91
|
-
# @param aTerminal [Terminal] a terminal symbol that
|
92
|
-
# immediately follows a dot
|
93
|
-
# @param aPosition [Fixnum] position in the input token sequence.
|
94
|
-
# @param nextMapping [Proc or Lambda] code to evaluate in order to
|
95
|
-
# determine the "next" dotted rule for a given one.
|
96
|
-
def scanning(aTerminal, aPosition, &_nextMapping)
|
97
|
-
curr_token = tokens[aPosition]
|
98
|
-
return unless curr_token.terminal == aTerminal
|
99
|
-
|
100
|
-
states = states_expecting(aTerminal, aPosition, false)
|
101
|
-
states.each do |s|
|
102
|
-
next_item = yield s.dotted_rule
|
103
|
-
push_state(next_item, s.origin, aPosition + 1, :scanning)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
# This method is called when a parse state at chart entry reaches the end
|
110
|
-
# of a production.
|
111
|
-
# For every state in chart[aPosition] that is complete
|
112
|
-
# (i.e. of the form: { dotted_rule: X -> γ •, origin: j}),
|
113
|
-
# Find states s in chart[j] of the form
|
114
|
-
# {dotted_rule: Y -> α • X β, origin: i}
|
115
|
-
# In other words, rules that predicted the non-terminal X.
|
116
|
-
# For each s, add to chart[aPosition] a state of the form
|
117
|
-
# { dotted_rule: Y → α X • β, origin: i})
|
118
|
-
def completion(aState, aPosition, &_nextMapping)
|
119
|
-
curr_origin = aState.origin
|
120
|
-
curr_lhs = aState.dotted_rule.lhs
|
121
|
-
states = states_expecting(curr_lhs, curr_origin, false)
|
122
|
-
states.each do |s|
|
123
|
-
next_item = yield s.dotted_rule
|
124
|
-
push_state(next_item, s.origin, aPosition, :completion)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
|
129
|
-
# The list of ParseState from the chart entry at given position
|
130
|
-
# that expect the given terminal
|
131
|
-
def states_expecting(aTerminal, aPosition, toSort)
|
132
|
-
expecting = chart[aPosition].states_expecting(aTerminal)
|
133
|
-
return expecting if !toSort || expecting.size < 2
|
134
|
-
|
135
|
-
# Put predicted states ahead
|
136
|
-
(predicted, others) = expecting.partition(&:predicted?)
|
137
|
-
|
138
|
-
# Sort state in reverse order of their origin value
|
139
|
-
[predicted, others].each do |set|
|
140
|
-
set.sort! { |a, b| b.origin <=> a.origin }
|
141
|
-
end
|
142
|
-
|
143
|
-
return predicted + others
|
144
|
-
end
|
145
|
-
|
146
|
-
# Retrieve the parse state(s) that represents a complete, successful parse
|
147
|
-
# After a successful parse, the last chart entry
|
148
|
-
# has a parse state that involves the start symbol and
|
149
|
-
# has a dot positioned at the end of its rhs.
|
150
|
-
def end_parse_states()
|
151
|
-
last_chart_entry = chart.state_sets[-1]
|
152
|
-
start_symbol = chart.start_symbol
|
153
|
-
|
154
|
-
# Retrieve all the complete states with origin at 0
|
155
|
-
end_states = last_chart_entry.states_rewriting(start_symbol)
|
156
|
-
|
157
|
-
return end_states
|
158
|
-
end
|
159
|
-
|
160
|
-
|
161
|
-
# Insert in a parse tree the symbol on the left of the
|
162
|
-
# current dotted rule.
|
163
|
-
def insert_matched_symbol(aStateTracker, aBuilder)
|
164
|
-
# Retrieve symbol before the dot in active parse state
|
165
|
-
match_symbol = aStateTracker.symbol_on_left
|
166
|
-
|
167
|
-
# Retrieve tree node being processed
|
168
|
-
tree_node = aBuilder.current_node
|
169
|
-
|
170
|
-
done = false
|
171
|
-
case [match_symbol.class, tree_node.class]
|
172
|
-
when [Syntax::Terminal, PTree::TerminalNode]
|
173
|
-
aStateTracker.to_prev_state_set
|
174
|
-
predecessor_state_terminal(match_symbol, aStateTracker, aBuilder)
|
175
|
-
|
176
|
-
when [NilClass, Rley::PTree::TerminalNode],
|
177
|
-
[NilClass, PTree::NonTerminalNode]
|
178
|
-
# Retrieve all parse states that expect the lhs
|
179
|
-
new_states = states_expecting_lhs(aStateTracker, aBuilder)
|
180
|
-
done = true if new_states.empty?
|
181
|
-
# Select an unused parse state
|
182
|
-
aStateTracker.select_state(new_states)
|
183
|
-
|
184
|
-
when [Syntax::NonTerminal, PTree::NonTerminalNode]
|
185
|
-
completed_state_for(match_symbol, aStateTracker, aBuilder)
|
186
|
-
end
|
187
|
-
|
188
|
-
done ||= aBuilder.root == aBuilder.current_node
|
189
|
-
return done
|
190
|
-
end
|
191
|
-
|
192
|
-
private
|
193
|
-
|
194
|
-
# Factory method. Creates and initializes a ParseStateTracker instance.
|
195
|
-
def new_state_tracker()
|
196
|
-
instance = ParseStateTracker.new(chart.last_index)
|
197
|
-
instance.parse_state = end_parse_states.first
|
198
|
-
|
199
|
-
return instance
|
200
|
-
end
|
201
|
-
|
202
|
-
|
203
|
-
# A terminal symbol is on the left of dot.
|
204
|
-
# Go to the predecessor state for the given terminal
|
205
|
-
def predecessor_state_terminal(_a_symb, aStateTracker, aTreeBuilder)
|
206
|
-
index = aStateTracker.state_set_index
|
207
|
-
aTreeBuilder.current_node.range = { low: index, high: index + 1 }
|
208
|
-
link_node_to_token(aTreeBuilder, aStateTracker.state_set_index)
|
209
|
-
unless aTreeBuilder.current_node.is_a?(PTree::TerminalNode)
|
210
|
-
raise StandardError, 'Expected terminal node'
|
211
|
-
end
|
212
|
-
aTreeBuilder.move_back
|
213
|
-
state_set = chart[aStateTracker.state_set_index]
|
214
|
-
previous_state = state_set.predecessor_state(aStateTracker.parse_state)
|
215
|
-
aStateTracker.parse_state = previous_state
|
216
|
-
end
|
217
|
-
|
218
|
-
|
219
|
-
# Retrieve a complete state with given terminal symbol as lhs.
|
220
|
-
def completed_state_for(a_symb, aTracker, aTreeBuilder)
|
221
|
-
new_states = chart[aTracker.state_set_index].states_rewriting(a_symb)
|
222
|
-
aTracker.select_state(new_states)
|
223
|
-
aTreeBuilder.range = { high: aTracker.state_set_index }
|
224
|
-
aTreeBuilder.use_complete_state(aTracker.parse_state)
|
225
|
-
link_node_to_token(aTreeBuilder, aTracker.state_set_index - 1)
|
226
|
-
aTreeBuilder.move_down
|
227
|
-
end
|
228
|
-
|
229
|
-
|
230
|
-
def states_expecting_lhs(aStateTracker, aTreeBuilder)
|
231
|
-
lhs = aStateTracker.curr_dotted_item.production.lhs
|
232
|
-
new_states = states_expecting(lhs, aStateTracker.state_set_index, true)
|
233
|
-
new_states.reject! { |st| st == aStateTracker.parse_state }
|
234
|
-
# Filter out parse states with incompatible range
|
235
|
-
if new_states.size > 1
|
236
|
-
previous_node = aTreeBuilder.current_path[-3]
|
237
|
-
new_states.select! do |parse_state|
|
238
|
-
parse_state.dotted_rule.production.lhs == previous_node.symbol
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
return new_states
|
243
|
-
end
|
244
|
-
|
245
|
-
# If the current node is a terminal node
|
246
|
-
# then link the token to that node
|
247
|
-
def link_node_to_token(aTreeBuilder, aStateSetIndex)
|
248
|
-
return unless aTreeBuilder.current_node.is_a?(PTree::TerminalNode)
|
249
|
-
return unless aTreeBuilder.current_node.token.nil?
|
250
|
-
|
251
|
-
a_node = aTreeBuilder.current_node
|
252
|
-
a_node.token = tokens[aStateSetIndex] unless a_node.token
|
253
|
-
end
|
254
|
-
|
255
|
-
# Factory method. Initializes a ParseTreeBuilder object
|
256
|
-
def tree_builder(anIndex)
|
257
|
-
full_range = { low: 0, high: anIndex }
|
258
|
-
start_production = chart.start_dotted_rule.production
|
259
|
-
return ParseTreeBuilder.new(start_production, full_range)
|
260
|
-
end
|
261
|
-
end # class
|
262
|
-
end # module
|
263
|
-
end # module
|
264
|
-
|
265
|
-
# End of file
|