rley 0.2.15 → 0.3.00
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/lib/rley/constants.rb +1 -1
- data/lib/rley/gfg/call_edge.rb +30 -0
- data/lib/rley/gfg/edge.rb +4 -0
- data/lib/rley/gfg/end_vertex.rb +1 -1
- data/lib/rley/gfg/epsilon_edge.rb +0 -4
- data/lib/rley/gfg/grm_flow_graph.rb +32 -7
- data/lib/rley/gfg/item_vertex.rb +71 -25
- data/lib/rley/gfg/non_terminal_vertex.rb +10 -1
- data/lib/rley/gfg/return_edge.rb +31 -0
- data/lib/rley/gfg/scan_edge.rb +2 -1
- data/lib/rley/gfg/shortcut_edge.rb +26 -0
- data/lib/rley/gfg/start_vertex.rb +2 -2
- data/lib/rley/gfg/vertex.rb +27 -1
- data/lib/rley/parse_forest_visitor.rb +115 -0
- data/lib/rley/parser/base_parser.rb +27 -0
- data/lib/rley/parser/dotted_item.rb +11 -0
- data/lib/rley/parser/earley_parser.rb +3 -15
- data/lib/rley/parser/gfg_chart.rb +106 -0
- data/lib/rley/parser/gfg_earley_parser.rb +139 -0
- data/lib/rley/parser/gfg_parsing.rb +384 -0
- data/lib/rley/parser/parse_entry.rb +148 -0
- data/lib/rley/parser/parse_entry_set.rb +104 -0
- data/lib/rley/parser/parse_entry_tracker.rb +56 -0
- data/lib/rley/parser/parse_forest_builder.rb +229 -0
- data/lib/rley/parser/parse_forest_factory.rb +54 -0
- data/lib/rley/parser/parse_walker_factory.rb +237 -0
- data/lib/rley/ptree/token_range.rb +14 -1
- data/lib/rley/sppf/alternative_node.rb +34 -0
- data/lib/rley/sppf/composite_node.rb +27 -0
- data/lib/rley/sppf/epsilon_node.rb +27 -0
- data/lib/rley/sppf/leaf_node.rb +12 -0
- data/lib/rley/sppf/non_terminal_node.rb +38 -0
- data/lib/rley/sppf/parse_forest.rb +48 -0
- data/lib/rley/sppf/sppf_node.rb +24 -0
- data/lib/rley/sppf/token_node.rb +29 -0
- data/lib/rley/syntax/grammar_builder.rb +16 -12
- data/lib/rley/syntax/grm_symbol.rb +6 -0
- data/lib/rley/syntax/terminal.rb +5 -0
- data/spec/rley/gfg/call_edge_spec.rb +51 -0
- data/spec/rley/gfg/end_vertex_spec.rb +1 -0
- data/spec/rley/gfg/grm_flow_graph_spec.rb +24 -2
- data/spec/rley/gfg/item_vertex_spec.rb +75 -6
- data/spec/rley/gfg/non_terminal_vertex_spec.rb +14 -0
- data/spec/rley/gfg/return_edge_spec.rb +51 -0
- data/spec/rley/gfg/shortcut_edge_spec.rb +43 -0
- data/spec/rley/gfg/vertex_spec.rb +52 -37
- data/spec/rley/parse_forest_visitor_spec.rb +238 -0
- data/spec/rley/parser/dotted_item_spec.rb +29 -8
- data/spec/rley/parser/gfg_chart_spec.rb +138 -0
- data/spec/rley/parser/gfg_earley_parser_spec.rb +918 -0
- data/spec/rley/parser/gfg_parsing_spec.rb +565 -0
- data/spec/rley/parser/parse_entry_set_spec.rb +179 -0
- data/spec/rley/parser/parse_entry_spec.rb +208 -0
- data/spec/rley/parser/parse_forest_builder_spec.rb +382 -0
- data/spec/rley/parser/parse_forest_factory_spec.rb +81 -0
- data/spec/rley/parser/parse_walker_factory_spec.rb +235 -0
- data/spec/rley/parser/state_set_spec.rb +4 -0
- data/spec/rley/sppf/alternative_node_spec.rb +72 -0
- data/spec/rley/sppf/antecedence_graph.rb +87 -0
- data/spec/rley/sppf/forest_representation.rb +136 -0
- data/spec/rley/sppf/gfg_representation.rb +111 -0
- data/spec/rley/sppf/non_terminal_node_spec.rb +64 -0
- data/spec/rley/support/ambiguous_grammar_helper.rb +36 -36
- data/spec/rley/support/expectation_helper.rb +36 -0
- data/spec/rley/support/grammar_helper.rb +28 -0
- data/spec/rley/support/grammar_sppf_helper.rb +25 -0
- data/spec/rley/syntax/grammar_builder_spec.rb +5 -0
- data/spec/rley/syntax/non_terminal_spec.rb +4 -0
- data/spec/rley/syntax/terminal_spec.rb +4 -0
- metadata +58 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require_relative '../gfg/start_vertex'
|
|
2
|
+
require_relative '../gfg/end_vertex'
|
|
3
|
+
require_relative '../gfg/item_vertex'
|
|
4
|
+
|
|
5
|
+
module Rley # This module is used as a namespace
|
|
6
|
+
module Parser # This module is used as a namespace
|
|
7
|
+
|
|
8
|
+
# Responsibilities:
|
|
9
|
+
# - To know whether the vertex is a start, end or item vertex
|
|
10
|
+
# - To know the next symbol to expect
|
|
11
|
+
class ParseEntry
|
|
12
|
+
# Link to a vertex of the GFG
|
|
13
|
+
attr_reader(:vertex)
|
|
14
|
+
|
|
15
|
+
# Links to preceding parse entries
|
|
16
|
+
attr_reader(:antecedents)
|
|
17
|
+
|
|
18
|
+
# the position in the input that matches the beginning of the rhs
|
|
19
|
+
# of the production.
|
|
20
|
+
attr_reader(:origin)
|
|
21
|
+
|
|
22
|
+
def initialize(aVertex, theOrigin)
|
|
23
|
+
@vertex = valid_vertex(aVertex)
|
|
24
|
+
@origin = theOrigin
|
|
25
|
+
@antecedents = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Add a link to an antecedent parse entry
|
|
29
|
+
def add_antecedent(anAntecedent)
|
|
30
|
+
antecedents << anAntecedent
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Equality comparison. A parse entry behaves as a value object.
|
|
34
|
+
def ==(other)
|
|
35
|
+
return true if object_id == other.object_id
|
|
36
|
+
|
|
37
|
+
if (vertex == other.vertex) && (origin == other.origin)
|
|
38
|
+
result = true
|
|
39
|
+
else
|
|
40
|
+
result = false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns true iff the vertex is a start vertex (i.e. of the form: .X)
|
|
47
|
+
def start_entry?()
|
|
48
|
+
return vertex.kind_of?(GFG::StartVertex)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns true iff the vertex is at the start of rhs
|
|
52
|
+
# (i.e. of the form: X => .Y
|
|
53
|
+
def entry_entry?()
|
|
54
|
+
return false unless vertex.kind_of?(GFG::ItemVertex)
|
|
55
|
+
|
|
56
|
+
return vertex.dotted_item.at_start?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns true iff the vertex corresponds to a dotted item
|
|
60
|
+
# X => Y
|
|
61
|
+
def dotted_entry?
|
|
62
|
+
return vertex.kind_of?(GFG::ItemVertex)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns true iff the vertex is at end of rhs (i.e. of the form: X => Y.)
|
|
66
|
+
def exit_entry?()
|
|
67
|
+
return vertex.complete?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns true iff the vertex is an end vertex (i.e. of the form: X.)
|
|
71
|
+
def end_entry?()
|
|
72
|
+
return vertex.kind_of?(GFG::EndVertex)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Return the symbol before the dot (if any)
|
|
77
|
+
def prev_symbol()
|
|
78
|
+
return vertex.prev_symbol
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Return the symbol after the dot (if any)
|
|
82
|
+
def next_symbol()
|
|
83
|
+
return vertex.next_symbol
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Return true if the entry has no antecedent entry
|
|
87
|
+
def orphan?()
|
|
88
|
+
return antecedents.empty?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
=begin
|
|
92
|
+
# Returns true if the dot is at the end of the rhs of the production.
|
|
93
|
+
# In other words, the complete rhs matches the input.
|
|
94
|
+
def complete?()
|
|
95
|
+
return vertex.reduce_item?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns true if the dot is at the start of the rhs of the production.
|
|
99
|
+
def predicted?()
|
|
100
|
+
return vertex.predicted_item?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Next expected symbol in the production
|
|
104
|
+
def next_symbol()
|
|
105
|
+
return vertex.next_symbol
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Does this parse state have the 'other' as successor?
|
|
109
|
+
def precedes?(other)
|
|
110
|
+
return false if self == other
|
|
111
|
+
|
|
112
|
+
return false unless origin == other.origin
|
|
113
|
+
other_production = other.dotted_rule.production
|
|
114
|
+
return false unless dotted_rule.production == other_production
|
|
115
|
+
|
|
116
|
+
prev_position = other.dotted_rule.prev_position
|
|
117
|
+
if prev_position.nil?
|
|
118
|
+
result = false
|
|
119
|
+
else
|
|
120
|
+
result = dotted_rule.position == prev_position
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
return result
|
|
124
|
+
end
|
|
125
|
+
=end
|
|
126
|
+
|
|
127
|
+
# Give a String representation of itself.
|
|
128
|
+
# The format of the text representation is
|
|
129
|
+
# "format of dotted rule" + " | " + origin
|
|
130
|
+
# @return [String]
|
|
131
|
+
def to_s()
|
|
132
|
+
return vertex.label + " | #{origin}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
# Return the validated GFG vertex
|
|
139
|
+
def valid_vertex(aVertex)
|
|
140
|
+
fail StandardError, 'GFG vertex cannot be nil' if aVertex.nil?
|
|
141
|
+
|
|
142
|
+
return aVertex
|
|
143
|
+
end
|
|
144
|
+
end # class
|
|
145
|
+
end # module
|
|
146
|
+
end # module
|
|
147
|
+
|
|
148
|
+
# End of file
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'forwardable' # Delegation
|
|
2
|
+
|
|
3
|
+
require_relative '../syntax/terminal'
|
|
4
|
+
require_relative '../syntax/non_terminal'
|
|
5
|
+
|
|
6
|
+
module Rley # This module is used as a namespace
|
|
7
|
+
module Parser # This module is used as a namespace
|
|
8
|
+
# Responsibilities:
|
|
9
|
+
# - To know all the parse entries in the set
|
|
10
|
+
class ParseEntrySet
|
|
11
|
+
extend Forwardable
|
|
12
|
+
def_delegators :entries, :empty?, :size, :first, :last, :pop, :each
|
|
13
|
+
|
|
14
|
+
# The set of parse entries
|
|
15
|
+
attr_reader :entries
|
|
16
|
+
|
|
17
|
+
# A Hash with pairs of the form:
|
|
18
|
+
# terminal symbol => [ parse entry expecting the terminal ]
|
|
19
|
+
#attr_reader :entries4term
|
|
20
|
+
|
|
21
|
+
# A Hash with pairs of the form:
|
|
22
|
+
# non terminal symbol => [ parse entry expecting the non-terminal ]
|
|
23
|
+
# attr_reader :entries4n_term
|
|
24
|
+
|
|
25
|
+
def initialize()
|
|
26
|
+
@entries = []
|
|
27
|
+
@entries4term = Hash.new { |hash, key| hash[key] = [] }
|
|
28
|
+
@entries4n_term = Hash.new { |hash, key| hash[key] = [] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Access the entry at given position
|
|
32
|
+
def [](index)
|
|
33
|
+
return entries[index]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def entries4term(aTerminal)
|
|
37
|
+
return @entries4term.fetch(aTerminal, [])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def entries4n_term(aNonTerminal)
|
|
41
|
+
return @entries4n_term.fetch(aNonTerminal, [])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Append the given entry (if it isn't yet in the set)
|
|
45
|
+
# to the list of parse entries
|
|
46
|
+
# @param aState [ParseEntry] the parse entry to push.
|
|
47
|
+
# @return [ParseEntry] the passed parse entry it doesn't added
|
|
48
|
+
def push_entry(anEntry)
|
|
49
|
+
match = entries.find { |entry| entry == anEntry }
|
|
50
|
+
if match
|
|
51
|
+
result = match
|
|
52
|
+
#if entries.include?(anEntry) # TODO: check performance
|
|
53
|
+
# result = false
|
|
54
|
+
else
|
|
55
|
+
@entries << anEntry
|
|
56
|
+
expecting = anEntry.next_symbol
|
|
57
|
+
add_lookup4symbol(anEntry) if expecting
|
|
58
|
+
result = anEntry
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return result
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Return an Array of Arrays of ambiguous parse entries.
|
|
66
|
+
def ambiguities()
|
|
67
|
+
complete_entries = entries.select { |entry| entry.exit_entry? }
|
|
68
|
+
return [] if complete_entries.size <= 1
|
|
69
|
+
|
|
70
|
+
# Group parse entries by lhs symbol and origin
|
|
71
|
+
groupings = complete_entries.group_by do |entry|
|
|
72
|
+
"#{entry.vertex.dotted_rule.lhs.object_id}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Retain the groups having more than one element.
|
|
76
|
+
ambiguous_groups = []
|
|
77
|
+
groupings.each_value do |a_group|
|
|
78
|
+
ambiguous_groups << a_group if a_group.size > 1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return ambiguous_groups
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The list of distinct expected terminal symbols. An expected symbol
|
|
85
|
+
# is on the left of a dot in a parse state of the parse set.
|
|
86
|
+
def expected_terminals()
|
|
87
|
+
return @entries4term.keys
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
def add_lookup4symbol(anEntry)
|
|
92
|
+
symb = anEntry.next_symbol
|
|
93
|
+
case symb
|
|
94
|
+
when Syntax::Terminal
|
|
95
|
+
@entries4term[symb] << anEntry
|
|
96
|
+
|
|
97
|
+
when Syntax::NonTerminal
|
|
98
|
+
@entries4n_term[symb] << anEntry
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end # class
|
|
102
|
+
end # module
|
|
103
|
+
end # module
|
|
104
|
+
# End of file
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Rley # This module is used as a namespace
|
|
2
|
+
module Parser # This module is used as a namespace
|
|
3
|
+
# Helper class that keeps track of the parse entries used
|
|
4
|
+
# while a Parsing instance is constructing a parse forest.
|
|
5
|
+
class ParseEntryTracker
|
|
6
|
+
# The index of the current entry set
|
|
7
|
+
attr_reader(:entry_set_index)
|
|
8
|
+
|
|
9
|
+
# The current parse entry
|
|
10
|
+
attr_reader(:parse_entry)
|
|
11
|
+
|
|
12
|
+
# The already processed entries from current entry set
|
|
13
|
+
attr_reader(:processed_entries)
|
|
14
|
+
|
|
15
|
+
# Constructor.
|
|
16
|
+
def initialize(aEntrySetIndex)
|
|
17
|
+
self.entry_set_index = aEntrySetIndex
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Write accessor. Sets the value of the entry set index
|
|
21
|
+
def entry_set_index=(anIndex)
|
|
22
|
+
@entry_set_index = anIndex
|
|
23
|
+
@processed_entries = {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Write accessor. Set the given parse entry as the current one.
|
|
27
|
+
def parse_entry=(aParseEntry)
|
|
28
|
+
fail StandardError, 'Nil parse entry' if aParseEntry.nil?
|
|
29
|
+
@parse_entry = aParseEntry
|
|
30
|
+
processed_entries[parse_entry] = true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Take the first provided entry that wasn't processed yet.
|
|
34
|
+
def select_entry(theEntrys)
|
|
35
|
+
a_entry = theEntrys.find { |st| !processed_entries.include?(st) }
|
|
36
|
+
self.parse_entry = a_entry
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The dotted item for the current parse entry.
|
|
40
|
+
def curr_dotted_item()
|
|
41
|
+
parse_entry.dotted_rule
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def symbol_on_left()
|
|
45
|
+
return curr_dotted_item.prev_symbol
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Notification that one begins with the previous entry set
|
|
49
|
+
def to_prev_entry_set()
|
|
50
|
+
self.entry_set_index = entry_set_index - 1
|
|
51
|
+
end
|
|
52
|
+
end # class
|
|
53
|
+
end # module
|
|
54
|
+
end # module
|
|
55
|
+
|
|
56
|
+
# End of file
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
require_relative '../sppf/epsilon_node'
|
|
2
|
+
require_relative '../sppf/non_terminal_node'
|
|
3
|
+
require_relative '../sppf/alternative_node'
|
|
4
|
+
require_relative '../sppf/parse_forest'
|
|
5
|
+
|
|
6
|
+
module Rley # This module is used as a namespace
|
|
7
|
+
module Parser # This module is used as a namespace
|
|
8
|
+
# Builder GoF pattern. Builder pattern builds a complex object
|
|
9
|
+
# (say, a parse forest) from simpler objects (terminal and non-terminal
|
|
10
|
+
# nodes) and using a step by step approach.
|
|
11
|
+
class ParseForestBuilder
|
|
12
|
+
# Link to parse result
|
|
13
|
+
attr_reader(:parsing)
|
|
14
|
+
|
|
15
|
+
# Link to forest object
|
|
16
|
+
attr_reader(:forest)
|
|
17
|
+
|
|
18
|
+
# Link to current path
|
|
19
|
+
attr_reader(:curr_path)
|
|
20
|
+
|
|
21
|
+
# A hash with pairs of the form: visited parse entry => forest node
|
|
22
|
+
attr_reader(:entry2node)
|
|
23
|
+
|
|
24
|
+
# A hash with pairs of the form: parent end entry => path to alternative node
|
|
25
|
+
# This is needed for synchronizing backtracking
|
|
26
|
+
attr_reader(:entry2path_to_alt)
|
|
27
|
+
|
|
28
|
+
def initialize(aParsingResult)
|
|
29
|
+
@parsing = aParsingResult
|
|
30
|
+
@curr_path = []
|
|
31
|
+
@entry2node = {}
|
|
32
|
+
@entry2path_to_alt = {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def receive_event(anEvent, anEntry, anIndex)
|
|
36
|
+
# puts "Event: #{anEvent} #{anEntry} #{anIndex}"
|
|
37
|
+
case anEntry.vertex
|
|
38
|
+
when GFG::StartVertex
|
|
39
|
+
process_start_entry(anEvent, anEntry, anIndex)
|
|
40
|
+
|
|
41
|
+
when GFG::ItemVertex
|
|
42
|
+
process_item_entry(anEvent, anEntry, anIndex)
|
|
43
|
+
|
|
44
|
+
when GFG::EndVertex
|
|
45
|
+
process_end_entry(anEvent, anEntry, anIndex)
|
|
46
|
+
else
|
|
47
|
+
fail NotImplementedError
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Return the current_parent node
|
|
52
|
+
def curr_parent()
|
|
53
|
+
return self.curr_path.last
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def process_start_entry(anEvent, anEntry, anIndex)
|
|
59
|
+
self.curr_path.pop while curr_parent.kind_of?(SPPF::AlternativeNode)
|
|
60
|
+
self.curr_path.pop
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def process_end_entry(anEvent, anEntry, anIndex)
|
|
64
|
+
case anEvent
|
|
65
|
+
when :visit
|
|
66
|
+
if curr_path.empty?
|
|
67
|
+
# Build parse forest with root node derived from the
|
|
68
|
+
# accepting parse entry.
|
|
69
|
+
@forest = create_forest(anEntry)
|
|
70
|
+
else
|
|
71
|
+
# if current_parent node matches the lhs non-terminal of anEntry
|
|
72
|
+
# set its origin to the origin of its first child (if not yet assigned)
|
|
73
|
+
curr_parent.range.assign(low: anEntry.origin)
|
|
74
|
+
@entry2node[anEntry] = self.curr_parent
|
|
75
|
+
if anEntry.antecedents.size > 1
|
|
76
|
+
# Store current path for later backtracking
|
|
77
|
+
# puts "Store backtrack context #{anEntry}"
|
|
78
|
+
# puts "path [#{curr_path.join(', ')}]"
|
|
79
|
+
self.entry2path_to_alt[anEntry] = curr_path.dup
|
|
80
|
+
curr_parent.refinement = :or
|
|
81
|
+
|
|
82
|
+
create_alternative_node(anEntry.antecedents.first)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
when :backtrack
|
|
87
|
+
# Restore path
|
|
88
|
+
@curr_path = self.entry2path_to_alt[anEntry].dup
|
|
89
|
+
# puts "Restore path #{curr_path.join(', ')}]"
|
|
90
|
+
antecedent_index = curr_parent.subnodes.size
|
|
91
|
+
# puts "Current parent #{curr_parent.to_string(0)}"
|
|
92
|
+
# puts "Antecedent index #{antecedent_index}"
|
|
93
|
+
create_alternative_node(anEntry.antecedents[antecedent_index])
|
|
94
|
+
|
|
95
|
+
when :revisit
|
|
96
|
+
# Remove most recent entry in path
|
|
97
|
+
@curr_path.pop
|
|
98
|
+
|
|
99
|
+
# Remove also its reference in parent
|
|
100
|
+
curr_parent.subnodes.pop
|
|
101
|
+
|
|
102
|
+
# Retrieve the already existing node corresponding to re-visited entry
|
|
103
|
+
popular = @entry2node[anEntry]
|
|
104
|
+
|
|
105
|
+
# Share with parent
|
|
106
|
+
curr_parent.add_subnode(popular)
|
|
107
|
+
|
|
108
|
+
else
|
|
109
|
+
fail NotImplementedError
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
=begin
|
|
114
|
+
if it is a dotted item entry (pattern is: X => α . β):
|
|
115
|
+
if there is at least one symbol before the dot
|
|
116
|
+
if that symbol is a non-terminal:
|
|
117
|
+
|
|
118
|
+
if that symbol is a terminal # else
|
|
119
|
+
create a token node,
|
|
120
|
+
with same origin as token,
|
|
121
|
+
with same right extent = origin + 1
|
|
122
|
+
add the new node as first child of current_parent
|
|
123
|
+
set curr_entry_set_index to curr_entry_set_index - 1
|
|
124
|
+
if it is a dotted item entry with a beginning dot: # else
|
|
125
|
+
if current_parent node matches the lhs non-terminal of anEntry
|
|
126
|
+
set its origin to the origin of its first child (if not yet assigned)
|
|
127
|
+
remove this node from the path
|
|
128
|
+
=end
|
|
129
|
+
def process_item_entry(anEvent, anEntry, anIndex)
|
|
130
|
+
# Retrieve the grammar symbol before the dot (if any)
|
|
131
|
+
prev_symbol = anEntry.prev_symbol
|
|
132
|
+
case prev_symbol
|
|
133
|
+
when Syntax::Terminal
|
|
134
|
+
# create a token node,
|
|
135
|
+
# with same origin as token,
|
|
136
|
+
# with same right extent = origin + 1
|
|
137
|
+
# add the new node as first child of current_parent
|
|
138
|
+
create_token_node(anEntry, anIndex)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
when Syntax::NonTerminal
|
|
142
|
+
# create a node with the non-terminal before the dot,
|
|
143
|
+
# with same right extent as curr_entry_set_index
|
|
144
|
+
# add the new node as first child of current_parent
|
|
145
|
+
# append the new node to the curr_path
|
|
146
|
+
range = { high: anIndex }
|
|
147
|
+
create_non_terminal_node(anEntry, range, prev_symbol)
|
|
148
|
+
|
|
149
|
+
when NilClass # Dot at the beginning of production
|
|
150
|
+
if anEntry.vertex.dotted_item.production.empty?
|
|
151
|
+
# Empty rhs => create an epsilon node
|
|
152
|
+
create_epsilon_node(anEntry, anIndex)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Create an empty parse forest
|
|
158
|
+
def create_forest(anEntry)
|
|
159
|
+
full_range = { low: 0, high: parsing.chart.last_index }
|
|
160
|
+
root_node = create_non_terminal_node(anEntry, full_range)
|
|
161
|
+
return Rley::SPPF::ParseForest.new(root_node)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# Factory method. Build and return an SPPF non-terminal node.
|
|
166
|
+
def create_non_terminal_node(anEntry, aRange, nonTSymb = nil)
|
|
167
|
+
non_terminal = nonTSymb.nil? ? anEntry.vertex.non_terminal : nonTSymb
|
|
168
|
+
new_node = Rley::SPPF::NonTerminalNode.new(non_terminal, aRange)
|
|
169
|
+
entry2node[anEntry] = new_node
|
|
170
|
+
add_subnode(new_node)
|
|
171
|
+
|
|
172
|
+
return new_node
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Add an alternative node to the forest
|
|
177
|
+
def create_alternative_node(anEntry)
|
|
178
|
+
alternative = Rley::SPPF::AlternativeNode.new(anEntry.vertex, curr_parent.range)
|
|
179
|
+
add_subnode(alternative)
|
|
180
|
+
|
|
181
|
+
return alternative
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def create_token_node(anEntry, anIndex)
|
|
185
|
+
token_position = anIndex - 1
|
|
186
|
+
curr_token = parsing.tokens[token_position]
|
|
187
|
+
new_node = SPPF::TokenNode.new(curr_token, token_position)
|
|
188
|
+
candidate = add_node_to_forest(new_node)
|
|
189
|
+
entry2node[anEntry] = candidate
|
|
190
|
+
|
|
191
|
+
return candidate
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def create_epsilon_node(anEntry, anIndex)
|
|
196
|
+
new_node = SPPF::EpsilonNode.new(anIndex)
|
|
197
|
+
candidate = add_node_to_forest(new_node)
|
|
198
|
+
entry2node[anEntry] = candidate
|
|
199
|
+
|
|
200
|
+
return candidate
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Add the given node if not yet present in parse forest
|
|
204
|
+
def add_node_to_forest(aNode)
|
|
205
|
+
key_node = aNode.key
|
|
206
|
+
if forest.include?(key_node)
|
|
207
|
+
new_node = forest.key2node[key_node]
|
|
208
|
+
else
|
|
209
|
+
new_node = aNode
|
|
210
|
+
forest.key2node[key_node] = new_node
|
|
211
|
+
# puts "FOREST ADD #{key_node}"
|
|
212
|
+
end
|
|
213
|
+
add_subnode(new_node, false)
|
|
214
|
+
|
|
215
|
+
return new_node
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# Add the given node as sub-node of current parent node
|
|
220
|
+
# Optionally add the node to the current path
|
|
221
|
+
def add_subnode(aNode, addToPath = true)
|
|
222
|
+
curr_parent.add_subnode(aNode) unless curr_path.empty?
|
|
223
|
+
self.curr_path << aNode if addToPath
|
|
224
|
+
end
|
|
225
|
+
end # class
|
|
226
|
+
end # module
|
|
227
|
+
end # module
|
|
228
|
+
|
|
229
|
+
# End of file
|