loxxy 0.0.27 → 0.1.03

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.
@@ -6,11 +6,15 @@ module Loxxy
6
6
  module Ast
7
7
  class LoxBlockStmt < LoxCompoundExpr
8
8
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
9
- # @param operand [Loxxy::Ast::LoxSeqDecl]
9
+ # @param decls [Loxxy::Ast::LoxSeqDecl]
10
10
  def initialize(aPosition, decls)
11
11
  super(aPosition, [decls])
12
12
  end
13
13
 
14
+ def empty?
15
+ subnodes.size == 1 && subnodes[0].nil?
16
+ end
17
+
14
18
  # Part of the 'visitee' role in Visitor design pattern.
15
19
  # @param visitor [Ast::ASTVisitor] the visitor
16
20
  def accept(visitor)
@@ -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
@@ -18,7 +18,6 @@ module Loxxy
18
18
  # @param initialization [Loxxy::Ast::LoxNode]
19
19
  # @param testExpr [Loxxy::Ast::LoxNode]
20
20
  # @param updateExpr [Loxxy::Ast::LoxNode]
21
- # @param body [Loxxy::Ast::LoxNode]
22
21
  def initialize(aPosition, initialization, testExpr, updateExpr)
23
22
  child = initialization ? [initialization] : []
24
23
  super(aPosition, child)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ # rubocop: disable Style/AccessorGrouping
8
+ class LoxFunStmt < LoxCompoundExpr
9
+ attr_reader :name
10
+ attr_reader :params
11
+ attr_reader :body
12
+
13
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
14
+ # @param aName [String]
15
+ # @param arguments [Array<String>]
16
+ # @param body [Ast::LoxBlockStmt]
17
+ def initialize(aPosition, aName, paramList, aBody)
18
+ super(aPosition, [])
19
+ @name = aName
20
+ @params = paramList
21
+ @body = aBody
22
+ end
23
+
24
+ # Part of the 'visitee' role in Visitor design pattern.
25
+ # @param visitor [Ast::ASTVisitor] the visitor
26
+ def accept(visitor)
27
+ visitor.visit_fun_stmt(self)
28
+ end
29
+
30
+ alias operands subnodes
31
+ end # class
32
+ # rubocop: enable Style/AccessorGrouping
33
+ end # module
34
+ 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,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ Signature = Struct.new(:parameter_types)
8
+
9
+ # A Lox binary operator
10
+ class BinaryOperator
11
+ # @return [String] text representation of the operator
12
+ attr_reader :name
13
+
14
+ # @return [Array<Class>]
15
+ attr_reader :signatures
16
+
17
+ # @param aName [String] "name" of operator
18
+ # @param theSignatures [Array<Signature>] allowed signatures
19
+ def initialize(aName, theSignatures)
20
+ @name = aName
21
+ @signatures = theSignatures
22
+ end
23
+
24
+ # rubocop: disable Style/ClassEqualityComparison
25
+ def validate_operands(operand1, operand2)
26
+ compliant = signatures.find do |(type1, type2)|
27
+ next unless operand1.kind_of?(type1)
28
+
29
+ if type2 == :idem
30
+ (operand2.class == operand1.class)
31
+ else
32
+ operand2.kind_of?(type2)
33
+ end
34
+ end
35
+ # rubocop: enable Style/ClassEqualityComparison
36
+
37
+ unless compliant
38
+ err = Loxxy::RuntimeError
39
+ if signatures.size == 1 && signatures[0].last == :idem
40
+ raise err, "Operands must be #{datatype_name(signatures[0].first)}s."
41
+ elsif signatures.size == 2 && signatures.all? { |(_, second)| second == :idem }
42
+ type1 = datatype_name(signatures[0].first)
43
+ type2 = datatype_name(signatures[1].first)
44
+ raise err, "Operands must be two #{type1}s or two #{type2}s."
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def datatype_name(aClass)
52
+ # (?:(?:[^:](?!:|(?<=LX))))+$
53
+ aClass.name.sub(/^.+(?=::)::(?:LX)?/, '').downcase
54
+ end
55
+ end # class
56
+ end # module
57
+ end # module
@@ -2,7 +2,10 @@
2
2
 
3
3
  # Load all the classes implementing AST nodes
4
4
  require_relative '../ast/all_lox_nodes'
5
+ require_relative 'binary_operator'
6
+ require_relative 'function'
5
7
  require_relative 'symbol_table'
8
+ require_relative 'unary_operator'
6
9
 
7
10
  module Loxxy
8
11
  module BackEnd
@@ -19,12 +22,24 @@ module Loxxy
19
22
  # @return [Array<Datatype::BuiltinDatatyp>] Stack for the values of expr
