loxxy 0.1.07 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f171f0046ff95784ed21b369751ca65d25e0ff757d1b2dccc534087a4b9c4a8d
4
- data.tar.gz: 579badf6a30436962cb18e63dfee1ed361cfd152e9fecf54f2bfd25395db086b
3
+ metadata.gz: 665b2f201c4e369bb46b4b62cd8398f8d1f6f563b073fb6e1595b767f306c627
4
+ data.tar.gz: 7bd3ca2024f2f05150d3fd8af9cdaee28d6dc4d2bbe48ebfe6ae9fd78166fc7f
5
5
  SHA512:
6
- metadata.gz: 0470f022b121ceaff279f75024e07b78a218c3baa4d44e6f9bf54f4ebddf18341b27f697fe68c727eb2d61add2c55e7c102fbf73bfd29ef4fb3965024247f747
7
- data.tar.gz: 320dbb075351069f624103f1d93aadf903ec6a5b7bb685871f53227b496760ac67ae9fa1dae85491efd9b94cfeb90eac8f66e104b4f70eaac9b0da081dfde567
6
+ metadata.gz: 0cffb636754ac0140aa7873955c75d8e6a048a0806cacf22484de91bacecb4fdb7d55127f7ff6e2f5602ff02f1eb9f24de788a6be0053768c68f12b9cdd0caab
7
+ data.tar.gz: 331fb47a8a46a9ea6a086556814b95aba7d7a4d7a3c600f82acacfd38dd567c657ca79f6ce683e17e64e8104167692a2b49e7628bac9d8536a5bd0625ceab347
data/CHANGELOG.md CHANGED
@@ -1,3 +1,63 @@
1
+ ## [0.1.12] - 2021-04-03
2
+ - Intermediate version: `Loxxy` does instance creation (default constructor)
3
+
4
+ ### New
5
+ - Method `BackEnd::LoxClass#call` made class callable (for invoking the constructor)
6
+ - Class `BackEnd::LoxInstance` runtime representation of a Lox instance (object).
7
+
8
+ ### Changed
9
+ - Method `BackEnd::Engine#after_call_expr` Added Lox class as callable thing.
10
+
11
+ ### Fixed
12
+ - Method `Ast::ASTBuilder#reduce_class_body` couldn't handle properly empty classes
13
+
14
+ ## [0.1.11] - 2021-04-03
15
+ - Intermediate version: `Loxxy` does class declarations
16
+
17
+ ### New
18
+ - Class `Ast::LoxClassStmt` a syntax node that represents a class declaration
19
+ - Method `Ast::ASTBuilder#reduce_class_decl` creates a `LoxClassStmt` instance
20
+ - Method `Ast::ASTBuilder#reduce_class_name`
21
+ - Method `Ast::ASTBuilder#reduce_reduce_class_body` collect the methods of the class
22
+ - Method `Ast::ASTBuilder#reduce_method_plus_more` for dealing with methods
23
+ - Method `Ast::ASTBuilder#reduce_method_plus_end`
24
+ - Method `Ast::ASTVisitor#visit_class_stmt` for visiting an `Ast::LoxClassStmt` node
25
+ - Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
26
+ - Method `BackEnd::Engine#after_class_stmt`
27
+ - Method `BackEnd::Resolver#after_class_stmt`
28
+ - Method `BackEnd::Resolver#before_class_stmt`
29
+ - Class `BackEnd::LoxClass` runtime representation of a Lox class.
30
+
31
+ ### Changed
32
+ - File `grammar.rb` refactoring of class declaration syntax rules
33
+
34
+ ## [0.1.10] - 2021-03-31
35
+ - Flag return statements occurring outside functions as an error
36
+
37
+ ### Changed
38
+ - Class `BackEnd::Resolver` Added attribute `current_function` to know whether the visited parse node is located inside a function
39
+
40
+
41
+ ## [0.1.09] - 2021-03-28
42
+ - Fix and test suite for return statements
43
+
44
+ ### CHANGED
45
+ - `Loxxy` reports an error when a return statement occurs in top-level scope
46
+
47
+ ### Fixed
48
+ - A return without explicit value genrated an exception in some cases.
49
+
50
+ ## [0.1.08] - 2021-03-27
51
+ - `Loxxy` implements variable resolving and binding as described in Chapter 11 of "Crafting Interpreters" book.
52
+
53
+ ### New
54
+ - Class `BackEnd::Resolver` implements the variable resolution (whenever a variable is in use, locate the declaration of that variable)
55
+
56
+ ### CHANGED
57
+ - Class `Ast::Visitor` changes in some method signatures
58
+ - Class `BackEnd::Engine` new attribute `resolver` that points to a `BackEnd::Resolver` instance
59
+ - Class `BackEnd::Engine` several methods dealing with variables have been adapted to take the resolver into account.
60
+
1
61
  ## [0.1.07] - 2021-03-14
