loxxy 0.1.08 → 0.1.13

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
  SHA256:
3
- metadata.gz: 1724fce16b30fe2328ba31a725e92cc03f43ac7dee60fd54c68642c5a5639471
4
- data.tar.gz: 0f045add2ebf55375ee63e94f3a7316984588d0133200f3f4ce45de3ae118157
3
+ metadata.gz: 172ec5df8e553332b497968f7b910383cb38314aabbdb84fdb42fba6986841ea
4
+ data.tar.gz: 3d72fd4f13f507f88e9e735e1ebdd0d63fd603b771f9cfb552b9aa38b5b3ca6a
5
5
  SHA512:
6
- metadata.gz: 5e4a67161f2d6f6a89b237bdda4e1d096df1183eeb3a9b8ff4f1506d8b053bae265de73857faf82a225b77fad5d32faddfe07873d74e0e71a2839f113659c61b
7
- data.tar.gz: 29d09adcb56231c4f7b7c911c16b978e4ec3e0608f754e8b80b971293b99ea96fcf2bb6cd55dec62a8acac00bde0fb9b93e86cc37ee11024faea8e8eb0a70d3f
6
+ metadata.gz: 61073737ef6c069e6e1177fa79cdac543253e06ea63d2ca1b00517c08a06f46948fe7d51257308935d92579152793b4fe60bc0f5b901a1c931e5eafab9f35ecd
7
+ data.tar.gz: 57c87936451c3015511740d7d44f01d8f032a06e597bdfd1d133119413c227a61df11847f86a4be3a2b3440cc1f614a43b1144cceef05a58ade56ca1fa3c598b
data/CHANGELOG.md CHANGED
@@ -1,10 +1,85 @@
1
+ ## [0.1.13] - 2021-04-05
2
+ - `Loxxy` now implements method calls
3
+
4
+ ## New
5
+ - Class `Ast::LoxGetExpr` a syntax node that represents a read access to an object property
6
+ - Class `Ast::LoxSetExpr` a syntax node that represents a write access to an object property
7
+ - Method `Ast::ASTBuilder#reduce_set_expr` parse action for write access to an object property
8
+ - Method `Ast::ASTBuilder#reduce_get_expr` parse action for read access to an object property
9
+ - Method `Ast::Visitor#visit_set_expr` visit of an `Ast::LoxSetExpr` node
10
+ - Method `Ast::Visitor#visit_get_expr` visit of an `Ast::LoxGetExpr` node
11
+ - Method `BackEnd::Engine#after_set_expr` runtime action for property setting
12
+ - Method `BackEnd::Engine#after_get_expr` runtime action for property getting
13
+ - Method `BackEnd::LoxInstance#set` implementation of write accessor
14
+ - Method `BackEnd::LoxInstance#getr` implementation of read accessor
15
+ - Method `BackEnd::Resolver#after_set_expr` resolve action for property setting
16
+ - Method `BackEnd::Resolver#after_get_expr` resolve action for property getting
17
+
18
+ ## Changed
19
+ - Method `Ast::ASTBuilder#reduce_assign_expr` expanded to support write access to an object property
20
+ - Class `LoxClassStmt`: methods are now aggregate under the `body` attribute
21
+ - Class `LoxFunStmt`: has a new attribute `is_method` and inherits from `Ast::LoxNode`
22
+ - Method `BackEnd::Engine#after_class_stmt` methods are aggregated into the classes
23
+ - Method `BackEnd::Engine#after_fun_stmt` extension for method
24
+ - File `grammar.rb` added names to two syntax rules
25
+
26
+
27
+ ## [0.1.12] - 2021-04-03
28
+ - Intermediate version: `Loxxy` does instance creation (default constructor)
29
+
30
+ ### New
31
+ - Method `BackEnd::LoxClass#call` made class callable (for invoking the constructor)
32
+ - Class `BackEnd::LoxInstance` runtime representation of a Lox instance (object).
33
+
34
+ ### Changed
35
+ - Method `BackEnd::Engine#after_call_expr` Added Lox class as callable thing.
36
+
37
+ ### Fixed
38
+ - Method `Ast::ASTBuilder#reduce_class_body` couldn't handle properly empty classes
39
+
40
+ ## [0.1.11] - 2021-04-03
41
+ - Intermediate version: `Loxxy` does class declarations
42
+
43
+ ### New
44
+ - Class `Ast::LoxClassStmt` a syntax node that represents a class declaration
45
+ - Method `Ast::ASTBuilder#reduce_class_decl` creates a `LoxClassStmt` instance
46
+ - Method `Ast::ASTBuilder#reduce_class_name`
47
+ - Method `Ast::ASTBuilder#reduce_reduce_class_body` collect the methods of the class
48
+ - Method `Ast::ASTBuilder#reduce_method_plus_more` for dealing with methods
49
+ - Method `Ast::ASTBuilder#reduce_method_plus_end`
50
+ - Method `Ast::ASTVisitor#visit_class_stmt` for visiting an `Ast::LoxClassStmt` node
51
+ - Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
52
+ - Method `BackEnd::Engine#after_class_stmt`
53
+ - Method `BackEnd::Resolver#after_class_stmt`
54
+ - Method `BackEnd::Resolver#before_class_stmt`
55
+ - Class `BackEnd::LoxClass` runtime representation of a Lox class.
56
+
57
+ ### Changed
58
+ - File `grammar.rb` refactoring of class declaration syntax rules
59
+
60
+ ## [0.1.10] - 2021-03-31
61
+ - Flag return statements occurring outside functions as an error
62
+
63
+ ### Changed
64
+ - Class `BackEnd::Resolver` Added attribute `current_function` to know whether the visited parse node is located inside a function
65
+
66
+
67
+ ## [0.1.09] - 2021-03-28
68
+ - Fix and test suite for return statements
69
+
70
+ ### CHANGED
71
+ - `Loxxy` reports an error when a return statement occurs in top-level scope
72
+
73
+ ### Fixed
74
+ - A return without explicit value genrated an exception in some cases.
75
+
1
76
  ## [0.1.08] - 2021-03-27