20
23
  attr_reader :stack
21
24
 
25
+ # @return [Hash { Symbol => UnaryOperator}]
26
+ attr_reader :unary_operators
27
+
28
+ # @return [Hash { Symbol => BinaryOperator}]
29
+ attr_reader :binary_operators
30
+
22
31
  # @param theOptions [Hash]
23
32
  def initialize(theOptions)
24
33
  @config = theOptions
25
34
  @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
26
35
  @symbol_table = SymbolTable.new
27
36
  @stack = []
37
+ @unary_operators = {}
38
+ @binary_operators = {}
39
+
40
+ init_unary_operators
41
+ init_binary_operators
42
+ init_globals
28
43
  end
29
44
 
30
45
  # Given an abstract syntax parse tree visitor, launch the visit
@@ -80,7 +95,7 @@ module Loxxy
80
95
 
81
96
  def after_print_stmt(_printStmt)
82
97
  tos = stack.pop
83
- @ostream.print tos.to_str
98
+ @ostream.print tos ? tos.to_str : 'nil'
84
99
  end
85
100
 
86
101
  def after_while_stmt(aWhileStmt, aVisitor)
@@ -145,9 +160,11 @@ module Loxxy
145
160
  end
146
161
 
147
162
  def after_binary_expr(aBinaryExpr)
148
- op = aBinaryExpr.operator
149
163
  operand2 = stack.pop
150
164
  operand1 = stack.pop
165
+ op = aBinaryExpr.operator
166
+ operator = binary_operators[op]
167
+ operator.validate_operands(operand1, operand2)
151
168
  if operand1.respond_to?(op)
152
169
  stack.push operand1.send(op, operand2)
153
170
  else
@@ -157,16 +174,39 @@ module Loxxy
157
174
  end
158
175
 
159
176
  def after_unary_expr(anUnaryExpr)
160
- op = anUnaryExpr.operator
161
177
  operand = stack.pop
178
+ op = anUnaryExpr.operator
179
+ operator = unary_operators[op]
180
+ operator.validate_operand(operand)
162
181
  if operand.respond_to?(op)
163
182
  stack.push operand.send(op)
164
183
  else
165
- msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
184
+ msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
166
185
  raise StandardError, msg1
167
186
  end
168
187
  end
169
188
 
189
+ def after_call_expr(aCallExpr, aVisitor)
190
+ # Evaluate callee part
191
+ aCallExpr.callee.accept(aVisitor)
192
+ callee = stack.pop
193
+ aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
194
+
195
+ if callee.kind_of?(NativeFunction)
196
+ stack.push callee.call # Pass arguments
197
+ else
198
+ new_env = Environment.new(symbol_table.current_env)
199
+ symbol_table.enter_environment(new_env)
200
+ callee.parameters&.each do |param_name|
201
+ local = Variable.new(param_name, stack.pop)
202
+ symbol_table.insert(local)
203
+ end
204
+ callee.call(aVisitor)
205
+
206
+ symbol_table.leave_environment
207
+ end
208
+ end
209
+
170
210
  def after_grouping_expr(_groupingExpr)
171
211
  # Do nothing: work was already done by visiting /evaluating the subexpression
172
212
  end
@@ -184,10 +224,91 @@ module Loxxy
184
224
  stack.push(literalExpr.literal)
185
225
  end
186
226
 
187
- # @param aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
227
+ # @param aValue [Ast::BuiltinDattype] the built-in datatype value
188
228
  def before_visit_builtin(aValue)
189
229
  stack.push(aValue)
190
230
  end
