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