2
77
  - `Loxxy` implements variable resolving and binding as described in Chapter 11 of "Crafting Interpreters" book.
3
78
 
4
79
  ### New
5
80
  - Class `BackEnd::Resolver` implements the variable resolution (whenever a variable is in use, locate the declaration of that variable)
6
81
 
7
- ### Changed
82
+ ### CHANGED
8
83
  - Class `Ast::Visitor` changes in some method signatures
9
84
  - Class `BackEnd::Engine` new attribute `resolver` that points to a `BackEnd::Resolver` instance
10
85
  - Class `BackEnd::Engine` several methods dealing with variables have been adapted to take the resolver into account.
@@ -94,9 +169,9 @@
94
169
  - Method `Ast::ASTVisitor#visit_fun_stmt` for visiting an `Ast::LoxFunStmt` node
95
170
  - Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
96
171
  - Method `BackEnd::Engine#after_fun_stmt`
97
- - Method `Backend::NativeFunction#call`
98
- - Method `Backend::NativeFunction#to_str`
99
- - Method `Backend::Function` implementation of a function object.
172
+ - Method `BackEnd::NativeFunction#call`
173
+ - Method `BackEnd::NativeFunction#to_str`
174
+ - Method `BackEnd::LoxFunction` runtime representation of a Lox function.
100
175
 
101
176
  ### Changed
102
177
  - Method `BackEnd::Engine#after_call_expr`
data/README.md CHANGED
@@ -13,10 +13,9 @@ a simple language defined in Bob Nystrom's online book [Crafting Interpreters](h
13
13
  a Lox interpreter written in Lox.
14
14
 
15
15
  ### Current status
16
- The interpreter currently it can execute all allowed __Lox__ expressions and statements except
16
+ The interpreter currently can execute all allowed __Lox__ expressions and statements except
17
17
  object-oriented feaures (classes and objects).
18
-
19
- Our intent is implement to these missing features in Q2 2021.
18
+ The goal is to implement these missing features in Q2 2021.
20
19
 
21
20
 
22
21
  ## What's the fuss about Lox?
@@ -4,11 +4,13 @@ require_relative 'lox_fun_stmt'
4
4
  require_relative 'lox_variable_expr'
5
5
  require_relative 'lox_literal_expr'
6
6
  require_relative 'lox_noop_expr'
7
+ require_relative 'lox_get_expr'
7
8
  require_relative 'lox_call_expr'
8
9
  require_relative 'lox_grouping_expr'
9
10
  require_relative 'lox_unary_expr'
10
11
  require_relative 'lox_binary_expr'