2
62
  - `Loxxy` now supports nested functions and closures
3
63
 
@@ -12,8 +72,6 @@
12
72
  - Method `Ast::AstBuilder#after_var_stmt` now takes into account the value from the top of stack
13
73
 
14
74
 
15
-
16
-
17
75
  ## [0.1.06] - 2021-03-06
18
76
  - Parameters/arguments checks in function declaration and call
19
77
 
@@ -85,9 +143,9 @@
85
143
  - Method `Ast::ASTVisitor#visit_fun_stmt` for visiting an `Ast::LoxFunStmt` node
86
144
  - Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
87
145
  - Method `BackEnd::Engine#after_fun_stmt`
88
- - Method `Backend::NativeFunction#call`
89
- - Method `Backend::NativeFunction#to_str`
90
- - Method `Backend::Function` implementation of a function object.
146
+ - Method `BackEnd::NativeFunction#call`
147
+ - Method `BackEnd::NativeFunction#to_str`
148
+ - Method `BackEnd::LoxFunction` runtime representation of a Lox function.
91
149
 
92
150
  ### Changed
93
151
  - Method `BackEnd::Engine#after_call_expr`
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ### What is loxxy?
6
6
  A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html ),
7
- a simple language used in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/ ).
7
+ a simple language defined in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/ ).
8
8
 
9
9
  ### Purpose of this project:
10
10
  - To deliver an open source example of a programming language fully implemented in Ruby
@@ -13,11 +13,9 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
13
13
  a Lox interpreter written in Lox.
14
14
 
15
15
  ### Current status
16
- The project is still in inception and the interpreter is being implemented...
17
- Currently it can execute all allowed __Lox__ expressions and statements except:
18
- - Classes and objects.
19
-
20
- These will be implemented soon.
16
+ The interpreter currently can execute all allowed __Lox__ expressions and statements except
17
+ object-oriented feaures (classes and objects).
18
+ The goal is to implement these missing features in Q2 2021.
21
19
 
22
20
 
23
21
  ## What's the fuss about Lox?
@@ -17,4 +17,5 @@ require_relative 'lox_print_stmt'
17
17
  require_relative 'lox_if_stmt'
18
18
  require_relative 'lox_for_stmt'
19
19
  require_relative 'lox_var_stmt'
20
+ require_relative 'lox_class_stmt'
20
21
  require_relative 'lox_seq_decl'
@@ -163,6 +163,31 @@ module Loxxy
163
163
  [theChildren[0]]
164
164
  end
165
165
 
166
+ # rule('classDecl' => 'CLASS classNaming class_body')
167
+ def reduce_class_decl(_production, _range, _tokens, theChildren)
168
+ Ast::LoxClassStmt.new(tokens[1].position, theChildren[1], theChildren[2])
169
+ end
170
+
171
+ # rule('classNaming' => 'IDENTIFIER')
172
+ def reduce_class_name(_production, _range, _tokens, theChildren)
173
+ theChildren[0].token.lexeme
174
+ end
175
+
176
+ # rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE')
177
+ def reduce_class_body(_production, _range, _tokens, theChildren)
178
+ theChildren[1].nil? ? [] : theChildren[1]
179
+ end
180
+
181
+ # rule('method_plus' => 'method_plus function')
182
+ def reduce_method_plus_more(_production, _range, _tokens, theChildren)
183
+ theChildren[0] << theChildren[1]
184
+ end
185
+
186
+ # rule('method_plus' => 'function')
187
+ def reduce_method_plus_end(_production, _range, _tokens, theChildren)
188
+ theChildren
189
+ end
190
+
166
191
  # rule('funDecl' => 'FUN function')
