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.
@@ -12,7 +12,7 @@ module Loxxy
12
12
  # attr_reader(:runtime)
13
13
 
14
14
  # Build a visitor for the given top.
15
- # @param aRoot [AST::LoxNode] the parse tree to visit.
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 aPrintStmt [AST::LOXVarStmt] the variable declaration node to visit
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 aPrintStmt [AST::LOXVarStmt] the variable declaration node to visit
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 aLiteralExpr [AST::LoxAssignExpr] the variable assignment node to visit.
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 aBinaryExpr [AST::LOXBinaryExpr] the logical expression node to visit
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 aLiteralExpr [AST::LoxVariableExpr] the variable reference node to visit.
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 aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
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(_non_terminal_node)
196
+ def visit_nonterminal(aNonTerminalNode)
157
197
  # Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
158
198
  # that it cannot handle.
159
- raise NotImplementedError, 'Loxxy cannot execute this code yet.'
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 subExpr [Loxxy::Ast::LoxNode]
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 aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
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