11
12
  require_relative 'lox_logical_expr'
13
+ require_relative 'lox_set_expr'
12
14
  require_relative 'lox_assign_expr'
13
15
  require_relative 'lox_block_stmt'
14
16
  require_relative 'lox_while_stmt'
@@ -17,4 +19,5 @@ require_relative 'lox_print_stmt'
17
19
  require_relative 'lox_if_stmt'
18
20
  require_relative 'lox_for_stmt'
19
21
  require_relative 'lox_var_stmt'
22
+ require_relative 'lox_class_stmt'
20
23
  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]
@@ -244,10 +269,22 @@ module Loxxy
244
269
 
245
270
  # rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
246
271
  def reduce_assign_expr(_production, _range, tokens, theChildren)
247
- var_name = theChildren[1].token.lexeme.dup
248
- Ast::LoxAssignExpr.new(tokens[1].position, var_name, theChildren[3])
272
+ name_assignee = theChildren[1].token.lexeme.dup
273
+ if theChildren[0].kind_of?(Ast::LoxSetExpr)
274
+ theChildren[0].property = name_assignee
275
+ theChildren[0].subnodes << theChildren[3]
276
+ theChildren[0]
277
+ else
278
+ Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
279
+ end
249
280
  end
250
281
 
282
+ # rule('owner_opt' => 'call DOT')
283
+ def reduce_set_expr(_production, _range, tokens, theChildren)
284
+ Ast::LoxSetExpr.new(tokens[1].position, theChildren[0])
285
+ end
286
+
287
+
251
288
  # rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
252
289
  # TODO: is it meaningful to implement this rule?
253
290
 
@@ -292,6 +329,11 @@ module Loxxy
292
329
  LoxCallExpr.new(tokens[0].position, args)
293
330
  end
294
331
 
332
+ # rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
333
+ def reduce_get_expr(_production, _range, tokens, theChildren)
334
+ LoxGetExpr.new(tokens[0].position, theChildren[1].token.lexeme)
335
+ end
336
+
295
337
  # rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
296
338
  def reduce_grouping_expr(_production, _range, tokens, theChildren)
297
339
  subexpr = 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 aClassStmt [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)
@@ -123,6 +131,13 @@ module Loxxy
123
131
  broadcast(:after_assign_expr, anAssignExpr, self)
124
132
  end
125
133
 
134
+ # @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
135
+ def visit_set_expr(aSetExpr)
136
+ broadcast(:before_set_expr, aSetExpr)
137
+ traverse_subnodes(aSetExpr)
138
+ broadcast(:after_set_expr, aSetExpr, self)
139
+ end
140
+
126
141
  # Visit event. The visitor is about to visit a logical expression.
127
142
  # Since logical expressions may take shorcuts by not evaluating all their
128
143
  # sub-expressiosns, they are responsible for visiting or not their children.
@@ -161,6 +176,12 @@ module Loxxy
161
176
  broadcast(:after_call_expr, aCallExpr, self)
162
177
  end
163
178
 
179
+ # @param aGetExpr [AST::LOXGetExpr] the get expression node to visit
180
+ def visit_get_expr(aGetExpr)
181
+ broadcast(:before_get_expr, aGetExpr)
182
+ broadcast(:after_get_expr, aGetExpr, self)
183
+ end
184
+
164
185
  # Visit event. The visitor is about to visit a grouping expression.
165
186
  # @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
166
187
  def visit_grouping_expr(aGroupingExpr)
@@ -195,7 +216,6 @@ module Loxxy
195
216
  # @param aFunStmt [AST::LoxFunStmt] function declaration to visit
196
217
  def visit_fun_stmt(aFunStmt)
197
218
  broadcast(:before_fun_stmt, aFunStmt, self)
198
- traverse_subnodes(aFunStmt)
199
219
  broadcast(:after_fun_stmt, aFunStmt, self)
200
220
  end
201
221
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxClassStmt < LoxCompoundExpr
8
+ # @return [String] the class name
9
+ attr_reader :name
10
+
11
+ # @return [Array<Ast::LoxFunStmt>] the methods
12
+ attr_reader :body
13
+
14
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
+ # @param condExpr [Loxxy::Ast::LoxNode] iteration condition
16
+ # @param theBody [Array<Loxxy::Ast::LoxNode>]
17
+ def initialize(aPosition, aName, theMethods)
18
+ super(aPosition, [])
19
+ @name = aName.dup
20
+ @body = theMethods
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_class_stmt(self)
27
+ end
28
+ end # class
29
+ end # module
30
+ end # module
@@ -1,24 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lox_compound_expr'
3
+ require_relative 'lox_node'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
7
  # rubocop: disable Style/AccessorGrouping
