rley 0.1.12 → 0.2.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 +8 -8
- data/CHANGELOG.md +9 -0
- data/examples/parsers/parsing_L0.rb +125 -0
- data/examples/parsers/parsing_b_expr.rb +84 -84
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/formatter/json.rb +4 -0
- data/lib/rley/parser/chart.rb +12 -0
- data/lib/rley/parser/parse_state_tracker.rb +55 -0
- data/lib/rley/parser/parse_tree_builder.rb +181 -0
- data/lib/rley/parser/parsing.rb +92 -55
- data/lib/rley/ptree/parse_tree.rb +3 -86
- data/lib/rley/ptree/token_range.rb +2 -0
- data/lib/rley/syntax/grammar.rb +6 -0
- data/lib/rley/syntax/grammar_builder.rb +1 -1
- data/spec/rley/formatter/json_spec.rb +2 -2
- data/spec/rley/parser/chart_spec.rb +5 -1
- data/spec/rley/parser/parse_tree_builder_spec.rb +179 -0
- data/spec/rley/parser/parsing_spec.rb +83 -9
- data/spec/rley/ptree/parse_tree_spec.rb +7 -60
- data/spec/rley/ptree/token_range_spec.rb +1 -0
- data/spec/rley/syntax/grammar_spec.rb +4 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZTAxMjk3Yzc3YTFjZmUxY2Q2OGI1MDFhZjMxMTk5M2Q1YmMzZWMwNw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ODMxNDQ0ODZjYmI0YWRlYTE5NWQwZjE3Njg2ZGI2MzBjMjc5ZmVlMQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NmVkNTY4MzE3ZWNhMDhlNWI5OTJlODBkM2ZmOTg1Y2RjODViOThiMzAyYmUy
|
10
|
+
ZTY5N2RhN2EyNzFhMmY3MjhlNmJkNjI4MGVlOTBjZDgwYmM4ZTY0ZjhlNjE2
|
11
|
+
OGU5ZTRlNzAzOGRiOTlmMmZkZDg0NzcyMGQwZDFlYjVmYjAwMzI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
N2E0OGJhNjVjOWM3YTMzMzk5YjdjZDdiY2IzMGE5N2RkNTJkNDlkMDcyYTI3
|
14
|
+
YzY5Y2YzZTE4NjcxYjFlNzhhMmM4NDExYzk0NGEzMTE1ZmMwNDUzNzExMTA5
|
15
|
+
OGI3MDFlMzFkYWQ2Y2UzNmI1MGQ5ZGEwNGYyNTg4N2MzMTk3NTk=
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
### 0.2.00 / 2015-01-03
|
2
|
+
Version number bump: major re-design of the parse tree generation.
|
3
|
+
* [NEW] Class `ParseTreeBuilder`: builder for creating parse tree.
|
4
|
+
* [NEW] Class `ParseStateTracker`: helper class used in parse tree generation.
|
5
|
+
* [NEW] Examplar file `parsing_L0.rb`: demo using a (highly simplified) English grammar.
|
6
|
+
* [CHANGE] Class `ParseTree`: construction methods removed.
|
7
|
+
* [CHANGE] Method `Parsing#parse_tree` completely rewritten.
|
8
|
+
* [FIX] Method `Parsing#parse_tree` now handles situations where there are multiple complete parse states for a non-terminal.
|
9
|
+
|
1
10
|
### 0.1.12 / 2014-12-22
|
2
11
|
* [FIX] Fixed `Parsing#parse_tree`: code couldn't cope with parse state set containing more
|
3
12
|
than one parse state that expected the same symbol.
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# Purpose: to demonstrate how to build and render a parse tree for the L0
|
2
|
+
# language
|
3
|
+
require 'pp'
|
4
|
+
require 'rley' # Load the gem
|
5
|
+
|
6
|
+
# Steps to render a parse tree (of a valid parsed input):
|
7
|
+
# 1. Define a grammar
|
8
|
+
# 2. Create a tokenizer for the language
|
9
|
+
# 3. Create a parser for that grammar
|
10
|
+
# 4. Tokenize the input
|
11
|
+
# 5. Let the parser process the input
|
12
|
+
# 6. Generate a parse tree from the parse result
|
13
|
+
# 7. Render the parse tree (in JSON)
|
14
|
+
|
15
|
+
########################################
|
16
|
+
# Step 1. Define a grammar for a micro English-like language
|
17
|
+
# based on Jurafky & Martin L0 language.
|
18
|
+
# It defines the syntax of a sentence in a language with a
|
19
|
+
# very limited syntax and lexicon in the context of airline reservation.
|
20
|
+
builder = Rley::Syntax::GrammarBuilder.new
|
21
|
+
builder.add_terminals('Noun', 'Verb', 'Pronoun', 'Proper-Noun')
|
22
|
+
builder.add_terminals('Determiner', 'Preposition', )
|
23
|
+
builder.add_production('S' => %w[NP VP])
|
24
|
+
builder.add_production('NP' => 'Pronoun')
|
25
|
+
builder.add_production('NP' => 'Proper-Noun')
|
26
|
+
builder.add_production('NP' => %w[Determiner Nominal])
|
27
|
+
builder.add_production('Nominal' => %w[Nominal Noun])
|
28
|
+
builder.add_production('Nominal' => 'Noun')
|
29
|
+
builder.add_production('VP' => 'Verb')
|
30
|
+
builder.add_production('VP' => %w[Verb NP])
|
31
|
+
builder.add_production('VP' => %w[Verb NP PP])
|
32
|
+
builder.add_production('VP' => %w[Verb PP])
|
33
|
+
builder.add_production('PP' => %w[Preposition PP])
|
34
|
+
|
35
|
+
# And now build the grammar...
|
36
|
+
grammar_l0 = builder.grammar
|
37
|
+
|
38
|
+
|
39
|
+
########################################
|
40
|
+
# 2. Create a tokenizer for the language
|
41
|
+
# The tokenizer transforms the input into an array of tokens
|
42
|
+
# This is a very simplistic implementation for demo purposes.
|
43
|
+
|
44
|
+
# The lexicon is just a Hash with pairs of the form:
|
45
|
+
# word =>terminal symbol name
|
46
|
+
L0_lexicon = {
|
47
|
+
'flight' => 'Noun',
|
48
|
+
'breeze' => 'Noun',
|
49
|
+
'trip' => 'Noun',
|
50
|
+
'morning' => 'Noun',
|
51
|
+
'is' => 'Verb',
|
52
|
+
'prefer' => 'Verb',
|
53
|
+
'like' => 'Verb',
|
54
|
+
'need' => 'Verb',
|
55
|
+
'want' => 'Verb',
|
56
|
+
'fly' => 'Verb',
|
57
|
+
'me' => 'Pronoun',
|
58
|
+
'I' => 'Pronoun',
|
59
|
+
'you' => 'Pronoun',
|
60
|
+
'it' => 'Pronoun',
|
61
|
+
'Alaska' => 'Proper-Noun',
|
62
|
+
'Baltimore' => 'Proper-Noun',
|
63
|
+
'Chicago' => 'Proper-Noun',
|
64
|
+
'United' => 'Proper-Noun',
|
65
|
+
'American' => 'Proper-Noun',
|
66
|
+
'the' => 'Determiner',
|
67
|
+
'a' => 'Determiner',
|
68
|
+
'an' => 'Determiner',
|
69
|
+
'this' => 'Determiner',
|
70
|
+
'these' => 'Determiner',
|
71
|
+
'that' => 'Determiner',
|
72
|
+
'from' => 'Preposition',
|
73
|
+
'to' => 'Preposition',
|
74
|
+
'on' => 'Preposition',
|
75
|
+
'near' => 'Preposition'
|
76
|
+
}
|
77
|
+
|
78
|
+
# Highly simplified tokenizer implementation.
|
79
|
+
def tokenizer(aText, aGrammar)
|
80
|
+
tokens = aText.scan(/\S+/).map do |word|
|
81
|
+
term_name = L0_lexicon[word]
|
82
|
+
if term_name.nil?
|
83
|
+
fail StandardError, "Word '#{word}' not found in lexicon"
|
84
|
+
end
|
85
|
+
terminal = aGrammar.name2symbol[term_name]
|
86
|
+
Rley::Parser::Token.new(word, terminal)
|
87
|
+
end
|
88
|
+
|
89
|
+
return tokens
|
90
|
+
end
|
91
|
+
|
92
|
+
########################################
|
93
|
+
# Step 3. Create a parser for that grammar
|
94
|
+
parser = Rley::Parser::EarleyParser.new(grammar_l0)
|
95
|
+
|
96
|
+
########################################
|
97
|
+
# Step 3. Tokenize the input
|
98
|
+
valid_input = 'I prefer a morning flight'
|
99
|
+
# Another sentence: it is a flight from Chicago
|
100
|
+
tokens = tokenizer(valid_input, grammar_l0)
|
101
|
+
|
102
|
+
########################################
|
103
|
+
# Step 5. Let the parser process the input
|
104
|
+
result = parser.parse(tokens)
|
105
|
+
|
106
|
+
puts "Parsing success? #{result.success?}"
|
107
|
+
|
108
|
+
|
109
|
+
########################################
|
110
|
+
# Step 6. Generate a parse tree from the parse result
|
111
|
+
ptree = result.parse_tree
|
112
|
+
pp ptree
|
113
|
+
|
114
|
+
########################################
|
115
|
+
# Step 7. Render the parse tree (in JSON)
|
116
|
+
# Let's create a parse tree visitor
|
117
|
+
visitor = Rley::ParseTreeVisitor.new(ptree)
|
118
|
+
|
119
|
+
#Here we create a renderer object...
|
120
|
+
renderer = Rley::Formatter::Json.new(STDOUT)
|
121
|
+
|
122
|
+
# Now emit the parse tree as JSON on the console output
|
123
|
+
puts "JSON rendering of the parse tree for '#{valid_input}' input:"
|
124
|
+
renderer.render(visitor)
|
125
|
+
# End of file
|
@@ -1,85 +1,85 @@
|
|
1
|
-
# Purpose: to demonstrate how to parse basic arithmetic expressions
|
2
|
-
# and render a parse tree
|
3
|
-
require 'pp' # TODO remove this dependency
|
4
|
-
require 'rley' # Load the gem
|
5
|
-
|
6
|
-
# Steps to render a parse tree (of a valid parsed input):
|
7
|
-
# 1. Define a grammar
|
8
|
-
# 2. Create a tokenizer for the language
|
9
|
-
# 3. Create a parser for that grammar
|
10
|
-
# 4. Tokenize the input
|
11
|
-
# 5. Let the parser process the input
|
12
|
-
# 6. Generate a parse tree from the parse result
|
13
|
-
# 7. Render the parse tree (in JSON)
|
14
|
-
|
15
|
-
########################################
|
16
|
-
# Step 1. Define a grammar for a very simple arithmetic expression language
|
17
|
-
# (based on example in article on Earley's algorithm in Wikipedia)
|
18
|
-
|
19
|
-
# Let's create the grammar piece by piece
|
20
|
-
builder = Rley::Syntax::GrammarBuilder.new
|
21
|
-
builder.add_terminals('+', '*', 'integer')
|
22
|
-
builder.add_production('P' => 'S')
|
23
|
-
builder.add_production('S' => %w(S + M))
|
24
|
-
builder.add_production('S' => 'M')
|
25
|
-
builder.add_production('M' => %w(M * T))
|
26
|
-
builder.add_production('M' => 'T')
|
27
|
-
builder.add_production('T' => 'integer')
|
28
|
-
|
29
|
-
# And now build the grammar...
|
30
|
-
grammar_s_expr = builder.grammar
|
31
|
-
|
32
|
-
|
33
|
-
########################################
|
34
|
-
# 2. Create a tokenizer for the language
|
35
|
-
# The tokenizer transforms the input into an array of tokens
|
36
|
-
def tokenizer(aText, aGrammar)
|
37
|
-
tokens = aText.scan(/\S+/).map do |lexeme|
|
38
|
-
case lexeme
|
39
|
-
when '+', '*'
|
40
|
-
terminal = aGrammar.name2symbol[lexeme]
|
41
|
-
when /^[-+]?\d+$/
|
42
|
-
terminal = aGrammar.name2symbol['integer']
|
43
|
-
else
|
44
|
-
msg = "Unknown input text '#{lexeme}'"
|
45
|
-
fail StandardError, msg
|
46
|
-
end
|
47
|
-
Rley::Parser::Token.new(lexeme, terminal)
|
48
|
-
end
|
49
|
-
|
50
|
-
return tokens
|
51
|
-
end
|
52
|
-
|
53
|
-
########################################
|
54
|
-
# Step 3. Create a parser for that grammar
|
55
|
-
parser = Rley::Parser::EarleyParser.new(grammar_s_expr)
|
56
|
-
|
57
|
-
########################################
|
58
|
-
# Step 3. Tokenize the input
|
59
|
-
valid_input = '2 + 3 * 4'
|
60
|
-
tokens = tokenizer(valid_input, grammar_s_expr)
|
61
|
-
|
62
|
-
########################################
|
63
|
-
# Step 5. Let the parser process the input
|
64
|
-
result = parser.parse(tokens)
|
65
|
-
puts "Parse successful? #{result.success?}"
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
ptree
|
72
|
-
|
73
|
-
########################################
|
74
|
-
# Step 7. Render the parse tree (in JSON)
|
75
|
-
# Let's create a parse tree visitor
|
76
|
-
visitor = Rley::ParseTreeVisitor.new(ptree)
|
77
|
-
|
78
|
-
#Here we create a renderer object...
|
79
|
-
renderer = Rley::Formatter::Json.new(STDOUT)
|
80
|
-
|
81
|
-
# Now emit the parse tree as JSON on the console output
|
82
|
-
puts "JSON rendering of the parse tree for '#{valid_input}' input:"
|
83
|
-
renderer.render(visitor)
|
84
|
-
|
1
|
+
# Purpose: to demonstrate how to parse basic arithmetic expressions
|
2
|
+
# and render a parse tree
|
3
|
+
require 'pp' # TODO remove this dependency
|
4
|
+
require 'rley' # Load the gem
|
5
|
+
|
6
|
+
# Steps to render a parse tree (of a valid parsed input):
|
7
|
+
# 1. Define a grammar
|
8
|
+
# 2. Create a tokenizer for the language
|
9
|
+
# 3. Create a parser for that grammar
|
10
|
+
# 4. Tokenize the input
|
11
|
+
# 5. Let the parser process the input
|
12
|
+
# 6. Generate a parse tree from the parse result
|
13
|
+
# 7. Render the parse tree (in JSON)
|
14
|
+
|
15
|
+
########################################
|
16
|
+
# Step 1. Define a grammar for a very simple arithmetic expression language
|
17
|
+
# (based on example in article on Earley's algorithm in Wikipedia)
|
18
|
+
|
19
|
+
# Let's create the grammar piece by piece
|
20
|
+
builder = Rley::Syntax::GrammarBuilder.new
|
21
|
+
builder.add_terminals('+', '*', 'integer')
|
22
|
+
builder.add_production('P' => 'S')
|
23
|
+
builder.add_production('S' => %w(S + M))
|
24
|
+
builder.add_production('S' => 'M')
|
25
|
+
builder.add_production('M' => %w(M * T))
|
26
|
+
builder.add_production('M' => 'T')
|
27
|
+
builder.add_production('T' => 'integer')
|
28
|
+
|
29
|
+
# And now build the grammar...
|
30
|
+
grammar_s_expr = builder.grammar
|
31
|
+
|
32
|
+
|
33
|
+
########################################
|
34
|
+
# 2. Create a tokenizer for the language
|
35
|
+
# The tokenizer transforms the input into an array of tokens
|
36
|
+
def tokenizer(aText, aGrammar)
|
37
|
+
tokens = aText.scan(/\S+/).map do |lexeme|
|
38
|
+
case lexeme
|
39
|
+
when '+', '*'
|
40
|
+
terminal = aGrammar.name2symbol[lexeme]
|
41
|
+
when /^[-+]?\d+$/
|
42
|
+
terminal = aGrammar.name2symbol['integer']
|
43
|
+
else
|
44
|
+
msg = "Unknown input text '#{lexeme}'"
|
45
|
+
fail StandardError, msg
|
46
|
+
end
|
47
|
+
Rley::Parser::Token.new(lexeme, terminal)
|
48
|
+
end
|
49
|
+
|
50
|
+
return tokens
|
51
|
+
end
|
52
|
+
|
53
|
+
########################################
|
54
|
+
# Step 3. Create a parser for that grammar
|
55
|
+
parser = Rley::Parser::EarleyParser.new(grammar_s_expr)
|
56
|
+
|
57
|
+
########################################
|
58
|
+
# Step 3. Tokenize the input
|
59
|
+
valid_input = '2 + 3 * 4'
|
60
|
+
tokens = tokenizer(valid_input, grammar_s_expr)
|
61
|
+
|
62
|
+
########################################
|
63
|
+
# Step 5. Let the parser process the input
|
64
|
+
result = parser.parse(tokens)
|
65
|
+
puts "Parse successful? #{result.success?}"
|
66
|
+
|
67
|
+
|
68
|
+
########################################
|
69
|
+
# Step 6. Generate a parse tree from the parse result
|
70
|
+
ptree = result.parse_tree
|
71
|
+
pp ptree
|
72
|
+
|
73
|
+
########################################
|
74
|
+
# Step 7. Render the parse tree (in JSON)
|
75
|
+
# Let's create a parse tree visitor
|
76
|
+
visitor = Rley::ParseTreeVisitor.new(ptree)
|
77
|
+
|
78
|
+
#Here we create a renderer object...
|
79
|
+
renderer = Rley::Formatter::Json.new(STDOUT)
|
80
|
+
|
81
|
+
# Now emit the parse tree as JSON on the console output
|
82
|
+
puts "JSON rendering of the parse tree for '#{valid_input}' input:"
|
83
|
+
renderer.render(visitor)
|
84
|
+
|
85
85
|
# End of file
|
data/lib/rley/constants.rb
CHANGED
data/lib/rley/formatter/json.rb
CHANGED
@@ -64,6 +64,10 @@ module Rley # This module is used as a namespace
|
|
64
64
|
def before_terminal(term_node)
|
65
65
|
separator = sibling_flags[-1] ? ",\n" : "\n"
|
66
66
|
name = term_node.symbol.name
|
67
|
+
if term_node.token.nil?
|
68
|
+
msg = "No token associated with #{name}"
|
69
|
+
fail StandardError, msg
|
70
|
+
end
|
67
71
|
lexeme = term_node.token.lexeme
|
68
72
|
print_text(separator, "{\"#{name}\": \"#{lexeme}\"}")
|
69
73
|
sibling_flags[-1] = true
|
data/lib/rley/parser/chart.rb
CHANGED
@@ -24,6 +24,18 @@ module Rley # This module is used as a namespace
|
|
24
24
|
def [](index)
|
25
25
|
return state_sets[index]
|
26
26
|
end
|
27
|
+
|
28
|
+
# Return the index value of the last non-empty state set.
|
29
|
+
def last_index()
|
30
|
+
first_empty = state_sets.find_index { |set| set.empty? }
|
31
|
+
if first_empty.nil?
|
32
|
+
index = state_sets.size - 1
|
33
|
+
else
|
34
|
+
index = first_empty == 0 ? 0 : first_empty - 1
|
35
|
+
end
|
36
|
+
|
37
|
+
return index
|
38
|
+
end
|
27
39
|
|
28
40
|
# Push a parse state for the chart entry with given index
|
29
41
|
def push_state(aDottedItem, anOrigin, anIndex)
|
@@ -0,0 +1,55 @@
|
|
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 states used
|
4
|
+
# while a Parsing instance is constructing a parse tree.
|
5
|
+
class ParseStateTracker
|
6
|
+
# The index of the current state set
|
7
|
+
attr_reader(:state_set_index)
|
8
|
+
|
9
|
+
# The current parse state
|
10
|
+
attr_reader(:parse_state)
|
11
|
+
|
12
|
+
# The already processed states from current state set
|
13
|
+
attr_reader(:processed_states)
|
14
|
+
|
15
|
+
# Constructor. Refined variant of the inherited constructor.
|
16
|
+
def initialize(aStateSetIndex)
|
17
|
+
self.state_set_index = aStateSetIndex
|
18
|
+
end
|
19
|
+
|
20
|
+
# Write accessor. Sets the value of the state set index
|
21
|
+
def state_set_index=(anIndex)
|
22
|
+
@state_set_index = anIndex
|
23
|
+
@processed_states = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
# Write accessor. Set the given parse state as the current one.
|
27
|
+
def parse_state=(aParseState)
|
28
|
+
@parse_state = aParseState
|
29
|
+
self.processed_states[parse_state] = true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Take the first provided state that wasn't processed yet.
|
33
|
+
def select_state(theStates)
|
34
|
+
a_state = theStates.find { |st| ! processed_states.include?(st) }
|
35
|
+
self.parse_state = a_state
|
36
|
+
end
|
37
|
+
|
38
|
+
# The dotted item for the current parse state.
|
39
|
+
def curr_dotted_item()
|
40
|
+
parse_state.dotted_rule
|
41
|
+
end
|
42
|
+
|
43
|
+
def symbol_on_left()
|
44
|
+
return curr_dotted_item.prev_symbol
|
45
|
+
end
|
46
|
+
|
47
|
+
# Notification that one begins with the previous state set
|
48
|
+
def to_prev_state_set()
|
49
|
+
self.state_set_index = self.state_set_index - 1
|
50
|
+
end
|
51
|
+
end # class
|
52
|
+
end # module
|
53
|
+
end # module
|
54
|
+
|
55
|
+
# End of file
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'ostruct' # TODO delete this
|
2
|
+
require_relative '../ptree/terminal_node'
|
3
|
+
require_relative '../ptree/non_terminal_node'
|
4
|
+
require_relative '../ptree/parse_tree'
|
5
|
+
|
6
|
+
|
7
|
+
module Rley # This module is used as a namespace
|
8
|
+
module Parser # This module is used as a namespace
|
9
|
+
# Builder GoF pattern. Builder pattern builds a complex object
|
10
|
+
# (say, a parse tree) from simpler objects (terminal and non-terminal
|
11
|
+
# nodes) and using a step by step approach.
|
12
|
+
class ParseTreeBuilder
|
13
|
+
attr_reader(:root)
|
14
|
+
attr_reader(:current_path)
|
15
|
+
|
16
|
+
def initialize(aStartProduction, aRange)
|
17
|
+
@current_path = []
|
18
|
+
start_symbol = aStartProduction.lhs
|
19
|
+
add_node(start_symbol, aRange)
|
20
|
+
use_production(aStartProduction, aRange)
|
21
|
+
move_down
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return the active node.
|
25
|
+
def current_node()
|
26
|
+
return current_path.last
|
27
|
+
end
|
28
|
+
|
29
|
+
# Factory method.
|
30
|
+
def parse_tree()
|
31
|
+
return PTree::ParseTree.new(root)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Given that the current node is also lhs of the production
|
36
|
+
# associated with the complete parse state,
|
37
|
+
# Then add the rhs constituents as child nodes of the current node.
|
38
|
+
# Assumption: current node is lhs of the production association
|
39
|
+
# with the parse state.
|
40
|
+
# @param aCompleteState [ParseState] A complete parse state
|
41
|
+
# (dot is at end of rhs)
|
42
|
+
def use_complete_state(aCompleteState)
|
43
|
+
prod = aCompleteState.dotted_rule.production
|
44
|
+
use_production(prod, {low: aCompleteState.origin})
|
45
|
+
end
|
46
|
+
|
47
|
+
# Given that the current node is a non-terminal
|
48
|
+
# Make its last child node the current node.
|
49
|
+
def move_down()
|
50
|
+
curr_node = current_node
|
51
|
+
unless curr_node.is_a?(PTree::NonTerminalNode)
|
52
|
+
msg = "Current node isn't a non-terminal node #{curr_node.class}"
|
53
|
+
fail StandardError, msg
|
54
|
+
end
|
55
|
+
children = curr_node.children
|
56
|
+
path_increment = [children.size - 1, children.last]
|
57
|
+
@current_path.concat(path_increment)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# Make the predecessor of current node the
|
62
|
+
# new current node.
|
63
|
+
def move_back()
|
64
|
+
begin
|
65
|
+
if current_path.length == 1
|
66
|
+
msg = 'Cannot move further back'
|
67
|
+
fail StandardError, msg
|
68
|
+
end
|
69
|
+
(parent, pos, child_node) = current_path[-3, 3]
|
70
|
+
current_path.pop(2)
|
71
|
+
if pos > 0
|
72
|
+
new_pos = pos - 1
|
73
|
+
new_curr_node = parent.children[new_pos]
|
74
|
+
current_path << new_pos
|
75
|
+
current_path << new_curr_node
|
76
|
+
range = high_bound(child_node.range.low)
|
77
|
+
end
|
78
|
+
end while pos == 0 && new_curr_node.is_a?(PTree::NonTerminalNode)
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Add a child node to the current node.
|
83
|
+
def add_node(aSymbol, aRange)
|
84
|
+
# Create the node
|
85
|
+
a_node = new_node(aSymbol, aRange)
|
86
|
+
|
87
|
+
# Add it to the current node
|
88
|
+
add_child(a_node)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Set unbound endpoints of current node range
|
92
|
+
# to the given range.
|
93
|
+
def range=(aRange)
|
94
|
+
curr_node = current_node
|
95
|
+
return if curr_node.nil?
|
96
|
+
lower = low_bound(aRange)
|
97
|
+
unless lower.nil?
|
98
|
+
current_node.range = lower
|
99
|
+
if curr_node.is_a?(PTree::TerminalNode)
|
100
|
+
current_node.range = high_bound(lower[:low] + 1)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
upper = high_bound(aRange)
|
104
|
+
current_node.range = upper unless upper.nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def new_node(aSymbol, aRange)
|
110
|
+
case aSymbol
|
111
|
+
when Syntax::Terminal
|
112
|
+
new_node = PTree::TerminalNode.new(aSymbol, aRange)
|
113
|
+
when Syntax::NonTerminal
|
114
|
+
new_node = PTree::NonTerminalNode.new(aSymbol, aRange)
|
115
|
+
end
|
116
|
+
|
117
|
+
return new_node
|
118
|
+
end
|
119
|
+
|
120
|
+
# Add children nodes to current one.
|
121
|
+
# The children correspond to the members of the rhs of the production.
|
122
|
+
def use_production(aProduction, aRange)
|
123
|
+
prod = aProduction
|
124
|
+
curr_node = current_node
|
125
|
+
|
126
|
+
if curr_node.symbol != prod.lhs
|
127
|
+
msg = "Current node is a #{curr_node.symbol} instead of #{prod.lhs}"
|
128
|
+
fail StandardError, msg
|
129
|
+
end
|
130
|
+
self.range = aRange
|
131
|
+
prod.rhs.each { |symb| add_node(symb, {}) }
|
132
|
+
|
133
|
+
unless curr_node.children.empty?
|
134
|
+
curr_node.children.first.range.assign({ low: curr_node.range.low })
|
135
|
+
curr_node.children.last.range.assign({ high: curr_node.range.high })
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Add the given node as child node of current node
|
140
|
+
def add_child(aNode)
|
141
|
+
curr_node = current_node
|
142
|
+
|
143
|
+
if curr_node.nil?
|
144
|
+
self.root = aNode
|
145
|
+
else
|
146
|
+
curr_node.children << aNode
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Set the root node of the tree.
|
151
|
+
def root=(aNode)
|
152
|
+
@root = aNode
|
153
|
+
@current_path = [ @root ]
|
154
|
+
root.range = low_bound(0)
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
def low_bound(aRange)
|
159
|
+
result = case aRange
|
160
|
+
when Fixnum then aRange
|
161
|
+
when Hash then aRange[:low]
|
162
|
+
when PTree::TokenRange then aRange.low
|
163
|
+
end
|
164
|
+
|
165
|
+
return { low: result }
|
166
|
+
end
|
167
|
+
|
168
|
+
def high_bound(aRange)
|
169
|
+
result = case aRange
|
170
|
+
when Fixnum then aRange
|
171
|
+
when Hash then aRange[:high]
|
172
|
+
when PTree::TokenRange then aRange.high
|
173
|
+
end
|
174
|
+
|
175
|
+
return { high: result }
|
176
|
+
end
|
177
|
+
end # class
|
178
|
+
end # module
|
179
|
+
end # module
|
180
|
+
|
181
|
+
# End of file
|