167
192
  def reduce_fun_decl(_production, _range, _tokens, theChildren)
168
193
  theChildren[1]
@@ -67,6 +67,14 @@ module Loxxy
67
67
  broadcast(:after_var_stmt, aVarStmt)
68
68
  end
69
69
 
70
+ # Visit event. The visitor is about to visit a class declaration.
71
+ # @param aXlassStmt [AST::LOXClassStmt] the for statement node to visit
72
+ def visit_class_stmt(aClassStmt)
73
+ broadcast(:before_class_stmt, aClassStmt)
74
+ traverse_subnodes(aClassStmt) # The methods are visited here...
75
+ broadcast(:after_class_stmt, aClassStmt, self)
76
+ end
77
+
70
78
  # Visit event. The visitor is about to visit a for statement.
71
79
  # @param aForStmt [AST::LOXForStmt] the for statement node to visit
72
80
  def visit_for_stmt(aForStmt)
@@ -120,7 +128,7 @@ module Loxxy
120
128
  def visit_assign_expr(anAssignExpr)
121
129
  broadcast(:before_assign_expr, anAssignExpr)
122
130
  traverse_subnodes(anAssignExpr)
123
- broadcast(:after_assign_expr, anAssignExpr)
131
+ broadcast(:after_assign_expr, anAssignExpr, self)
124
132
  end
125
133
 
126
134
  # Visit event. The visitor is about to visit a logical expression.
@@ -194,7 +202,7 @@ module Loxxy
194
202
  # Visit event. The visitor is about to visit a function statement node.
195
203
  # @param aFunStmt [AST::LoxFunStmt] function declaration to visit
196
204
  def visit_fun_stmt(aFunStmt)
197
- broadcast(:before_fun_stmt, aFunStmt)
205
+ broadcast(:before_fun_stmt, aFunStmt, self)
198
206
  traverse_subnodes(aFunStmt)
199
207
  broadcast(:after_fun_stmt, aFunStmt, self)
200
208
  end
@@ -20,8 +20,6 @@ module Loxxy
20
20
  def accept(visitor)
21
21
  visitor.visit_block_stmt(self)
22
22
  end
23
-
24
- alias operands subnodes
25
23
  end # class
26
24
  end # module
27
25
  end # module
@@ -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 LoxClassStmt < LoxCompoundExpr
8
+ attr_reader :name
9
+
10
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
11
+ # @param condExpr [Loxxy::Ast::LoxNode] iteration condition
12
+ # @param theBody [Loxxy::Ast::LoxNode]
13
+ def initialize(aPosition, aName, theMethods)
14
+ super(aPosition, theMethods)
15
+ @name = aName.dup
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_class_stmt(self)
22
+ end
23
+
24
+ alias body subnodes
25
+ end # class
26
+ end # module
27
+ end # module
@@ -16,7 +16,7 @@ module Loxxy
16
16
  # @param body [Ast::LoxBlockStmt]
17
17
  def initialize(aPosition, aName, paramList, aBody)
18
18
  super(aPosition, [])
19
- @name = aName
19
+ @name = aName.dup
20
20
  @params = paramList
21
21
  @body = aBody
22
22
  end
@@ -26,8 +26,6 @@ module Loxxy
26
26
  def accept(visitor)
27
27
  visitor.visit_fun_stmt(self)
28
28
  end
29
-
30
- alias operands subnodes
31
29
  end # class
32
30
  # rubocop: enable Style/AccessorGrouping
33
31
  end # module
@@ -16,8 +16,6 @@ module Loxxy
16
16
  def accept(visitor)
17
17
  visitor.visit_grouping_expr(self)
18
18
  end
19
-
20
- alias operands subnodes
21
19
  end # class
22
20
  end # module
23
21
  end # module
@@ -8,7 +8,8 @@ module Loxxy
8
8
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
9
9
  # @param anExpression [Ast::LoxNode] expression to return
10
10
  def initialize(aPosition, anExpression)
11
- super(aPosition, [anExpression])
11
+ expr = anExpression || Datatype::Nil.instance
12
+ super(aPosition, [expr])
12
13
  end
13
14
 
14
15
  # Part of the 'visitee' role in Visitor design pattern.