8
- class LoxFunStmt < LoxCompoundExpr
8
+ class LoxFunStmt < LoxNode
9
9
  attr_reader :name
10
10
  attr_reader :params
11
11
  attr_reader :body
12
+ attr_accessor :is_method
12
13
 
13
14
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
14
15
  # @param aName [String]
15
16
  # @param arguments [Array<String>]
16
17
  # @param body [Ast::LoxBlockStmt]
17
18
  def initialize(aPosition, aName, paramList, aBody)
18
- super(aPosition, [])
19
- @name = aName
19
+ super(aPosition)
20
+ @name = aName.dup
20
21
  @params = paramList
21
22
  @body = aBody
23
+ @is_method = false
22
24
  end
23
25
 
24
26
  # Part of the 'visitee' role in Visitor design pattern.
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_node'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxGetExpr < LoxNode
8
+ # @return [Ast::LoxNode] the object to which the property belongs to
9
+ attr_accessor :object
10
+
11
+ # @return [String] Name of an object property
12
+ attr_reader :property
13
+
14
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
+ # @param aPropertyName [String] Name of an object property
16
+ def initialize(aPosition, aPropertyName)
17
+ super(aPosition)
18
+ @property = aPropertyName
19
+ end
20
+
21
+ # Part of the 'visitee' role in Visitor design pattern.
22
+ # @param visitor [ASTVisitor] the visitor
23
+ def accept(visitor)
24
+ visitor.visit_get_expr(self)
25
+ end
26
+
27
+ alias callee= object=
28
+ end # class
29
+ end # module
30
+ 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.
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxSetExpr < LoxCompoundExpr
8
+ # @return [Ast::LoxNode] the object to which the property belongs to
9
+ attr_reader :object
10
+
11
+ # @return [String] Name of an object property
12
+ attr_accessor :property
13
+
14
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
+ # @param anObject [Ast::LoxNode] The object which the given property is being set
16
+ def initialize(aPosition, anObject)
17
+ super(aPosition, [])
18
+ @object = anObject
19
+ end
20
+
21
+ # Part of the 'visitee' role in Visitor design pattern.
22
+ # @param visitor [ASTVisitor] the visitor
23
+ def accept(visitor)
24
+ visitor.visit_set_expr(self)
25
+ end
26
+ end # class
27
+ end # module
28
+ end # module
@@ -3,6 +3,7 @@
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'
7
8
  require_relative 'resolver'
8
9
  require_relative 'symbol_table'
@@ -68,6 +69,19 @@ module Loxxy
68
69
  # Do nothing, subnodes were already evaluated
69
70
  end
70
71
 
72
+ def after_class_stmt(aClassStmt, aVisitor)
73
+ # Convert LoxFunStmt into LoxFunction
74
+ meths = aClassStmt.body.map do |func_node|
75
+ func_node.is_method = true
76
+ func_node.accept(aVisitor)
77
+ stack.pop
78
+ end
79
+
80
+ klass = LoxClass.new(aClassStmt.name, meths, self)
81
+ new_var = Variable.new(aClassStmt.name, klass)
82
+ symbol_table.insert(new_var)
83
+ end
84
+
71
85
  def before_var_stmt(aVarStmt)
72
86
  new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
73
87
  symbol_table.insert(new_var)
@@ -146,6 +160,18 @@ module Loxxy
146
160
  variable.assign(value)
147
161
  end
148
162
 
163
+ def after_set_expr(aSetExpr, aVisitor)
164
+ value = stack.pop
165
+ # Evaluate object part
166
+ aSetExpr.object.accept(aVisitor)
167
+ assignee = stack.pop
168
+ unless assignee.kind_of?(LoxInstance)
169
+ raise StandardError, 'Only instances have fields.'
170
+ end
171
+
172
+ assignee.set(aSetExpr.property, value)
173
+ end
174
+
149
175
  def after_logical_expr(aLogicalExpr, visitor)
150
176
  op = aLogicalExpr.operator
151
177
  operand1 = stack.pop # only first operand was evaluated
