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