@@ -10,8 +10,6 @@ module Loxxy
10
10
  def accept(visitor)
11
11
  visitor.visit_seq_decl(self)
12
12
  end
13
-
14
- alias operands subnodes
15
13
  end # class
16
14
  end # module
17
15
  end # module
@@ -13,8 +13,8 @@ module Loxxy
13
13
  # @param aName [String] name of the variable
14
14
  # @param aValue [Loxxy::Ast::LoxNode, NilClass] initial value for the variable
15
15
  def initialize(aPosition, aName, aValue)
16
- initial_value = aValue ? [aValue] : [Datatype::Nil.instance]
17
- super(aPosition, initial_value)
16
+ initial_value = aValue || Datatype::Nil.instance
17
+ super(aPosition, [initial_value])
18
18
  @name = aName
19
19
  end
20
20
 
@@ -3,7 +3,9 @@
3
3
  # Load all the classes implementing AST nodes
4
4
  require_relative '../ast/all_lox_nodes'
5
5
  require_relative 'binary_operator'
6
+ require_relative 'lox_class'
6
7
  require_relative 'lox_function'
8
+ require_relative 'resolver'
7
9
  require_relative 'symbol_table'
8
10
  require_relative 'unary_operator'
9
11
 
@@ -11,7 +13,6 @@ module Loxxy
11
13
  module BackEnd
12
14
  # An instance of this class executes the statements as when they
13
15
  # occur during the abstract syntax tree walking.
14
- # @note WIP: very crude implementation.
15
16
  class Engine
16
17
  # @return [Hash] A set of configuration options
17
18
  attr_reader :config
@@ -28,6 +29,9 @@ module Loxxy
28
29
  # @return [Hash { Symbol => BinaryOperator}]
29
30
  attr_reader :binary_operators
30
31
 
32
+ # @return [BackEnd::Resolver]
33
+ attr_reader :resolver
34
+
31
35
  # @param theOptions [Hash]
32
36
  def initialize(theOptions)
33
37
  @config = theOptions
@@ -47,6 +51,10 @@ module Loxxy
47
51
  # @param aVisitor [AST::ASTVisitor]
48
52
  # @return [Loxxy::Datatype::BuiltinDatatype]
49
53
  def execute(aVisitor)
54
+ # Do variable resolution pass first
55
+ @resolver = BackEnd::Resolver.new
56
+ resolver.analyze(aVisitor)
57
+
50
58
  aVisitor.subscribe(self)
51
59
  aVisitor.start
52
60
  aVisitor.unsubscribe(self)
@@ -61,11 +69,26 @@ module Loxxy
61
69
  # Do nothing, subnodes were already evaluated
62
70
  end
63
71
 
64
- def after_var_stmt(aVarStmt)
65
- new_var = Variable.new(aVarStmt.name, stack.pop)
72
+ def after_class_stmt(aClassStmt, _visitor)
73
+ klass = LoxClass.new(aClassStmt.name, aClassStmt.methods, self)
74
+ new_var = Variable.new(aClassStmt.name, klass)
75
+ symbol_table.insert(new_var)
76
+ end
77
+
78
+ def before_var_stmt(aVarStmt)
79
+ new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
66
80
  symbol_table.insert(new_var)
67
81
  end
68
82
 
83
+ def after_var_stmt(aVarStmt)
84
+ var_name = aVarStmt.name
85
+ variable = symbol_table.lookup(var_name)
86
+ raise StandardError, "Unknown variable #{var_name}" unless variable
87
+
88
+ value = stack.pop
89
+ variable.assign(value)
90
+ end
91
+
69
92
  def before_for_stmt(aForStmt)
70
93
  before_block_stmt(aForStmt)
71
94
  end
@@ -121,9 +144,9 @@ module Loxxy
121
144
  symbol_table.leave_environment
122
145
  end
123
146
 
124
- def after_assign_expr(anAssignExpr)
147
+ def after_assign_expr(anAssignExpr, _visitor)
125
148
  var_name = anAssignExpr.name
126
- variable = symbol_table.lookup(var_name)
149
+ variable = variable_lookup(anAssignExpr)
127
150
  raise StandardError, "Unknown variable #{var_name}" unless variable
128
151
 
129
152
  value = stack.last # ToS remains since an assignment produces a value