@@ -221,11 +247,23 @@ module Loxxy
221
247
  raise Loxxy::RuntimeError, msg
222
248
  end
223
249
  callee.call(self, aVisitor)
250
+ when LoxClass
251
+ callee.call(self, aVisitor)
224
252
  else
225
253
  raise Loxxy::RuntimeError, 'Can only call functions and classes.'
226
254
  end
227
255
  end
228
256
 
257
+ def after_get_expr(aGetExpr, aVisitor)
258
+ aGetExpr.object.accept(aVisitor)
259
+ instance = stack.pop
260
+ unless instance.kind_of?(LoxInstance)
261
+ raise StandardError, 'Only instances have properties.'
262
+ end
263
+
264
+ stack.push instance.get(aGetExpr.property)
265
+ end
266
+
229
267
  def after_grouping_expr(_groupingExpr)
230
268
  # Do nothing: work was already done by visiting /evaluating the subexpression
231
269
  end
@@ -233,7 +271,7 @@ module Loxxy
233
271
  def after_variable_expr(aVarExpr, aVisitor)
234
272
  var_name = aVarExpr.name
235
273
  var = variable_lookup(aVarExpr)
236
- raise StandardError, "Unknown variable #{var_name}" unless var
274
+ raise StandardError, "Undefined variable '#{var_name}'." unless var
237
275
 
238
276
  var.value.accept(aVisitor) # Evaluate variable value then push on stack
239
277
  end
@@ -250,8 +288,12 @@ module Loxxy
250
288
 
251
289
  def after_fun_stmt(aFunStmt, _visitor)
252
290
  function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
253
- new_var = Variable.new(aFunStmt.name, function)
254
- symbol_table.insert(new_var)
291
+ if aFunStmt.is_method
292
+ stack.push function
293
+ else
294
+ new_var = Variable.new(aFunStmt.name, function)
295
+ symbol_table.insert(new_var)
296
+ end
255
297
  end
256
298
 
257
299
  private
@@ -0,0 +1,59 @@
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 [Hash{String => LoxFunction}] the list of methods
14
+ attr_reader :meths
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
+ @meths = {}
22
+ theMethods.each do |func|
23
+ meths[func.name] = func
24
+ end
25
+ @stack = anEngine.stack
26
+ end
27
+
28
+ def accept(_visitor)
29
+ stack.push self
30
+ end
31
+
32
+ def arity
33
+ 0
34
+ end
35
+
36
+ def call(engine, _visitor)
37
+ instance = LoxInstance.new(self, engine)
38
+ engine.stack.push(instance)
39
+ end
40
+
41
+ # @param aName [String] the method name to search for
42
+ def find_method(aName)
43
+ meths[aName]
44
+ end
45
+
46
+ # Logical negation.
47
+ # As a function is a truthy thing, its negation is thus false.
48
+ # @return [Datatype::False]
49
+ def !
50
+ Datatype::False.instance
51
+ end
52
+
53
+ # Text representation of a Lox class
54
+ def to_str
55
+ name
56
+ end
57
+ end # class
58
+ end # module
59
+ 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,58 @@
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 that this object is an instance of
10
+ attr_reader :klass
11
+
12
+ attr_reader :stack
13
+
14
+ # @return [Hash{String => BuiltinDatatype | LoxFunction | LoxInstance }]
15
+ attr_reader :fields
16
+
17
+ # Create an instance from given class
18
+ # @param aClass [BackEnd::LoxClass] the class this this object belong
19
+ def initialize(aClass, anEngine)
20
+ @klass = aClass
21
+ @stack = anEngine.stack
22
+ @fields = {}
23
+ end
24
+
25
+ def accept(_visitor)
26
+ stack.push self
27
+ end
28
+
29
+ # Text representation of a Lox instance
30
+ def to_str
31
+ "#{klass.to_str} instance"
32
+ end
33
+
34
+ # Look up the value of property with given name
35
+ # aName [String] name of object property
36
+ def get(aName)
37
+ return fields[aName] if fields.include? aName
38
+
39
+ method = klass.find_method(aName)
40
+ unless method
41
+ raise StandardError, "Undefined property '#{aName}'."
42
+ end
43
+
44
+ method
45
+ end
46
+
47
+ # Set the value of property with given name
48
+ # aName [String] name of object property
49
+ def set(aName, aValue)
50
+ unless fields.include? aName
51
+ raise StandardError, "Undefined property '#{aName}'."
52
+ end
53
+
54
+ fields[aName] = aValue
55
+ end
56
+ end # class
57
+ end # module
58
+ end # module
@@ -22,9 +22,14 @@ module Loxxy
22
22
  # @return [Hash {LoxNode => Integer}]
