rley 0.5.04 → 0.5.05
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 +10 -0
- data/examples/general/calc_iter1/calc_demo.rb +1 -1
- data/examples/general/calc_iter2/calc_ast_builder.rb +200 -0
- data/examples/general/calc_iter2/calc_ast_nodes.rb +156 -0
- data/examples/general/calc_iter2/calc_demo.rb +66 -0
- data/examples/general/calc_iter2/calc_grammar.rb +31 -0
- data/examples/general/calc_iter2/calc_lexer.rb +78 -0
- data/examples/general/calc_iter2/calc_parser.rb +24 -0
- data/examples/general/calc_iter2/spec/calculator_spec.rb +113 -0
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/gfg/grm_flow_graph.rb +41 -12
- data/lib/rley/gfg/vertex.rb +11 -4
- data/lib/rley/parser/gfg_parsing.rb +29 -1
- data/lib/rley/parser/parse_entry_set.rb +2 -1
- data/lib/rley/parser/parse_forest_factory.rb +7 -0
- data/lib/rley/parser/parse_rep_creator.rb +8 -2
- data/lib/rley/parser/parse_tree_builder.rb +5 -3
- data/lib/rley/parser/parse_tree_factory.rb +1 -1
- data/lib/rley/parser/parse_walker_factory.rb +15 -10
- data/spec/rley/parser/ambiguous_parse_spec.rb +1 -1
- data/spec/rley/parser/gfg_earley_parser_spec.rb +2 -2
- data/spec/rley/parser/gfg_parsing_spec.rb +1 -1
- data/spec/rley/parser/groucho_spec.rb +1 -1
- data/spec/rley/parser/parse_forest_builder_spec.rb +1 -1
- data/spec/rley/parser/parse_walker_factory_spec.rb +148 -11
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fdf5f08878706a50fe9b024c7ffed95b3cb08fd
|
4
|
+
data.tar.gz: 92265adccb7cb660b56ac2b060aee4a5facdd777
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 936959f76b1b2dc9fe6db68e4a0372f6aa421ea143876421c0ee90ab95852f71dad9fc166d80269af070fe84dfcbdef3848cb29be321e474afd40dae9e3bce59
|
7
|
+
data.tar.gz: 4e2f8a16366d4771e6d8506d3333adc40329c01a21952ee2002d432a5d0205c6ab6658b96d245bd4688227c6cf2c053fa66a57a9bdbeaeca4ef4969425b21457
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
### 0.5.05 / 2017-11-04
|
2
|
+
* [FIX] Method `GFGParsing#call_rule` didn't handle properly the case of nullable symbols appearing in more than one production rule.
|
3
|
+
* [New] New calculator example. In addition to the basic arithmetic operators (+, -, *, /) it accepts the unary minus
|
4
|
+
and the exponentiation operator. As a convenience it displays both CST and AST parse representations.
|
5
|
+
* [CHANGE] Method `ParseWalkerFactory#build_walker` added one argument that controls the way the visit when reaching anew an end vertex.
|
6
|
+
* [CHANGE] Method `ParseWalkerFactory#visit_entry` when re-visit an end vertex, the jump to related start vertex is now conditional.
|
7
|
+
* [CHANGE] File `parse_walker_factory_spec.rb` Added test to validate the different ways to walk over the parse entries.
|
8
|
+
* [CHANGE] Class `GrmFlowGraph`: Added more documentation.
|
9
|
+
* [CHANGE] Class `Vertex`: Documentation refined.
|
10
|
+
|
1
11
|
### 0.5.04 / 2017-10-26
|
2
12
|
* [Fix] Method GrmFlowGraph#traverse_df code terminated prematurely with nested call edges.
|
3
13
|
* [CHANGE] Method Grammar#name_production: suffix in default production name is changed (e.g. 'expression[3]' changed to expression_3)
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require_relative 'calc_ast_nodes'
|
2
|
+
|
3
|
+
# The purpose of a CalcASTBuilder is to build piece by piece an AST
|
4
|
+
# (Abstract Syntax Tree) from a sequence of input tokens and
|
5
|
+
# visit events produced by walking over a GFGParsing object.
|
6
|
+
# Uses the Builder GoF pattern.
|
7
|
+
# The Builder pattern creates a complex object
|
8
|
+
# (say, a parse tree) from simpler objects (terminal and non-terminal
|
9
|
+
# nodes) and using a step by step approach.
|
10
|
+
class CalcASTBuilder < Rley::Parser::ParseTreeBuilder
|
11
|
+
Terminal2NodeClass = {
|
12
|
+
# Lexical ambiguity: minus sign represents two very concepts:
|
13
|
+
# The unary negation operator on one hand, the binary substraction operator
|
14
|
+
'MINUS' => { 'add_operator_1' => Rley::PTree::TerminalNode,
|
15
|
+
'factor_2' => CalcNegateNode,
|
16
|
+
'sign_1' => CalcNegateNode
|
17
|
+
},
|
18
|
+
'NUMBER' => CalcNumberNode
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def return_first_child(_range, _tokens, theChildren)
|
24
|
+
return theChildren[0]
|
25
|
+
end
|
26
|
+
|
27
|
+
def return_second_child(_range, _tokens, theChildren)
|
28
|
+
return theChildren[1]
|
29
|
+
end
|
30
|
+
|
31
|
+
def return_last_child(_range, _tokens, theChildren)
|
32
|
+
return theChildren[-1]
|
33
|
+
end
|
34
|
+
|
35
|
+
def return_epsilon(_range, _tokens, _children)
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Overriding method.
|
40
|
+
# Create a parse tree object with given
|
41
|
+
# node as root node.
|
42
|
+
def create_tree(aRootNode)
|
43
|
+
return Rley::PTree::ParseTree.new(aRootNode)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Overriding method.
|
47
|
+
# Factory method for creating a node object for the given
|
48
|
+
# input token.
|
49
|
+
# @param aTerminal [Terminal] Terminal symbol associated with the token
|
50
|
+
# @param aTokenPosition [Integer] Position of token in the input stream
|
51
|
+
# @param aToken [Token] The input token
|
52
|
+
def new_leaf_node(aProduction, aTerminal, aTokenPosition, aToken)
|
53
|
+
klass = Terminal2NodeClass.fetch(aTerminal.name, CalcTerminalNode)
|
54
|
+
node = if klass
|
55
|
+
if klass.is_a?(Hash)
|
56
|
+
# Lexical ambiguity...
|
57
|
+
klass = klass.fetch(aProduction.name)
|
58
|
+
end
|
59
|
+
klass.new(aToken, aTokenPosition)
|
60
|
+
else
|
61
|
+
PTree::TerminalNode.new(aToken, aTokenPosition)
|
62
|
+
end
|
63
|
+
|
64
|
+
return node
|
65
|
+
end
|
66
|
+
|
67
|
+
# Method to override.
|
68
|
+
# Factory method for creating a parent node object.
|
69
|
+
# @param aProduction [Production] Production rule
|
70
|
+
# @param aRange [Range] Range of tokens matched by the rule
|
71
|
+
# @param theTokens [Array] The input tokens
|
72
|
+
# @param theChildren [Array] Children nodes (one per rhs symbol)
|
73
|
+
def new_parent_node(aProduction, aRange, theTokens, theChildren)
|
74
|
+
node = case aProduction.name
|
75
|
+
when 'expression_0' # rule 'expression' => %w[simple_expression]
|
76
|
+
return_first_child(aRange, theTokens, theChildren)
|
77
|
+
|
78
|
+
when 'simple_expression_0' # rule 'simple_expression' => 'term'
|
79
|
+
return_first_child(aRange, theTokens, theChildren)
|
80
|
+
|
81
|
+
when 'simple_expression_1'
|
82
|
+
# rule 'simple_expression' => %w[simple_expression add_operator term]
|
83
|
+
reduce_simple_expression_1(aProduction, aRange, theTokens, theChildren)
|
84
|
+
|
85
|
+
when 'term_0' # rule 'term' => 'factor'
|
86
|
+
return_first_child(aRange, theTokens, theChildren)
|
87
|
+
|
88
|
+
when 'term_1' # rule 'term' => %w[term mul_operator factor]
|
89
|
+
reduce_term_1(aProduction, aRange, theTokens, theChildren)
|
90
|
+
|
91
|
+
when 'factor_0' # rule 'factor' => 'simple_factor'
|
92
|
+
return_first_child(aRange, theTokens, theChildren)
|
93
|
+
|
94
|
+
when 'factor_1' # rule 'factor' => %w[simple_factor POWER simple_factor]
|
95
|
+
reduce_factor_1(aProduction, aRange, theTokens, theChildren)
|
96
|
+
|
97
|
+
when 'simple_factor_0' # rule 'simple_factor' => %[sign NUMBER]
|
98
|
+
reduce_simple_factor_0(aProduction, aRange, theTokens, theChildren)
|
99
|
+
|
100
|
+
when 'simple_factor_1' # rule 'simple_factor' => %w[LPAREN expression RPAREN]
|
101
|
+
return_second_child(aRange, theTokens, theChildren)
|
102
|
+
|
103
|
+
when 'simple_factor_2' # rule 'simple_factor' => %w[MINUS LPAREN expression RPAREN]
|
104
|
+
reduce_simple_factor_2(aProduction, aRange, theTokens, theChildren)
|
105
|
+
|
106
|
+
|
107
|
+
when 'sign_0' # rule 'sign' => 'PLUS'
|
108
|
+
return_first_child(aRange, theTokens, theChildren)
|
109
|
+
|
110
|
+
when 'sign_1' # rule 'sign' => 'MINUS'
|
111
|
+
return_first_child(aRange, theTokens, theChildren)
|
112
|
+
|
113
|
+
when 'sign_2'
|
114
|
+
return_epsilon(aRange, theTokens, theChildren)
|
115
|
+
|
116
|
+
when 'add_operator_0' # rule 'add_operator' => 'PLUS'
|
117
|
+
reduce_add_operator_0(aProduction, aRange, theTokens, theChildren)
|
118
|
+
|
119
|
+
when 'add_operator_1' # rule 'add_operator' => 'MINUS'
|
120
|
+
reduce_add_operator_1(aProduction, aRange, theTokens, theChildren)
|
121
|
+
|
122
|
+
when 'mul_operator_0' # rule 'mul_operator' => 'STAR'
|
123
|
+
reduce_mul_operator_0(aProduction, aRange, theTokens, theChildren)
|
124
|
+
|
125
|
+
when 'mul_operator_1' # rule 'mul_operator' => 'DIVIDE'
|
126
|
+
reduce_mul_operator_1(aProduction, aRange, theTokens, theChildren)
|
127
|
+
|
128
|
+
else
|
129
|
+
raise StandardError, "Don't know production #{aProduction.name}"
|
130
|
+
end
|
131
|
+
|
132
|
+
return node
|
133
|
+
end
|
134
|
+
|
135
|
+
def reduce_binary_operator(theChildren)
|
136
|
+
operator_node = theChildren[1]
|
137
|
+
operator_node.children << theChildren[0]
|
138
|
+
operator_node.children << theChildren[2]
|
139
|
+
return operator_node
|
140
|
+
end
|
141
|
+
|
142
|
+
# rule 'simple_expression' => %w[simple_expression add_operator term]
|
143
|
+
def reduce_simple_expression_1(_production, _range, _tokens, theChildren)
|
144
|
+
reduce_binary_operator(theChildren)
|
145
|
+
end
|
146
|
+
|
147
|
+
# rule 'term' => %w[term mul_operator factor]
|
148
|
+
def reduce_term_1(_production, _range, _tokens, theChildren)
|
149
|
+
reduce_binary_operator(theChildren)
|
150
|
+
end
|
151
|
+
|
152
|
+
# rule 'factor' => %w[simple_factor POWER simple_factor]]
|
153
|
+
def reduce_factor_1(aProduction, aRange, theTokens, theChildren)
|
154
|
+
result = PowerNode.new(theChildren[1].symbol, aRange)
|
155
|
+
result.children << theChildren[0]
|
156
|
+
result.children << theChildren[2]
|
157
|
+
|
158
|
+
return result
|
159
|
+
end
|
160
|
+
|
161
|
+
# rule 'simple_factor' => %[sign NUMBER]
|
162
|
+
def reduce_simple_factor_0(aProduction, aRange, theTokens, theChildren)
|
163
|
+
first_child = theChildren[0]
|
164
|
+
result = if first_child.kind_of?(CalcNegateNode)
|
165
|
+
-theChildren[1]
|
166
|
+
else
|
167
|
+
theChildren[1]
|
168
|
+
end
|
169
|
+
|
170
|
+
return result
|
171
|
+
end
|
172
|
+
|
173
|
+
# rule 'simple_factor' => %w[MINUS LPAREN expression RPAREN]
|
174
|
+
def reduce_simple_factor_2(aProduction, aRange, theTokens, theChildren)
|
175
|
+
negation = CalcNegateNode.new(theChildren[0].symbol, aRange.low)
|
176
|
+
negation.children << theChildren[2]
|
177
|
+
return negation
|
178
|
+
end
|
179
|
+
|
180
|
+
# rule 'add_operator' => 'PLUS'
|
181
|
+
def reduce_add_operator_0(_production, aRange, _tokens, theChildren)
|
182
|
+
return CalcAddNode.new(theChildren[0].symbol, aRange)
|
183
|
+
end
|
184
|
+
|
185
|
+
# rule 'add_operator' => 'MINUS'
|
186
|
+
def reduce_add_operator_1(_production, aRange, _tokens, theChildren)
|
187
|
+
return CalcSubtractNode.new(theChildren[0].symbol, aRange)
|
188
|
+
end
|
189
|
+
|
190
|
+
# rule 'mul_operator' => 'STAR'
|
191
|
+
def reduce_mul_operator_0(_production, aRange, _tokens, theChildren)
|
192
|
+
return CalcMultiplyNode.new(theChildren[0].symbol, aRange)
|
193
|
+
end
|
194
|
+
|
195
|
+
# rule 'mul_operator' => 'DIVIDE'
|
196
|
+
def reduce_mul_operator_1(_production, aRange, _tokens, theChildren)
|
197
|
+
return CalcDivideNode.new(theChildren[0].symbol, aRange)
|
198
|
+
end
|
199
|
+
end # class
|
200
|
+
# End of file
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# Classes that implement nodes of Abstract Syntax Trees (AST) representing
|
2
|
+
# calculator parse results.
|
3
|
+
|
4
|
+
|
5
|
+
CalcTerminalNode = Struct.new(:token, :value, :position) do
|
6
|
+
def initialize(aToken, aPosition)
|
7
|
+
self.token = aToken
|
8
|
+
self.position = aPosition
|
9
|
+
init_value(aToken.lexeme)
|
10
|
+
end
|
11
|
+
|
12
|
+
# This method can be overriden
|
13
|
+
def init_value(aLiteral)
|
14
|
+
self.value = aLiteral.dup
|
15
|
+
end
|
16
|
+
|
17
|
+
def symbol()
|
18
|
+
token.terminal
|
19
|
+
end
|
20
|
+
|
21
|
+
def interpret()
|
22
|
+
return value
|
23
|
+
end
|
24
|
+
|
25
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
26
|
+
# @param aVisitor[ParseTreeVisitor] the visitor
|
27
|
+
def accept(aVisitor)
|
28
|
+
aVisitor.visit_terminal(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class CalcNumberNode < CalcTerminalNode
|
33
|
+
def init_value(aLiteral)
|
34
|
+
case aLiteral
|
35
|
+
when /^[+-]?\d+$/
|
36
|
+
self.value = aLiteral.to_i
|
37
|
+
|
38
|
+
when /^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/
|
39
|
+
self.value = aLiteral.to_f
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Overriding the unary minus operator
|
44
|
+
def -@()
|
45
|
+
self.value = - self.value
|
46
|
+
return self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class CalcCompositeNode
|
51
|
+
attr_accessor(:children)
|
52
|
+
attr_accessor(:symbol)
|
53
|
+
attr_accessor(:aPosition)
|
54
|
+
|
55
|
+
def initialize(aSymbol, aPosition)
|
56
|
+
@symbol = aSymbol
|
57
|
+
@children = []
|
58
|
+
@position = aPosition
|
59
|
+
end
|
60
|
+
|
61
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
62
|
+
# @param aVisitor[ParseTreeVisitor] the visitor
|
63
|
+
def accept(aVisitor)
|
64
|
+
aVisitor.visit_nonterminal(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
alias subnodes children
|
68
|
+
end # class
|
69
|
+
|
70
|
+
class CalcUnaryOpNode < CalcCompositeNode
|
71
|
+
def initialize(aSymbol, aPosition)
|
72
|
+
super(aSymbol,aPosition)
|
73
|
+
end
|
74
|
+
|
75
|
+
alias members children
|
76
|
+
end # class
|
77
|
+
|
78
|
+
class CalcNegateNode < CalcUnaryOpNode
|
79
|
+
def initialize(aSymbol, aPosition)
|
80
|
+
super(aSymbol,aPosition)
|
81
|
+
end
|
82
|
+
|
83
|
+
def interpret()
|
84
|
+
return -(children[0].interpret)
|
85
|
+
end
|
86
|
+
end # class
|
87
|
+
|
88
|
+
class CalcBinaryOpNode < CalcCompositeNode
|
89
|
+
def initialize(aSymbol, aRange)
|
90
|
+
super(aSymbol, aRange)
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
def retrieve_operands()
|
96
|
+
operands = []
|
97
|
+
children.each do |child|
|
98
|
+
oper = child.respond_to?(:interpret) ? child.interpret : child
|
99
|
+
operands << oper
|
100
|
+
end
|
101
|
+
|
102
|
+
return operands
|
103
|
+
end
|
104
|
+
end # class
|
105
|
+
|
106
|
+
class CalcAddNode < CalcBinaryOpNode
|
107
|
+
# TODO
|
108
|
+
def interpret()
|
109
|
+
operands = retrieve_operands
|
110
|
+
|
111
|
+
sum = operands[0] + operands[1]
|
112
|
+
return sum
|
113
|
+
end
|
114
|
+
end # class
|
115
|
+
|
116
|
+
|
117
|
+
class CalcSubtractNode < CalcBinaryOpNode
|
118
|
+
# TODO
|
119
|
+
def interpret()
|
120
|
+
operands = retrieve_operands
|
121
|
+
|
122
|
+
substraction = operands[0] - operands[1]
|
123
|
+
return substraction
|
124
|
+
end
|
125
|
+
end # class
|
126
|
+
|
127
|
+
class CalcMultiplyNode < CalcBinaryOpNode
|
128
|
+
# TODO
|
129
|
+
def interpret()
|
130
|
+
operands = retrieve_operands
|
131
|
+
multiplication = operands[0] * operands[1]
|
132
|
+
return multiplication
|
133
|
+
end
|
134
|
+
end # class
|
135
|
+
|
136
|
+
class CalcDivideNode < CalcBinaryOpNode
|
137
|
+
# TODO
|
138
|
+
def interpret()
|
139
|
+
operands = retrieve_operands
|
140
|
+
numerator = operands[0].to_f
|
141
|
+
denominator = operands[1]
|
142
|
+
division = numerator / denominator
|
143
|
+
return division
|
144
|
+
end
|
145
|
+
end # class
|
146
|
+
|
147
|
+
|
148
|
+
class PowerNode < CalcBinaryOpNode
|
149
|
+
# TODO
|
150
|
+
def interpret()
|
151
|
+
operands = retrieve_operands
|
152
|
+
exponentiation = operands[0] ** operands[1]
|
153
|
+
return exponentiation
|
154
|
+
end
|
155
|
+
end # class
|
156
|
+
# End of file
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative 'calc_parser'
|
2
|
+
require_relative 'calc_ast_builder'
|
3
|
+
|
4
|
+
def print_title(aTitle)
|
5
|
+
puts aTitle
|
6
|
+
puts '=' * aTitle.size
|
7
|
+
end
|
8
|
+
|
9
|
+
def print_tree(aTitle, aParseTree)
|
10
|
+
# Let's create a parse tree visitor
|
11
|
+
visitor = Rley::ParseTreeVisitor.new(aParseTree)
|
12
|
+
|
13
|
+
# Now output formatted parse tree
|
14
|
+
print_title(aTitle)
|
15
|
+
renderer = Rley::Formatter::Asciitree.new($stdout)
|
16
|
+
renderer.render(visitor)
|
17
|
+
puts ''
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a calculator parser object
|
21
|
+
parser = CalcParser.new
|
22
|
+
|
23
|
+
# Parse the input expression in command-line
|
24
|
+
if ARGV.empty?
|
25
|
+
my_name = File.basename(__FILE__)
|
26
|
+
msg = <<-END_MSG
|
27
|
+
Demo calculator that prints:
|
28
|
+
- The Concrete and Abstract Syntax Trees of the math expression.
|
29
|
+
- The result of the math expression.
|
30
|
+
|
31
|
+
Command-line symtax:
|
32
|
+
ruby #{my_name} "arithmetic expression"
|
33
|
+
where:
|
34
|
+
the arithmetic expression is enclosed between double quotes (")
|
35
|
+
|
36
|
+
Example:
|
37
|
+
ruby #{my_name} "2 * 3 + (1 + 3 ** 2)"
|
38
|
+
END_MSG
|
39
|
+
puts msg
|
40
|
+
exit(1)
|
41
|
+
end
|
42
|
+
puts ARGV[0]
|
43
|
+
result = parser.parse_expression(ARGV[0])
|
44
|
+
|
45
|
+
unless result.success?
|
46
|
+
# Stop if the parse failed...
|
47
|
+
puts "Parsing of '#{ARGV[0]}' failed"
|
48
|
+
puts "Reason: #{result.failure_reason.message}"
|
49
|
+
exit(1)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Generate a concrete syntax parse tree from the parse result
|
54
|
+
cst_ptree = result.parse_tree
|
55
|
+
print_tree('Concrete Syntax Tree (CST)', cst_ptree)
|
56
|
+
|
57
|
+
# Generate an abstract syntax parse tree from the parse result
|
58
|
+
tree_builder = CalcASTBuilder
|
59
|
+
ast_ptree = result.parse_tree(tree_builder)
|
60
|
+
print_tree('Abstract Syntax Tree (AST)', ast_ptree)
|
61
|
+
|
62
|
+
# Now perform the computation of math expression
|
63
|
+
root = ast_ptree.root
|
64
|
+
print_title('Result:')
|
65
|
+
puts root.interpret.to_s # Output the expression result
|
66
|
+
# End of file
|