@@ -205,6 +228,8 @@ module Loxxy
205
228
  raise Loxxy::RuntimeError, msg
206
229
  end
207
230
  callee.call(self, aVisitor)
231
+ when LoxClass
232
+ callee.call(self, aVisitor)
208
233
  else
209
234
  raise Loxxy::RuntimeError, 'Can only call functions and classes.'
210
235
  end
@@ -216,10 +241,10 @@ module Loxxy
216
241
 
217
242
  def after_variable_expr(aVarExpr, aVisitor)
218
243
  var_name = aVarExpr.name
219
- var = symbol_table.lookup(var_name)
244
+ var = variable_lookup(aVarExpr)
220
245
  raise StandardError, "Unknown variable #{var_name}" unless var
221
246
 
222
- var.value.accept(aVisitor) # Evaluate the variable value
247
+ var.value.accept(aVisitor) # Evaluate variable value then push on stack
223
248
  end
224
249
 
225
250
  # @param literalExpr [Ast::LoxLiteralExpr]
@@ -240,6 +265,19 @@ module Loxxy
240
265
 
241
266
  private
242
267
 
268
+ def variable_lookup(aVarNode)
269
+ env = nil
270
+ offset = resolver.locals[aVarNode]
271
+ if offset.nil?
272
+ env = symbol_table.root
273
+ else
274
+ env = symbol_table.current_env
275
+ offset.times { env = env.enclosing }
276
+ end
277
+
278
+ env.defns[aVarNode.name]
279
+ end
280
+
243
281
  NativeFunction = Struct.new(:callable, :interp) do
244
282
  def accept(_visitor)
245
283
  interp.stack.push self
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../datatype/all_datatypes'
4
+ require_relative 'lox_instance'
5
+
6
+ module Loxxy
7
+ module BackEnd
8
+ # Runtime representation of a Lox class.
9
+ class LoxClass
10
+ # @return [String] The name of the class
11
+ attr_reader :name
12
+
13
+ # @return [Array<>] the list of methods
14
+ attr_reader :methods
15
+ attr_reader :stack
16
+
17
+ # Create a class with given name
18
+ # @param aName [String] The name of the class
19
+ def initialize(aName, theMethods, anEngine)
20
+ @name = aName.dup
21
+ @methods = theMethods
22
+ @stack = anEngine.stack
23
+ end
24
+
25
+ def accept(_visitor)
26
+ stack.push self
27
+ end
28
+
29
+ def arity
30
+ 0
31
+ end
32
+
33
+ def call(engine, _visitor)
34
+ instance = LoxInstance.new(self, engine)
35
+ engine.stack.push(instance)
36
+ end
37
+
38
+ # Logical negation.
39
+ # As a function is a truthy thing, its negation is thus false.
40
+ # @return [Datatype::False]
41
+ def !
42
+ Datatype::False.instance
43
+ end
44
+
45
+ # Text representation of a Lox class
46
+ def to_str
47
+ name
48
+ end
49
+ end # class
50
+ end # module
51
+ end # module
@@ -6,7 +6,6 @@ module Loxxy
6
6
  module BackEnd
7
7
  # rubocop: disable Style/AccessorGrouping
8
8
  # Representation of a Lox function.
9
- # It is a named slot that can be associated with a value at the time.
10
9
  class LoxFunction
11
10
  # @return [String] The name of the function (if any)
12
11
  attr_reader :name
@@ -18,7 +17,7 @@ module Loxxy
18
17
  attr_reader :closure
19
18
 
20
19
  # Create a function with given name
21
- # @param aName [String] The name of the variable
20
+ # @param aName [String] The name of the function
22
21
  def initialize(aName, parameterList, aBody, anEngine)
23
22
  @name = aName.dup
24
23
  @parameters = parameterList
@@ -37,7 +36,6 @@ module Loxxy
37
36
  end
38
37
 
39
38
  def call(engine, aVisitor)
40
- # new_env = Environment.new(engine.symbol_table.current_env)
41
39
  new_env = Environment.new(closure)
42
40
  engine.symbol_table.enter_environment(new_env)