23
23
  attr_reader :locals
24
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
+
25
29
  def initialize
26
30
  @scopes = []
27
31
  @locals = {}
32
+ @current_function = :none
28
33
  end
29
34
 
30
35
  # Given an abstract syntax parse tree visitor, launch the visit
@@ -48,6 +53,18 @@ module Loxxy
48
53
  end_scope
49
54
  end
50
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, aVisitor)
62
+ define(aClassStmt.name)
63
+ aClassStmt.body.each do |fun_stmt|
64
+ resolve_function(fun_stmt, :method, aVisitor)
65
+ end
66
+ end
67
+
51
68
  def before_for_stmt(aForStmt)
52
69
  before_block_stmt(aForStmt)
53
70
  end
@@ -64,6 +81,18 @@ module Loxxy
64
81
  anIfStmt.else_stmt&.accept(aVisitor)
65
82
  end
66
83
 
84
+ def before_return_stmt(_returnStmt)
85
+ if scopes.size < 2
86
+ msg = "Error at 'return': Can't return from top-level code."
87
+ raise StandardError, msg
88
+ end
89
+
90
+ if current_function == :none
91
+ msg = "Error at 'return': Can't return from outside a function."
92
+ raise StandardError, msg
93
+ end
94
+ end
95
+
67
96
  def after_while_stmt(aWhileStmt, aVisitor)
68
97
  aWhileStmt.body.accept(aVisitor)
69
98
  aWhileStmt.condition.accept(aVisitor)
@@ -83,6 +112,11 @@ module Loxxy
83
112
  resolve_local(anAssignExpr, aVisitor)
84
113
  end
85
114
 
115
+ def after_set_expr(aSetExpr, aVisitor)
116
+ # Evaluate object part
117
+ aSetExpr.object.accept(aVisitor)
118
+ end
119
+
86
120
  # Variable expressions require their variables resolved
87
121
  def before_variable_expr(aVarExpr)
88
122
  var_name = aVarExpr.name
@@ -101,11 +135,16 @@ module Loxxy
101
135
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
102
136
  end
103
137
 
138
+ def after_get_expr(aGetExpr, aVisitor)
139
+ # Evaluate object part
140
+ aGetExpr.object.accept(aVisitor)
141
+ end
142
+
104
143
  # function declaration creates a new scope for its body & binds its parameters for that scope
105
144
  def before_fun_stmt(aFunStmt, aVisitor)
106
145
  declare(aFunStmt.name)
107
146
  define(aFunStmt.name)
108
- resolve_function(aFunStmt, aVisitor)
147
+ resolve_function(aFunStmt, :function, aVisitor)
109
148
  end
110
149
 
111
150
  private
@@ -122,6 +161,10 @@ module Loxxy
122
161
  return if scopes.empty?
123
162
 
124
163
  curr_scope = scopes.last
164
+ if curr_scope.include?(aVarName)
165
+ msg = "Error at '#{aVarName}': Already variable with this name in this scope."
166
+ raise StandardError, msg
167
+ end
125
168
 
126
169
  # The initializer is not yet processed.
127
170
  # Mark the variable as 'not yet ready' = exists but may not be referenced yet
@@ -151,7 +194,9 @@ module Loxxy
151
194
  end
152
195
  end
153
196
 
154
- def resolve_function(aFunStmt, aVisitor)
197
+ def resolve_function(aFunStmt, funVisitState, aVisitor)
198
+ enclosing_function = current_function
199
+ @current_function = funVisitState
155
200
  begin_scope
156
201
 
157
202
  aFunStmt.params&.each do |param_name|
@@ -165,6 +210,7 @@ module Loxxy
165
210
  end
166
211
 
167
212
  end_scope
213
+ @current_function = enclosing_function
168
214
  end
169
215
  end # class
170
216
  end # mmodule
@@ -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
 
@@ -85,7 +87,7 @@ module Loxxy
85
87
  rule('expression' => 'assignment')
86
88
  rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
