loxxy 0.0.24 → 0.1.0
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 +74 -0
- data/README.md +52 -12
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -0
- data/lib/loxxy/ast/ast_builder.rb +88 -81
- data/lib/loxxy/ast/ast_visitor.rb +51 -9
- data/lib/loxxy/ast/lox_block_stmt.rb +27 -0
- data/lib/loxxy/ast/lox_call_expr.rb +25 -0
- data/lib/loxxy/ast/lox_for_stmt.rb +41 -0
- data/lib/loxxy/ast/lox_fun_stmt.rb +32 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +3 -1
- data/lib/loxxy/ast/lox_while_stmt.rb +32 -0
- data/lib/loxxy/back_end/engine.rb +102 -1
- data/lib/loxxy/back_end/environment.rb +3 -3
- data/lib/loxxy/back_end/function.rb +45 -0
- data/lib/loxxy/back_end/symbol_table.rb +3 -3
- data/lib/loxxy/front_end/grammar.rb +34 -34
- data/lib/loxxy/front_end/literal.rb +1 -1
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/environment_spec.rb +1 -1
- data/spec/back_end/symbol_table_spec.rb +1 -1
- data/spec/datatype/lx_string_spec.rb +2 -0
- data/spec/datatype/number_spec.rb +3 -1
- data/spec/front_end/scanner_spec.rb +2 -0
- data/spec/interpreter_spec.rb +126 -2
- metadata +8 -2
@@ -12,7 +12,7 @@ module Loxxy
|
|
12
12
|
# attr_reader(:runtime)
|
13
13
|
|
14
14
|
# Build a visitor for the given top.
|
15
|
-
# @param
|
15
|
+
# @param aTop [AST::LoxNode] the parse tree to visit.
|
16
16
|
def initialize(aTop)
|
17
17
|
raise StandardError if aTop.nil?
|
18
18
|
|
@@ -52,7 +52,7 @@ module Loxxy
|
|
52
52
|
end
|
53
53
|
|
54
54
|
# Visit event. The visitor is about to visit a variable declaration statement.
|
55
|
-
# @param
|
55
|
+
# @param aSeqDecls [AST::LOXSeqDecl] the variable declaration node to visit
|
56
56
|
def visit_seq_decl(aSeqDecls)
|
57
57
|
broadcast(:before_seq_decl, aSeqDecls)
|
58
58
|
traverse_subnodes(aSeqDecls)
|
@@ -60,13 +60,21 @@ module Loxxy
|
|
60
60
|
end
|
61
61
|
|
62
62
|
# Visit event. The visitor is about to visit a variable declaration statement.
|
63
|
-
# @param
|
63
|
+
# @param aVarStmt [AST::LOXVarStmt] the variable declaration node to visit
|
64
64
|
def visit_var_stmt(aVarStmt)
|
65
65
|
broadcast(:before_var_stmt, aVarStmt)
|
66
66
|
traverse_subnodes(aVarStmt)
|
67
67
|
broadcast(:after_var_stmt, aVarStmt)
|
68
68
|
end
|
69
69
|
|
70
|
+
# Visit event. The visitor is about to visit a for statement.
|
71
|
+
# @param aForStmt [AST::LOXForStmt] the for statement node to visit
|
72
|
+
def visit_for_stmt(aForStmt)
|
73
|
+
broadcast(:before_for_stmt, aForStmt)
|
74
|
+
traverse_subnodes(aForStmt) # The condition is visited/evaluated here...
|
75
|
+
broadcast(:after_for_stmt, aForStmt, self)
|
76
|
+
end
|
77
|
+
|
70
78
|
# Visit event. The visitor is about to visit a if statement.
|
71
79
|
# @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
|
72
80
|
def visit_if_stmt(anIfStmt)
|
@@ -83,8 +91,24 @@ module Loxxy
|
|
83
91
|
broadcast(:after_print_stmt, aPrintStmt)
|
84
92
|
end
|
85
93
|
|
94
|
+
# Visit event. The visitor is about to visit a while statement node.
|
95
|
+
# @param aWhileStmt [AST::LOXWhileStmt] the while statement node to visit
|
96
|
+
def visit_while_stmt(aWhileStmt)
|
97
|
+
broadcast(:before_while_stmt, aWhileStmt)
|
98
|
+
traverse_subnodes(aWhileStmt) # The condition is visited/evaluated here...
|
99
|
+
broadcast(:after_while_stmt, aWhileStmt, self)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Visit event. The visitor is about to visit a block statement.
|
103
|
+
# @param aBlockStmt [AST::LOXBlockStmt] the print statement node to visit
|
104
|
+
def visit_block_stmt(aBlockStmt)
|
105
|
+
broadcast(:before_block_stmt, aBlockStmt)
|
106
|
+
traverse_subnodes(aBlockStmt) unless aBlockStmt.empty?
|
107
|
+
broadcast(:after_block_stmt, aBlockStmt)
|
108
|
+
end
|
109
|
+
|
86
110
|
# Visit event. The visitor is visiting an assignment node
|
87
|
-
# @param
|
111
|
+
# @param anAssignExpr [AST::LoxAssignExpr] the variable assignment node to visit.
|
88
112
|
def visit_assign_expr(anAssignExpr)
|
89
113
|
broadcast(:before_assign_expr, anAssignExpr)
|
90
114
|
traverse_subnodes(anAssignExpr)
|
@@ -94,7 +118,7 @@ module Loxxy
|
|
94
118
|
# Visit event. The visitor is about to visit a logical expression.
|
95
119
|
# Since logical expressions may take shorcuts by not evaluating all their
|
96
120
|
# sub-expressiosns, they are responsible for visiting or not their children.
|
97
|
-
# @param
|
121
|
+
# @param aLogicalExpr [AST::LOXLogicalExpr] the logical expression node to visit
|
98
122
|
def visit_logical_expr(aLogicalExpr)
|
99
123
|
broadcast(:before_logical_expr, aLogicalExpr)
|
100
124
|
|
@@ -121,6 +145,14 @@ module Loxxy
|
|
121
145
|
broadcast(:after_unary_expr, anUnaryExpr)
|
122
146
|
end
|
123
147
|
|
148
|
+
# Visit event. The visitor is about to visit a call expression.
|
149
|
+
# @param aCallExpr [AST::LoxCallExpr] call expression to visit
|
150
|
+
def visit_call_expr(aCallExpr)
|
151
|
+
broadcast(:before_call_expr, aCallExpr)
|
152
|
+
traverse_subnodes(aCallExpr)
|
153
|
+
broadcast(:after_call_expr, aCallExpr, self)
|
154
|
+
end
|
155
|
+
|
124
156
|
# Visit event. The visitor is about to visit a grouping expression.
|
125
157
|
# @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
|
126
158
|
def visit_grouping_expr(aGroupingExpr)
|
@@ -138,25 +170,35 @@ module Loxxy
|
|
138
170
|
end
|
139
171
|
|
140
172
|
# Visit event. The visitor is visiting a variable usage node
|
141
|
-
# @param
|
173
|
+
# @param aVariableExpr [AST::LoxVariableExpr] the variable reference node to visit.
|
142
174
|
def visit_variable_expr(aVariableExpr)
|
143
175
|
broadcast(:before_variable_expr, aVariableExpr)
|
144
176
|
broadcast(:after_variable_expr, aVariableExpr, self)
|
145
177
|
end
|
146
178
|
|
147
179
|
# Visit event. The visitor is about to visit the given terminal datatype value.
|
148
|
-
# @param
|
180
|
+
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
149
181
|
def visit_builtin(aValue)
|
150
182
|
broadcast(:before_visit_builtin, aValue)
|
151
183
|
broadcast(:after_visit_builtin, aValue)
|
152
184
|
end
|
153
185
|
|
186
|
+
# Visit event. The visitor is about to visit a function statement node.
|
187
|
+
# @param aFunStmt [AST::LoxFunStmt] function declaration to visit
|
188
|
+
def visit_fun_stmt(aFunStmt)
|
189
|
+
broadcast(:before_fun_stmt, aFunStmt)
|
190
|
+
traverse_subnodes(aFunStmt)
|
191
|
+
broadcast(:after_fun_stmt, aFunStmt, self)
|
192
|
+
end
|
193
|
+
|
154
194
|
# Visit event. The visitor is about to visit the given non terminal node.
|
155
195
|
# @param aNonTerminalNode [Rley::PTree::NonTerminalNode] the node to visit.
|
156
|
-
def visit_nonterminal(
|
196
|
+
def visit_nonterminal(aNonTerminalNode)
|
157
197
|
# Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
|
158
198
|
# that it cannot handle.
|
159
|
-
|
199
|
+
symb = aNonTerminalNode.symbol.name
|
200
|
+
msg = "Loxxy cannot execute this code yet for non-terminal symbol '#{symb}'."
|
201
|
+
raise NotImplementedError, msg
|
160
202
|
end
|
161
203
|
|
162
204
|
private
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxBlockStmt < LoxCompoundExpr
|
8
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
+
# @param decls [Loxxy::Ast::LoxSeqDecl]
|
10
|
+
def initialize(aPosition, decls)
|
11
|
+
super(aPosition, [decls])
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
subnodes.size == 1 && subnodes[0].nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
19
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
20
|
+
def accept(visitor)
|
21
|
+
visitor.visit_block_stmt(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
alias operands subnodes
|
25
|
+
end # class
|
26
|
+
end # module
|
27
|
+
end # module
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxCallExpr < LoxCompoundExpr
|
8
|
+
attr_accessor :callee
|
9
|
+
attr_reader :arguments
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param argList [Array<Loxxy::Ast::LoxNode>]
|
13
|
+
def initialize(aPosition, argList)
|
14
|
+
super(aPosition, [])
|
15
|
+
@arguments = argList
|
16
|
+
end
|
17
|
+
|
18
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
19
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
20
|
+
def accept(visitor)
|
21
|
+
visitor.visit_call_expr(self)
|
22
|
+
end
|
23
|
+
end # class
|
24
|
+
end # module
|
25
|
+
end # module
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxForStmt < LoxCompoundExpr
|
8
|
+
# @return [LoxNode] test expression
|
9
|
+
attr_reader :test_expr
|
10
|
+
|
11
|
+
# @return [LoxNode] update expression
|
12
|
+
attr_reader :update_expr
|
13
|
+
|
14
|
+
# @return [LoxNode] body statement
|
15
|
+
attr_accessor :body_stmt
|
16
|
+
|
17
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
18
|
+
# @param initialization [Loxxy::Ast::LoxNode]
|
19
|
+
# @param testExpr [Loxxy::Ast::LoxNode]
|
20
|
+
# @param updateExpr [Loxxy::Ast::LoxNode]
|
21
|
+
def initialize(aPosition, initialization, testExpr, updateExpr)
|
22
|
+
child = initialization ? [initialization] : []
|
23
|
+
super(aPosition, child)
|
24
|
+
@test_expr = testExpr
|
25
|
+
@update_expr = updateExpr
|
26
|
+
end
|
27
|
+
|
28
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
29
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
30
|
+
def accept(visitor)
|
31
|
+
visitor.visit_for_stmt(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Accessor to the condition expression
|
35
|
+
# @return [LoxNode]
|
36
|
+
def condition
|
37
|
+
subnodes[0]
|
38
|
+
end
|
39
|
+
end # class
|
40
|
+
end # module
|
41
|
+
end # module
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxFunStmt < LoxCompoundExpr
|
8
|
+
attr_reader :name
|
9
|
+
attr_reader :params
|
10
|
+
attr_reader :body
|
11
|
+
|
12
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
13
|
+
# @param aName [String]
|
14
|
+
# @param arguments [Array<String>]
|
15
|
+
# @param body [Ast::LoxBlockStmt]
|
16
|
+
def initialize(aPosition, aName, paramList, aBody)
|
17
|
+
super(aPosition, [])
|
18
|
+
@name = aName
|
19
|
+
@params = paramList
|
20
|
+
@body = aBody
|
21
|
+
end
|
22
|
+
|
23
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
24
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
25
|
+
def accept(visitor)
|
26
|
+
visitor.visit_fun_stmt(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias operands subnodes
|
30
|
+
end # class
|
31
|
+
end # module
|
32
|
+
end # module
|
@@ -12,7 +12,9 @@ module Loxxy
|
|
12
12
|
attr_reader :else_stmt
|
13
13
|
|
14
14
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
|
-
# @param
|
15
|
+
# @param condExpr [Loxxy::Ast::LoxNode]
|
16
|
+
# @param thenStmt [Loxxy::Ast::LoxNode]
|
17
|
+
# @param elseStmt [Loxxy::Ast::LoxNode]
|
16
18
|
def initialize(aPosition, condExpr, thenStmt, elseStmt)
|
17
19
|
super(aPosition, [condExpr])
|
18
20
|
@then_stmt = thenStmt
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxWhileStmt < LoxCompoundExpr
|
8
|
+
# @return [LoxNode] body of the while loop (as a statement)
|
9
|
+
attr_reader :body
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param condExpr [Loxxy::Ast::LoxNode] iteration condition
|
13
|
+
# @param theBody [Loxxy::Ast::LoxNode]
|
14
|
+
def initialize(aPosition, condExpr, theBody)
|
15
|
+
super(aPosition, [condExpr])
|
16
|
+
@body = theBody
|
17
|
+
end
|
18
|
+
|
19
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
20
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
21
|
+
def accept(visitor)
|
22
|
+
visitor.visit_while_stmt(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Accessor to the condition expression
|
26
|
+
# @return [LoxNode]
|
27
|
+
def condition
|
28
|
+
subnodes[0]
|
29
|
+
end
|
30
|
+
end # class
|
31
|
+
end # module
|
32
|
+
end # module
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# Load all the classes implementing AST nodes
|
4
4
|
require_relative '../ast/all_lox_nodes'
|
5
|
+
require_relative 'function'
|
5
6
|
require_relative 'symbol_table'
|
6
7
|
|
7
8
|
module Loxxy
|
@@ -25,6 +26,8 @@ module Loxxy
|
|
25
26
|
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
26
27
|
@symbol_table = SymbolTable.new
|
27
28
|
@stack = []
|
29
|
+
|
30
|
+
init_globals
|
28
31
|
end
|
29
32
|
|
30
33
|
# Given an abstract syntax parse tree visitor, launch the visit
|
@@ -51,6 +54,23 @@ module Loxxy
|
|
51
54
|
symbol_table.insert(new_var)
|
52
55
|
end
|
53
56
|
|
57
|
+
def before_for_stmt(aForStmt)
|
58
|
+
before_block_stmt(aForStmt)
|
59
|
+
end
|
60
|
+
|
61
|
+
def after_for_stmt(aForStmt, aVisitor)
|
62
|
+
loop do
|
63
|
+
aForStmt.test_expr.accept(aVisitor)
|
64
|
+
condition = stack.pop
|
65
|
+
break unless condition.truthy?
|
66
|
+
|
67
|
+
aForStmt.body_stmt.accept(aVisitor)
|
68
|
+
aForStmt.update_expr&.accept(aVisitor)
|
69
|
+
stack.pop
|
70
|
+
end
|
71
|
+
after_block_stmt(aForStmt)
|
72
|
+
end
|
73
|
+
|
54
74
|
def after_if_stmt(anIfStmt, aVisitor)
|
55
75
|
# Retrieve the result of the condition evaluation
|
56
76
|
condition = stack.pop
|
@@ -66,10 +86,30 @@ module Loxxy
|
|
66
86
|
@ostream.print tos.to_str
|
67
87
|
end
|
68
88
|
|
89
|
+
def after_while_stmt(aWhileStmt, aVisitor)
|
90
|
+
loop do
|
91
|
+
condition = stack.pop
|
92
|
+
break unless condition.truthy?
|
93
|
+
|
94
|
+
aWhileStmt.body.accept(aVisitor)
|
95
|
+
aWhileStmt.condition.accept(aVisitor)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def before_block_stmt(_aBlockStmt)
|
100
|
+
new_env = Environment.new
|
101
|
+
symbol_table.enter_environment(new_env)
|
102
|
+
end
|
103
|
+
|
104
|
+
def after_block_stmt(_aBlockStmt)
|
105
|
+
symbol_table.leave_environment
|
106
|
+
end
|
107
|
+
|
69
108
|
def after_assign_expr(anAssignExpr)
|
70
109
|
var_name = anAssignExpr.name
|
71
110
|
variable = symbol_table.lookup(var_name)
|
72
111
|
raise StandardError, "Unknown variable #{var_name}" unless variable
|
112
|
+
|
73
113
|
value = stack.pop
|
74
114
|
variable.assign(value)
|
75
115
|
stack.push value # An expression produces a value
|
@@ -130,6 +170,27 @@ module Loxxy
|
|
130
170
|
end
|
131
171
|
end
|
132
172
|
|
173
|
+
def after_call_expr(aCallExpr, aVisitor)
|
174
|
+
# Evaluate callee part
|
175
|
+
aCallExpr.callee.accept(aVisitor)
|
176
|
+
callee = stack.pop
|
177
|
+
aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
|
178
|
+
|
179
|
+
if callee.kind_of?(NativeFunction)
|
180
|
+
stack.push callee.call # Pass arguments
|
181
|
+
else
|
182
|
+
new_env = Environment.new(symbol_table.current_env)
|
183
|
+
symbol_table.enter_environment(new_env)
|
184
|
+
callee.parameters&.each do |param_name|
|
185
|
+
local = Variable.new(param_name, stack.pop)
|
186
|
+
symbol_table.insert(local)
|
187
|
+
end
|
188
|
+
stack.push callee.call(aVisitor)
|
189
|
+
|
190
|
+
symbol_table.leave_environment
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
133
194
|
def after_grouping_expr(_groupingExpr)
|
134
195
|
# Do nothing: work was already done by visiting /evaluating the subexpression
|
135
196
|
end
|
@@ -147,10 +208,50 @@ module Loxxy
|
|
147
208
|
stack.push(literalExpr.literal)
|
148
209
|
end
|
149
210
|
|
150
|
-
# @param
|
211
|
+
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
151
212
|
def before_visit_builtin(aValue)
|
152
213
|
stack.push(aValue)
|
153
214
|
end
|
215
|
+
|
216
|
+
def after_fun_stmt(aFunStmt, aVisitor)
|
217
|
+
function = Function.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
|
218
|
+
new_var = Variable.new(aFunStmt.name, function)
|
219
|
+
symbol_table.insert(new_var)
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
NativeFunction = Struct.new(:callable, :interp) do
|
225
|
+
def accept(_visitor)
|
226
|
+
interp.stack.push self
|
227
|
+
end
|
228
|
+
|
229
|
+
def call
|
230
|
+
callable.call
|
231
|
+
end
|
232
|
+
|
233
|
+
def to_str
|
234
|
+
'<native fn>'
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def init_globals
|
239
|
+
add_native_fun('clock', native_clock)
|
240
|
+
end
|
241
|
+
|
242
|
+
def add_native_fun(aName, aProc)
|
243
|
+
native_fun = Variable.new(aName, NativeFunction.new(aProc, self))
|
244
|
+
symbol_table.insert(native_fun)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Ruby-native function that returns (as float) the number of seconds since
|
248
|
+
# a given time reference.
|
249
|
+
def native_clock
|
250
|
+
proc do
|
251
|
+
now = Time.now.to_f
|
252
|
+
Datatype::Number.new(now)
|
253
|
+
end
|
254
|
+
end
|
154
255
|
end # class
|
155
256
|
end # module
|
156
257
|
end # module
|