43
41
 
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../datatype/all_datatypes'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # Runtime representation of a Lox object (instance).
8
+ class LoxInstance
9
+ # @return BackEnd::LoxClass] the class this this object belong
10
+ attr_reader :klass
11
+
12
+ attr_reader :stack
13
+
14
+ # Create an instance from given class
15
+ # @param aClass [BackEnd::LoxClass] the class this this object belong
16
+ def initialize(aClass, anEngine)
17
+ @klass = aClass
18
+ @stack = anEngine.stack
19
+ end
20
+
21
+ def accept(_visitor)
22
+ stack.push self
23
+ end
24
+
25
+ # Text representation of a Lox instance
26
+ def to_str
27
+ "#{klass.to_str} instance"
28
+ end
29
+ end # class
30
+ end # module
31
+ end # module
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load all the classes implementing AST nodes
4
+ require_relative '../ast/all_lox_nodes'
5
+ require_relative 'binary_operator'
6
+ require_relative 'lox_function'
7
+ require_relative 'symbol_table'
8
+ require_relative 'unary_operator'
9
+
10
+ module Loxxy
11
+ module BackEnd
12
+ # A class aimed to perform variable resolution when it visits the parse tree.
13
+ # Resolving means retrieve the declaration of a variable/function everywhere it
14
+ # is referenced.
15
+ class Resolver
16
+ # A stack of Hashes of the form String => Boolean
17
+ # @return [Array<Hash{String => Boolean}>]
18
+ attr_reader :scopes
19
+
20
+ # A map from a LoxNode involving a variable and the number of enclosing scopes
21
+ # where it is declared.
22
+ # @return [Hash {LoxNode => Integer}]
23
+ attr_reader :locals
24
+
25
+ # An indicator that tells we're in the middle of a function declaration
26
+ # @return [Symbol] must be one of: :none, :function
27
+ attr_reader :current_function
28
+
29
+ def initialize
30
+ @scopes = []
31
+ @locals = {}
32
+ @current_function = :none
33
+ end
34
+
35
+ # Given an abstract syntax parse tree visitor, launch the visit
36
+ # and execute the visit events in the output stream.
37
+ # @param aVisitor [AST::ASTVisitor]
38
+ # @return [Loxxy::Datatype::BuiltinDatatype]
39
+ def analyze(aVisitor)
40
+ begin_scope
41
+ aVisitor.subscribe(self)
42
+ aVisitor.start
43
+ aVisitor.unsubscribe(self)
44
+ end_scope
45
+ end
46
+
47
+ # block statement introduces a new scope
48
+ def before_block_stmt(_aBlockStmt)
49
+ begin_scope
50
+ end
51
+
52
+ def after_block_stmt(_aBlockStmt)
53
+ end_scope
54
+ end
55
+
56
+ # A class declaration adds a new variable to current scope
57
+ def before_class_stmt(aClassStmt)
58
+ declare(aClassStmt.name)
59
+ end
60
+
61
+ def after_class_stmt(aClassStmt, _visitor)
62
+ define(aClassStmt.name)
63
+ end
64
+
65
+ def before_for_stmt(aForStmt)
66
+ before_block_stmt(aForStmt)
67
+ end
68
+
69
+ def after_for_stmt(aForStmt, aVisitor)
70
+ aForStmt.test_expr.accept(aVisitor)
71
+ aForStmt.body_stmt.accept(aVisitor)
72
+ aForStmt.update_expr&.accept(aVisitor)
73
+ after_block_stmt(aForStmt)
74
+ end
75
+
76
+ def after_if_stmt(anIfStmt, aVisitor)
77
+ anIfStmt.then_stmt.accept(aVisitor)
78
+ anIfStmt.else_stmt&.accept(aVisitor)
79
+ end
80
+
81
+ def before_return_stmt(_returnStmt)
82
+ if scopes.size < 2
83
+ msg = "Error at 'return': Can't return from top-level code."
84
+ raise StandardError, msg
85
+ end
86
+
87
+ if current_function == :none
88
+ msg = "Error at 'return': Can't return from outside a function."
89
+ raise StandardError, msg
90
+ end
91
+ end
92
+
93
+ def after_while_stmt(aWhileStmt, aVisitor)
94
+ aWhileStmt.body.accept(aVisitor)
95
+ aWhileStmt.condition.accept(aVisitor)
96
+ end
97
+
98
+ # A variable declaration adds a new variable to current scope
99
+ def before_var_stmt(aVarStmt)
100
+ declare(aVarStmt.name)
101
+ end
102
+
103
+ def after_var_stmt(aVarStmt)
104
+ define(aVarStmt.name)
105
+ end
106
+
107
+ # Assignment expressions require their variables resolved
108
+ def after_assign_expr(anAssignExpr, aVisitor)
109
+ resolve_local(anAssignExpr, aVisitor)
110
+ end
111
+
112
+ # Variable expressions require their variables resolved
113
+ def before_variable_expr(aVarExpr)
114
+ var_name = aVarExpr.name
115
+ if !scopes.empty? && (scopes.last[var_name] == false)
116
+ raise StandardError, "Can't read variable #{var_name} in its own initializer"
117
+ end
118
+ end
119
+
120
+ def after_variable_expr(aVarExpr, aVisitor)
121
+ resolve_local(aVarExpr, aVisitor)
122
+ end
123
+
124
+ def after_call_expr(aCallExpr, aVisitor)
125
+ # Evaluate callee part
126
+ aCallExpr.callee.accept(aVisitor)
127
+ aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
128
+ end
129
+
130
+ # function declaration creates a new scope for its body & binds its parameters for that scope
131
+ def before_fun_stmt(aFunStmt, aVisitor)
132
+ declare(aFunStmt.name)
133
+ define(aFunStmt.name)
134
+ resolve_function(aFunStmt, :function, aVisitor)
135
+ end
136
+
137
+ private
138
+
139
+ def begin_scope
140
+ scopes.push({})
141
+ end
142
+
143
+ def end_scope
144
+ scopes.pop
145
+ end
146
+
147
+ def declare(aVarName)
148
+ return if scopes.empty?
149
+
150
+ curr_scope = scopes.last
151
+ if curr_scope.include?(aVarName)
152
+ msg = "Error at '#{aVarName}': Already variable with this name in this scope."
153
+ raise StandardError, msg
154
+ end
155
+
156
+ # The initializer is not yet processed.
157
+ # Mark the variable as 'not yet ready' = exists but may not be referenced yet
158
+ curr_scope[aVarName] = false
159
+ end
160
+
161
+ def define(aVarName)
162
+ return if scopes.empty?
163
+
164
+ curr_scope = scopes.last
165
+
166
+ # The initializer (if any) was processed.
167
+ # Mark the variable as alive (= can be referenced in an expression)
168
+ curr_scope[aVarName] = true
169
+ end
170
+
171
+ def resolve_local(aVarExpr, _visitor)
172
+ max_i = i = scopes.size - 1
173
+ scopes.reverse_each do |scp|
174
+ if scp.include?(aVarExpr.name)
175
+ # Keep track of the difference of nesting levels between current scope
176
+ # and the scope where the variable is declared
177
+ @locals[aVarExpr] = max_i - i
178
+ break
179
+ end
180
+ i -= 1
181
+ end
182
+ end
183
+
184
+ def resolve_function(aFunStmt, funVisitState, aVisitor)
185
+ enclosing_function = current_function
186
+ @current_function = funVisitState
187
+ begin_scope
188
+
189
+ aFunStmt.params&.each do |param_name|
190
+ declare(param_name)
191
+ define(param_name)
192
+ end
193
+
194
+ body = aFunStmt.body
195
+ unless body.nil? || body.kind_of?(Ast::LoxNoopExpr)
196
+ body.subnodes.first&.accept(aVisitor)
197
+ end
198
+
199
+ end_scope
200
+ @current_function = enclosing_function
201
+ end
202
+ end # class
203
+ end # mmodule
204
+ end # module
@@ -37,12 +37,14 @@ module Loxxy
37
37
  rule('declaration' => 'varDecl')
