loxxy 0.1.08 → 0.1.13

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: 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