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