38
38
  rule('declaration' => 'statement')
39
39
 
40
- rule('classDecl' => 'CLASS classNaming class_body')
40
+ rule('classDecl' => 'CLASS classNaming class_body').as 'class_decl'
41
41
  rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
42
- rule('classNaming' => 'IDENTIFIER')
43
- rule('class_body' => 'LEFT_BRACE function_star RIGHT_BRACE')
44
- rule('function_star' => 'function_star function')
45
- rule('function_star' => [])
42
+ rule('classNaming' => 'IDENTIFIER').as 'class_name'
43
+ rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE').as 'class_body'
44
+ rule('methods_opt' => 'method_plus')
45
+ rule('methods_opt' => [])
46
+ rule('method_plus' => 'method_plus function').as 'method_plus_more'
47
+ rule('method_plus' => 'function').as 'method_plus_end'
46
48
 
47
49
  rule('funDecl' => 'FUN function').as 'fun_decl'
48
50
 
data/lib/loxxy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.1.07'
4
+ VERSION = '0.1.12'
5
5
  end
@@ -34,10 +34,18 @@ module Loxxy
34
34
  let(:var_decl) { Ast::LoxVarStmt.new(sample_pos, 'greeting', greeting) }
35
35
  let(:lit_expr) { Ast::LoxLiteralExpr.new(sample_pos, greeting) }