87
89
  rule('assignment' => 'logic_or')
88
- rule('owner_opt' => 'call DOT')
90
+ rule('owner_opt' => 'call DOT').as 'set_expr'
89
91
  rule('owner_opt' => [])
90
92
  rule('logic_or' => 'logic_and')
91
93
  rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
@@ -130,7 +132,7 @@ module Loxxy
130
132
  rule('refinement_plus' => 'refinement_plus refinement').as 'refinement_plus_more'
131
133
  rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
132
134
  rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
133
- rule('refinement' => 'DOT IDENTIFIER')
135
+ rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
134
136
  rule('primary' => 'TRUE').as 'literal_expr'
135
137
  rule('primary' => 'FALSE').as 'literal_expr'
136
138
  rule('primary' => 'NIL').as 'literal_expr'
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.08'
4
+ VERSION = '0.1.13'
5
5
  end
@@ -305,6 +305,83 @@ LOX_END
305
305
  expect(expr.operands[1].operands[1].literal.value).to eq(5)
306
306
  end
307
307
  end # context
308
+
309
+ context 'Object orientation:' do
310
+ it 'should parse object property get access' do
311
+ input = 'print someObject.someProperty;'
312
+ ptree = subject.parse(input)
313
+ expr = ptree.root.subnodes[0]
314
+ expect(expr).to be_kind_of(Ast::LoxPrintStmt)
315
+ get_expr = expr.subnodes[0]
316
+ expect(get_expr).to be_kind_of(Ast::LoxGetExpr)
317
+ expect(get_expr.object.name).to eq('someObject')
318
+ expect(get_expr.property).to eq('someProperty')
319
+ end
320
+
321
+ it 'should parse nested call expressions' do
322
+ input = 'print egg.scramble(3).with(cheddar);'
323
+ # From section 12.3.1, one expects something like:
324
+ # LoxCallExpr
325
+ # +- arguments = ['cheddar']
326
+ # +- callee = LoxGetExpr
327
+ # +- property = 'with'
328
+ # +- object = LoxCallExpr
329
+ # +- arguments = [3]
330
+ # +- callee = LoxGetExpr
331
+ # +- property = 'scramble'
332
+ # +- object = variable 'egg'
333
+ ptree = subject.parse(input)
334
+ print_stmt = ptree.root.subnodes[0]
335
+ expect(print_stmt).to be_kind_of(Ast::LoxPrintStmt)
336
+ outer_call = print_stmt.subnodes[0]
337
+ expect(outer_call).to be_kind_of(Ast::LoxCallExpr)
338
+ expect(outer_call.arguments[0].name).to eq('cheddar')
339
+ expect(outer_call.callee).to be_kind_of(Ast::LoxGetExpr)
340
+ expect(outer_call.callee.property).to eq('with')
341
+ inner_call = outer_call.callee.object
342
+ expect(inner_call).to be_kind_of(Ast::LoxCallExpr)
343
+ expect(inner_call.arguments[0].literal).to eq(3)
344
+ expect(inner_call.callee).to be_kind_of(Ast::LoxGetExpr)
345
+ expect(inner_call.callee.property).to eq('scramble')
346
+ expect(inner_call.callee.object.name).to eq('egg')
347
+ end
348
+
349
+ it 'should parse object property set access' do
350
+ input = 'someObject.someProperty = value;'
351
+ ptree = subject.parse(input)
352
+ expr = ptree.root.subnodes[0]
353
+ expect(expr).to be_kind_of(Ast::LoxSetExpr)
354
+ expect(expr.object.name).to eq('someObject')
355
+ expect(expr.property).to eq('someProperty')
356
+ expect(expr.subnodes[0]).to be_kind_of(Ast::LoxVariableExpr)
357
+ expect(expr.subnodes[0].name).to eq('value')
358
+ end
359
+
360
+ it 'should parse complex set access' do
361
+ input = 'breakfast.omelette.filling.meat = ham;'
362
+ # From section 12.3.2, one expects something like:
363
+ # LoxSetExpr
364
+ # +- property = 'meat'
365
+ # +- subnodes[0] = LoxVariableExpr 'ham'
366
+ # +- object = LoxGetExpr
367
+ # +- property = 'filling'
368
+ # +- object = LoxGetExpr
369
+ # +- property = 'omelette'
370
+ # +- object = LoxVariableExpr 'breakfast'
371
+ ptree = subject.parse(input)
372
+ expr = ptree.root.subnodes[0]
373
+ expect(expr).to be_kind_of(Ast::LoxSetExpr)
374
+ expect(expr.property).to eq('meat')
375
+ expect(expr.subnodes[0]).to be_kind_of(Ast::LoxVariableExpr)
376
+ expect(expr.subnodes[0].name).to eq('ham')
377
+ expect(expr.object).to be_kind_of(Ast::LoxGetExpr)
378
+ expect(expr.object.property).to eq('filling')
379
+ expect(expr.object.object).to be_kind_of(Ast::LoxGetExpr)
380
+ expect(expr.object.object.property).to eq('omelette')
381
+ expect(expr.object.object.object).to be_kind_of(Ast::LoxVariableExpr)
382
+ expect(expr.object.object.object.name).to eq('breakfast')
383
+ end
384
+ end # context
308
385
  end # describe