231
+
232
+ def after_fun_stmt(aFunStmt, _visitor)
233
+ function = Function.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
234
+ new_var = Variable.new(aFunStmt.name, function)
235
+ symbol_table.insert(new_var)
236
+ end
237
+
238
+ private
239
+
240
+ NativeFunction = Struct.new(:callable, :interp) do
241
+ def accept(_visitor)
242
+ interp.stack.push self
243
+ end
244
+
245
+ def call
246
+ callable.call
247
+ end
248
+
249
+ def to_str
250
+ '<native fn>'
251
+ end
252
+ end
253
+
254
+ def init_unary_operators
255
+ negate_op = UnaryOperator.new('-', [Datatype::Number])
256
+ unary_operators[:-@] = negate_op
257
+
258
+ negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype])
259
+ unary_operators[:!] = negation_op
260
+ end
261
+
262
+ def init_binary_operators
263
+ plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
264
+ [Datatype::LXString, :idem]])
265
+ binary_operators[:+] = plus_op
266
+
267
+ minus_op = BinaryOperator.new('-', [[Datatype::Number, :idem]])
268
+ binary_operators[:-] = minus_op
269
+
270
+ star_op = BinaryOperator.new('*', [[Datatype::Number, :idem]])
271
+ binary_operators[:*] = star_op
272
+
273
+ slash_op = BinaryOperator.new('/', [[Datatype::Number, :idem]])
274
+ binary_operators[:/] = slash_op
275
+
276
+ equal_equal_op = BinaryOperator.new('==', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
277
+ binary_operators[:==] = equal_equal_op
278
+
279
+ not_equal_op = BinaryOperator.new('!=', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
280
+ binary_operators[:!=] = not_equal_op
281
+
282
+ less_op = BinaryOperator.new('<', [[Datatype::Number, :idem]])
283
+ binary_operators[:<] = less_op
284
+
285
+ less_equal_op = BinaryOperator.new('<=', [[Datatype::Number, :idem]])
286
+ binary_operators[:<=] = less_equal_op
287
+
288
+ greater_op = BinaryOperator.new('>', [[Datatype::Number, :idem]])
289
+ binary_operators[:>] = greater_op
290
+
291
+ greater_equal_op = BinaryOperator.new('>=', [[Datatype::Number, :idem]])
292
+ binary_operators[:>=] = greater_equal_op
293
+ end
294
+
295
+ def init_globals
296
+ add_native_fun('clock', native_clock)
297
+ end
298
+
299
+ def add_native_fun(aName, aProc)
300
+ native_fun = Variable.new(aName, NativeFunction.new(aProc, self))
301
+ symbol_table.insert(native_fun)
302
+ end
303
+
304
+ # Ruby-native function that returns (as float) the number of seconds since
305
+ # a given time reference.
306
+ def native_clock
307
+ proc do
308
+ now = Time.now.to_f
309
+ Datatype::Number.new(now)
310
+ end
311
+ end
191
312
  end # class
192
313
  end # module
193
314
  end # module
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../datatype/all_datatypes'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # rubocop: disable Style/AccessorGrouping
8
+ # Representation of a Lox function.
9
+ # It is a named slot that can be associated with a value at the time.
10
+ class Function
11
+ # @return [String]
12
+ attr_reader :name
13
+
14
+ # @return [Array<>] the parameters
15
+ attr_reader :parameters
16
+ attr_reader :body
17
+ attr_reader :stack
18
+
19
+ # Create a variable with given name and initial value
20
+ # @param aName [String] The name of the variable
21
+ # @param aValue [Datatype::BuiltinDatatype] the initial assigned value
22
+ def initialize(aName, parameterList, aBody, aStack)
23
+ @name = aName.dup
24
+ @parameters = parameterList
25
+ @body = aBody
26
+ @stack = aStack
27
+ end
28
+
29
+ def accept(_visitor)
30
+ stack.push self
31
+ end
32
+
33
+ def call(aVisitor)
34
+ body.empty? ? Datatype::Nil.instance : body.accept(aVisitor)
35
+ end
36
+
37
+ # Text representation of a Lox function
38
+ def to_str
39
+ "<fn #{name}>"
40
+ end
41
+ end # class
42
+ # rubocop: enable Style/AccessorGrouping
43
+ end # module
44
+ end # module
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../error'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # A Lox unary operator
8
+ class UnaryOperator
9
+ # @return [String] text representation of the operator
10
+ attr_reader :name
11
+
12
+ # @return [Array<Class>]
13
+ attr_reader :signatures
14
+
15
+ # @param aName [String] "name" of operator
16
+ # @param theSignatures [Array<Class>] allowed signatures
17
+ def initialize(aName, theSignatures)
18
+ @name = aName
19
+ @signatures = theSignatures
20
+ end
21
+
22
+ def validate_operand(operand1)
23
+ compliant = signatures.find { |some_type| operand1.kind_of?(some_type) }
24
+
25
+ unless compliant
26
+ err = Loxxy::RuntimeError
27
+ # if signatures.size == 1
28
+ raise err, "Operand must be a #{datatype_name(signatures[0])}."
29
+ # end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def datatype_name(aClass)
36
+ # (?:(?:[^:](?!:|(?<=LX))))+$
37
+ aClass.name.sub(/^.+(?=::)::(?:LX)?/, '').downcase
38
+ end
39
+ end # class
40
+ end # module
41
+ end # module
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loxxy
4
+ # Abstract class. Generalization of Loxxy error classes.
5
+ class Error < StandardError; end
6
+
7
+ # Error occurring while Loxxy executes some invalid Lox code.
8
+ class RuntimeError < Error; end
9
+ end