36
36
 
37
+ it "should react to 'before_var_stmt' event" do
38
+ expect { subject.before_var_stmt(var_decl) }.not_to raise_error
39
+ current_env = subject.symbol_table.current_env
40
+ expect(current_env.defns['greeting']).to be_kind_of(Variable)
41
+ end
37
42
 
38
43
  it "should react to 'after_var_stmt' event" do
44
+ # Precondition: `before_var_stmt` is called...
45
+ expect { subject.before_var_stmt(var_decl) }.not_to raise_error
39
46
  # Precondition: value to assign is on top of stack
40
47
  subject.stack.push(greeting)
48
+
41
49
  expect { subject.after_var_stmt(var_decl) }.not_to raise_error
42
50
  current_env = subject.symbol_table.current_env
43
51
  expect(current_env.defns['greeting']).to be_kind_of(Variable)
@@ -451,6 +451,37 @@ LOX_END
451
451
  end
452
452
  # rubocop: enable Style/StringConcatenation
453
453
 
454
+ it 'should support class declaration' do
455
+ program = <<-LOX_END
456
+ class Duck {
457
+ noise() {
458
+ quack();
459
+ }
460
+
461
+ quack() {
462
+ print "quack";
463
+ }
464
+ }
465
+ print Duck;
466
+ LOX_END
467
+ expect { subject.evaluate(program) }.not_to raise_error
468
+ expect(sample_cfg[:ostream].string).to eq('Duck')
469
+ end
470
+
471
+ it 'should support instance creation' do
472
+ program = <<-LOX_END
473
+ class Duck {
474
+ quack() {
475
+ print "quack";
476
+ }
477
+ }
478
+ var daffy = Duck();
479
+ print daffy;
480
+ LOX_END
481
+ expect { subject.evaluate(program) }.not_to raise_error
482
+ expect(sample_cfg[:ostream].string).to eq('Duck instance')
483
+ end
484
+
454
485
  it 'should print the hello world message' do
455
486
  program = <<-LOX_END
456
487
  var greeting = "Hello"; // Declaring a variable
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loxxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.07
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-14 00:00:00.000000000 Z
11
+ date: 2021-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -93,6 +93,7 @@ files:
93
93
  - lib/loxxy/ast/lox_binary_expr.rb
94
94
  - lib/loxxy/ast/lox_block_stmt.rb
95
95
  - lib/loxxy/ast/lox_call_expr.rb
96
+ - lib/loxxy/ast/lox_class_stmt.rb
96
97
  - lib/loxxy/ast/lox_compound_expr.rb
97
98
  - lib/loxxy/ast/lox_for_stmt.rb
98
99
  - lib/loxxy/ast/lox_fun_stmt.rb
@@ -113,7 +114,10 @@ files:
113
114
  - lib/loxxy/back_end/engine.rb
114
115
  - lib/loxxy/back_end/entry.rb
115
116
  - lib/loxxy/back_end/environment.rb
117
+ - lib/loxxy/back_end/lox_class.rb
116
118
  - lib/loxxy/back_end/lox_function.rb
119
+ - lib/loxxy/back_end/lox_instance.rb
120
+ - lib/loxxy/back_end/resolver.rb
117
121
  - lib/loxxy/back_end/symbol_table.rb
118
122
  - lib/loxxy/back_end/unary_operator.rb
119
123
  - lib/loxxy/back_end/variable.rb