309
386
  end # module
310
387
  end # module
@@ -460,6 +460,56 @@ LOX_END
460
460
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')
461
461
  end
462
462
  end # context
463
+
464
+ context 'Object orientation:' do
465
+ let(:duck_class) do
466
+ snippet = <<-LOX_END
467
+ class Duck {
468
+ noise() {
469
+ quack();
470
+ }
471
+
472
+ quack() {
473
+ print "quack";
474
+ }
475
+ }
476
+ LOX_END
477
+
478
+ snippet
479
+ end
480
+
481
+ it 'should support class declaration' do
482
+ program = <<-LOX_END
483
+ #{duck_class}
484
+
485
+ print Duck; // Class names can appear in statements
486
+ LOX_END
487
+ expect { subject.evaluate(program) }.not_to raise_error
488
+ expect(sample_cfg[:ostream].string).to eq('Duck')
489
+ end
490
+
491
+ it 'should support default instance creation' do
492
+ program = <<-LOX_END
493
+ #{duck_class}
494
+
495
+ var daffy = Duck(); // Default constructor
496
+ print daffy;
497
+ LOX_END
498
+ expect { subject.evaluate(program) }.not_to raise_error
499
+ expect(sample_cfg[:ostream].string).to eq('Duck instance')
500
+ end
501
+
502
+ it 'should support calls to method' do
503
+ program = <<-LOX_END
504
+ #{duck_class}
505
+
506
+ var daffy = Duck(); // Default constructor
507
+ daffy.quack();
508
+ LOX_END
509
+ expect { subject.evaluate(program) }.not_to raise_error
510
+ expect(sample_cfg[:ostream].string).to eq('quack')
511
+ end
512
+ end # context
463
513
  end # describe
464
514
  # rubocop: enable Metrics/BlockLength
465
515
  end # module
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.08
4
+ version: 0.1.13
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-27 00:00:00.000000000 Z
11
+ date: 2021-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -93,9 +93,11 @@ 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
100
+ - lib/loxxy/ast/lox_get_expr.rb
99
101
  - lib/loxxy/ast/lox_grouping_expr.rb
100
102
  - lib/loxxy/ast/lox_if_stmt.rb
101
103
  - lib/loxxy/ast/lox_literal_expr.rb
@@ -105,6 +107,7 @@ files:
105
107
  - lib/loxxy/ast/lox_print_stmt.rb
106
108
  - lib/loxxy/ast/lox_return_stmt.rb
107
109
  - lib/loxxy/ast/lox_seq_decl.rb
110
+ - lib/loxxy/ast/lox_set_expr.rb
108
111
  - lib/loxxy/ast/lox_unary_expr.rb
109
112
  - lib/loxxy/ast/lox_var_stmt.rb
110
113
  - lib/loxxy/ast/lox_variable_expr.rb
@@ -113,7 +116,9 @@ files:
113
116
  - lib/loxxy/back_end/engine.rb
114
117
  - lib/loxxy/back_end/entry.rb
115
118
  - lib/loxxy/back_end/environment.rb
119
+ - lib/loxxy/back_end/lox_class.rb
116
120
  - lib/loxxy/back_end/lox_function.rb
121
+ - lib/loxxy/back_end/lox_instance.rb
117
122
  - lib/loxxy/back_end/resolver.rb
118
123
  - lib/loxxy/back_end/symbol_table.rb
119
124
  - lib/loxxy/back_end/unary_operator.rb