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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b5c84e4d5e47e769c76da705abef632e4748311c
4
- data.tar.gz: 876569a22741aa76f5d9b7bec8404c0a518e02b3
3
+ metadata.gz: 5fdf5f08878706a50fe9b024c7ffed95b3cb08fd
4
+ data.tar.gz: 92265adccb7cb660b56ac2b060aee4a5facdd777
5
5
  SHA512:
6
- metadata.gz: da13f9f5924752adc98be8822f079e078ff4e5345a1cbf63dcd3f6d0c28c695397abf4f80db6fa38e549615cb1db264baadcb4b3fdc1428ef45ac3aa490459cf
7
- data.tar.gz: 84b73e987ffb98f5ce5cf1a57f62a743507bff497c8c8c3294e887310036ba9893796ec5f3b9d1d8f2335b7c33bb47cda3e6455e7f66ce0dfcb3c85b90e629a4
6
+ metadata.gz: 936959f76b1b2dc9fe6db68e4a0372f6aa421ea143876421c0ee90ab95852f71dad9fc166d80269af070fe84dfcbdef3848cb29be321e474afd40dae9e3bce59
7
+ data.tar.gz: 4e2f8a16366d4771e6d8506d3333adc40329c01a21952ee2002d432a5d0205c6ab6658b96d245bd4688227c6cf2c053fa66a57a9bdbeaeca4ef4969425b21457
@@ -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)
@@ -11,7 +11,7 @@ Command-line symtax:
11
11
  the arithmetic expression is enclosed between double quotes (")
12
12
 
13
13
  Example:
14
- ruby #{my_name} "2 * 3 + (4 - 1)"
14
+ ruby #{my_name} "2 * 3 + (1 + 3 ** 2)"
15
15
  END_MSG
16
16
  puts msg
17
17
  exit(1)
@@ -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