loxxy 0.1.11 → 0.1.16

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: aee9fb6c5101bc39682ab401ffc0434aea9e6c4115f74a4f641d0df2d68a03ad
4
- data.tar.gz: ccc11c428ef206db9129fabbb97526df3477e03c5dce0b1b5a2f758425c8f9cb
3
+ metadata.gz: 60e57ef9264b4239571378ddcbefe044ff63192534ce94fc99b9a3b1c1813f02
4
+ data.tar.gz: 94c26f57455d0e267354f68655e86288dd00f5c322df3e4d1b6f5478cad0eb17
5
5
  SHA512:
6
- metadata.gz: dbb1a28eb85868636b304aa3ec3c2fb71f3f37f5c573740bd7d577ed4e88374ce1fea5446e39429168228e68bedbddfccd554bc6c88ec7996621956e11a42a3c
7
- data.tar.gz: 3b4c1d6b0996a351cfd7dae5d8f80683fcbdd1eeecc17c3a7932b0675285700dfecdabecc31f505243ffaf2f1bbc1bd8babb604c154f53dad0019c4ad96b007a
6
+ metadata.gz: 8e5d6f864c1a340aecc7a362d8f2c311dc553869fc72b40d287736df2b62715eb208b2b299f1106dba5645dc4bb859f1d96fbfa79441c83c0e5faa223dd6642d
7
+ data.tar.gz: 452f9815e8812d9313c228697410a4c7433ec12c58269da305999f1de2822c89ad0b9a0f4e72865c333ddbee725b0ff102b4bc14ab4c51dcf7dba2784a08074a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,79 @@
1
+ ## [0.1.16] - 2021-04-10
2
+ - Fixed an issue in name lookup. All the `this` test suite is passing.
3
+
4
+ ### Changed
5
+ - Method `BackEnd::Engine#after_var_stmt` now it creates the variable and pouts it in the symbol table
6
+
7
+ ### Removed
8
+ - Method `BackEnd::Engine#before_var_stmt` it generated bug when assigning a value to a var, when that var name occurred elsewhere
9
+
10
+ ## [0.1.15] - 2021-04-08
11
+ - Fixed the `dangling else`by tweaking the grammar rules
12
+
13
+ ### Changed
14
+ - Method `Ast::ASTBuilder#reduce_if__else_stmt` parse action specific for if with else branch
15
+
16
+ ### Fixed
17
+ - File `grammar.rb` changed rules to cope with `dangling else` issue
18
+
19
+ ### Changed
20
+ - Method `Ast::ASTBuilder#reduce_if_stmt` parse action for if without else branch
21
+ - File `README.md` removed the section about the `dangling else` issue.
22
+
23
+
24
+ ## [0.1.14] - 2021-04-05
25
+ - `Loxxy` now implements the 'this' keyword
26
+
27
+ ### New
28
+ - Class `Ast::LoxThisExpr` a syntax node that represents an occurrence of the `this` keyword
29
+ - Method `Ast::ASTBuilder#reduce_this_expr` parse action for this keyword
30
+ - Method `Ast::Visitor#visit_this_expr` visit of an `Ast::LoxThisExpr` node
31
+ - Method `BackEnd::Engine#after_this_expr` runtime action for this keyword (i.e. refers to current instance)
32
+ - Method `BackEnd::LoxFunction#bind` implementation of bound method
33
+
34
+ ### Changed
35
+ - Class `BackEnd::Resolver` implementing semantic actions for `this` keyword
36
+ - File `grammar.rb` added name to a syntax rule
37
+
38
+ ## [0.1.13] - 2021-04-05
39
+ - `Loxxy` now implements method calls
40
+
41
+ ### New
42
+ - Class `Ast::LoxGetExpr` a syntax node that represents a read access to an object property
43
+ - Class `Ast::LoxSetExpr` a syntax node that represents a write access to an object property
44
+ - Method `Ast::ASTBuilder#reduce_set_expr` parse action for write access to an object property
45
+ - Method `Ast::ASTBuilder#reduce_get_expr` parse action for read access to an object property
46
+ - Method `Ast::Visitor#visit_set_expr` visit of an `Ast::LoxSetExpr` node
47
+ - Method `Ast::Visitor#visit_get_expr` visit of an `Ast::LoxGetExpr` node
48
+ - Method `BackEnd::Engine#after_set_expr` runtime action for property setting
49
+ - Method `BackEnd::Engine#after_get_expr` runtime action for property getting
50
+ - Method `BackEnd::LoxInstance#set` implementation of write accessor
51
+ - Method `BackEnd::LoxInstance#get` implementation of read accessor
52
+ - Method `BackEnd::Resolver#after_set_expr` resolve action for property setting
53
+ - Method `BackEnd::Resolver#after_get_expr` resolve action for property getting
54
+
55
+ ### Changed
56
+ - Method `Ast::ASTBuilder#reduce_assign_expr` expanded to support write access to an object property
57
+ - Class `LoxClassStmt`: methods are now aggregate under the `body` attribute
58
+ - Class `LoxFunStmt`: has a new attribute `is_method` and inherits from `Ast::LoxNode`
59
+ - Method `BackEnd::Engine#after_class_stmt` methods are aggregated into the classes
60
+ - Method `BackEnd::Engine#after_fun_stmt` extension for method
61
+ - File `grammar.rb` added names to two syntax rules
62
+
63
+
64
+ ## [0.1.12] - 2021-04-03
65
+ - Intermediate version: `Loxxy` does instance creation (default constructor)
66
+
67
+ ### New
68
+ - Method `BackEnd::LoxClass#call` made class callable (for invoking the constructor)
69
+ - Class `BackEnd::LoxInstance` runtime representation of a Lox instance (object).
70
+
71
+ ### Changed
72
+ - Method `BackEnd::Engine#after_call_expr` Added Lox class as callable thing.
73
+
74
+ ### Fixed
75
+ - Method `Ast::ASTBuilder#reduce_class_body` couldn't handle properly empty classes
76
+
1
77
  ## [0.1.11] - 2021-04-03
2
78
  - Intermediate version: `Loxxy` does class declarations
3
79
 
@@ -13,9 +89,9 @@
13
89
  - Method `BackEnd::Engine#after_class_stmt`
14
90
  - Method `BackEnd::Resolver#after_class_stmt`
15
91
  - Method `BackEnd::Resolver#before_class_stmt`
16
- - Class `BackEnd::LoxClass` implementation of a Lox class.
92
+ - Class `BackEnd::LoxClass` runtime representation of a Lox class.
17
93
 
18
- ### CHANGED
94
+ ### Changed
19
95
  - File `grammar.rb` refactoring of class declaration syntax rules
20
96
 
21
97
  ## [0.1.10] - 2021-03-31
@@ -28,7 +104,7 @@
28
104
  ## [0.1.09] - 2021-03-28
29
105
  - Fix and test suite for return statements
30
106
 
31
- ### Changed
107
+ ### CHANGED
32
108
  - `Loxxy` reports an error when a return statement occurs in top-level scope
33
109
 
34
110
  ### Fixed
@@ -40,7 +116,7 @@
40
116
  ### New
41
117
  - Class `BackEnd::Resolver` implements the variable resolution (whenever a variable is in use, locate the declaration of that variable)
42
118
 
43
- ### Changed
119
+ ### CHANGED
44
120
  - Class `Ast::Visitor` changes in some method signatures
45
121
  - Class `BackEnd::Engine` new attribute `resolver` that points to a `BackEnd::Resolver` instance
46
122
  - Class `BackEnd::Engine` several methods dealing with variables have been adapted to take the resolver into account.
@@ -132,7 +208,7 @@
132
208
  - Method `BackEnd::Engine#after_fun_stmt`
133
209
  - Method `BackEnd::NativeFunction#call`
134
210
  - Method `BackEnd::NativeFunction#to_str`
135
- - Method `BackEnd::LoxFunction` implementation of a function object.
211
+ - Method `BackEnd::LoxFunction` runtime representation of a Lox function.
136
212
 
137
213
  ### Changed
138
214
  - Method `BackEnd::Engine#after_call_expr`
data/README.md CHANGED
@@ -177,8 +177,8 @@ Loxxy supports single line C-style comments.
177
177
  ### Keywords
178
178
  Loxxy implements the following __Lox__ reserved keywords:
179
179
  ```lang-none
180
- and, else, false, for, fun, if,
181
- nil, or, print, true, var, while
180
+ and, class, else, false, for, fun, if,
181
+ nil, or, print, return, this, true, var, while
182
182
  ```
183
183
 
184
184
  ### Datatypes
@@ -331,30 +331,6 @@ print "else-branch";
331
331
  ```
332
332
 
333
333
  As for other languages, the `else` part is optional.
334
- ##### Warning: nested `if`...`else`
335
- Call it a bug ... Nested `if` `else` control flow structure aren't yet supported by __Loxxy__.
336
- The culprit has a name: [the dangling else](https://en.wikipedia.org/wiki/Dangling_else).
337
-
338
- The problem in a nutshell: in a nested if ... else ... statement like this:
339
- ``` javascript
340
- 'if (true) if (false) print "bad"; else print "good";
341
- ```
342
- ... there is an ambiguity.
343
- Indeed, according to the __Lox__ grammar, the `else` could be bound
344
- either to the first `if` or to the second one.
345
- This ambiguity is usually lifted by applying an ad-hoc rule: an `else` is aways bound to the most
346
- recent (rightmost) `if`.
347
- Being a generic parsing library, `Rley` doesn't apply any of these supplemental rules.
348
- As a consequence,it complains about the found ambiguity and stops the parsing...
349
- Although `Rley` can cope with ambiguities, this requires the use of an advanced data structure
350
- called `Shared Packed Parse Forest (SPPF)`.
351
- SPPF are much more complex to handle than the "common" parse trees present in most compiler or interpreter books.
352
- Therefore, a future version of `Rley` will incorporate the capability to define disambuiguation rules.
353
-
354
- In the meantime, the `Loxxy` will progress on other __Lox__ features like:
355
- - Block structures...
356
- - Iteration structures (`for` and `while` loops)
357
-
358
334
 
359
335
  #### Print Statement
360
336
 
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'lox_fun_stmt'
4
+ require_relative 'lox_this_expr'
4
5
  require_relative 'lox_variable_expr'
5
6
  require_relative 'lox_literal_expr'
6
7
  require_relative 'lox_noop_expr'
8
+ require_relative 'lox_get_expr'
7
9
  require_relative 'lox_call_expr'
8
10
  require_relative 'lox_grouping_expr'
9
11
  require_relative 'lox_unary_expr'
10
12
  require_relative 'lox_binary_expr'
11
13
  require_relative 'lox_logical_expr'
14
+ require_relative 'lox_set_expr'
12
15
  require_relative 'lox_assign_expr'
13
16
  require_relative 'lox_block_stmt'
14
17
  require_relative 'lox_while_stmt'
@@ -175,7 +175,7 @@ module Loxxy
175
175
 
176
176
  # rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE')
177
177
  def reduce_class_body(_production, _range, _tokens, theChildren)
178
- theChildren[1]
178
+ theChildren[1].nil? ? [] : theChildren[1]
179
179
  end
180
180
 
181
181
  # rule('method_plus' => 'method_plus function')
@@ -233,11 +233,20 @@ module Loxxy
233
233
  return_first_child(range, tokens, theChildren)
234
234
  end
235
235
 
236
- # rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
236
+ # rule('ifStmt' => 'IF ifCondition statement ELSE statement')
237
+ # rule('unbalancedStmt' => 'IF ifCondition statement ELSE unbalancedStmt')
238
+ def reduce_if_else_stmt(_production, _range, tokens, theChildren)
239
+ condition = theChildren[1]
240
+ then_stmt = theChildren[2]
241
+ else_stmt = theChildren[4]
242
+ LoxIfStmt.new(tokens[0].position, condition, then_stmt, else_stmt)
243
+ end
244
+
245
+ # rule('unbalancedStmt' => 'IF ifCondition stmt').as ''
237
246
  def reduce_if_stmt(_production, _range, tokens, theChildren)
238
247
  condition = theChildren[1]
239
248
  then_stmt = theChildren[2]
240
- else_stmt = theChildren[3]
249
+ else_stmt = nil
241
250
  LoxIfStmt.new(tokens[0].position, condition, then_stmt, else_stmt)
242
251
  end
243
252
 
@@ -269,10 +278,22 @@ module Loxxy
269
278
 
270
279
  # rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
271
280
  def reduce_assign_expr(_production, _range, tokens, theChildren)
272
- var_name = theChildren[1].token.lexeme.dup
273
- Ast::LoxAssignExpr.new(tokens[1].position, var_name, theChildren[3])
281
+ name_assignee = theChildren[1].token.lexeme.dup
282
+ if theChildren[0].kind_of?(Ast::LoxSetExpr)
283
+ theChildren[0].property = name_assignee
284
+ theChildren[0].subnodes << theChildren[3]
285
+ theChildren[0]
286
+ else
287
+ Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
288
+ end
289
+ end
290
+
291
+ # rule('owner_opt' => 'call DOT')
292
+ def reduce_set_expr(_production, _range, tokens, theChildren)
293
+ Ast::LoxSetExpr.new(tokens[1].position, theChildren[0])
274
294
  end
275
295
 
296
+
276
297
  # rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
277
298
  # TODO: is it meaningful to implement this rule?
278
299
 
@@ -317,6 +338,11 @@ module Loxxy
317
338
  LoxCallExpr.new(tokens[0].position, args)
318
339
  end
319
340
 
341
+ # rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
342
+ def reduce_get_expr(_production, _range, tokens, theChildren)
343
+ LoxGetExpr.new(tokens[0].position, theChildren[1].token.lexeme)
344
+ end
345
+
320
346
  # rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
321
347
  def reduce_grouping_expr(_production, _range, tokens, theChildren)
322
348
  subexpr = theChildren[1]
@@ -337,6 +363,11 @@ module Loxxy
337
363
  LoxVariableExpr.new(tokens[0].position, var_name)
338
364
  end
339
365
 
366
+ # rule('primary' => 'THIS')
367
+ def reduce_this_expr(_production, _range, tokens, _children)
368
+ LoxThisExpr.new(tokens[0].position)
369
+ end
370
+
340
371
  # rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
341
372
  def reduce_function(_production, _range, _tokens, theChildren)
342
373
  first_child = theChildren.first
@@ -68,7 +68,7 @@ module Loxxy
68
68
  end
69
69
 
70
70
  # Visit event. The visitor is about to visit a class declaration.
71
- # @param aXlassStmt [AST::LOXClassStmt] the for statement node to visit
71
+ # @param aClassStmt [AST::LOXClassStmt] the for statement node to visit
72
72
  def visit_class_stmt(aClassStmt)
73
73
  broadcast(:before_class_stmt, aClassStmt)
74
74
  traverse_subnodes(aClassStmt) # The methods are visited here...
@@ -131,6 +131,13 @@ module Loxxy
131
131
  broadcast(:after_assign_expr, anAssignExpr, self)
132
132
  end
133
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
+
134
141
  # Visit event. The visitor is about to visit a logical expression.
135
142
  # Since logical expressions may take shorcuts by not evaluating all their
136
143
  # sub-expressiosns, they are responsible for visiting or not their children.
@@ -169,6 +176,12 @@ module Loxxy
169
176
  broadcast(:after_call_expr, aCallExpr, self)
170
177
  end
171
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
+
172
185
  # Visit event. The visitor is about to visit a grouping expression.
173
186
  # @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
174
187
  def visit_grouping_expr(aGroupingExpr)
@@ -192,6 +205,13 @@ module Loxxy
192
205
  broadcast(:after_variable_expr, aVariableExpr, self)
193
206
  end
194
207
 
208
+ # Visit event. The visitor is about to visit the this keyword.
209
+ # @param aThisExpr [Ast::LoxThisExpr] this expression
210
+ def visit_this_expr(aThisExpr)
211
+ broadcast(:before_this_expr, aThisExpr)
212
+ broadcast(:after_this_expr, aThisExpr, self)
213
+ end
214
+
195
215
  # Visit event. The visitor is about to visit the given terminal datatype value.
196
216
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
197
217
  def visit_builtin(aValue)
@@ -203,7 +223,6 @@ module Loxxy
203
223
  # @param aFunStmt [AST::LoxFunStmt] function declaration to visit
204
224
  def visit_fun_stmt(aFunStmt)
205
225
  broadcast(:before_fun_stmt, aFunStmt, self)
206
- traverse_subnodes(aFunStmt)
207
226
  broadcast(:after_fun_stmt, aFunStmt, self)
208
227
  end
209
228
 
@@ -5,14 +5,19 @@ require_relative 'lox_compound_expr'
5
5
  module Loxxy
6
6
  module Ast
7
7
  class LoxClassStmt < LoxCompoundExpr
8
+ # @return [String] the class name
8
9
  attr_reader :name
9
10
 
11
+ # @return [Array<Ast::LoxFunStmt>] the methods
12
+ attr_reader :body
13
+
10
14
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
11
15
  # @param condExpr [Loxxy::Ast::LoxNode] iteration condition
12
- # @param theBody [Loxxy::Ast::LoxNode]
16
+ # @param theBody [Array<Loxxy::Ast::LoxNode>]
13
17
  def initialize(aPosition, aName, theMethods)
14
- super(aPosition, theMethods)
18
+ super(aPosition, [])
15
19
  @name = aName.dup
20
+ @body = theMethods
16
21
  end
17
22
 
18
23
  # Part of the 'visitee' role in Visitor design pattern.
@@ -20,8 +25,6 @@ module Loxxy
20
25
  def accept(visitor)
21
26
  visitor.visit_class_stmt(self)
22
27
  end
23
-
24
- alias body subnodes
25
28
  end # class
26
29
  end # module
27
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
+ super(aPosition)
19
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
@@ -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
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_node'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ # A node in a parse tree that represents the occurrence of 'this' keyword.
8
+ class LoxThisExpr < LoxNode
9
+ # Duck-typing: behaves like a LoxVarExpr
10
+ # @return [String] return the this keyword
11
+ def name
12
+ 'this'
13
+ end
14
+
15
+ # Part of the 'visitee' role in Visitor design pattern.
16
+ # @param _visitor [LoxxyTreeVisitor] the visitor
17
+ def accept(aVisitor)
18
+ aVisitor.visit_this_expr(self)
19
+ end
20
+ end # class
21
+ end # module
22
+ end # module
@@ -69,24 +69,25 @@ module Loxxy
69
69
  # Do nothing, subnodes were already evaluated
70
70
  end
71
71
 
72
- def after_class_stmt(aClassStmt, _visitor)
73
- klass = LoxClass.new(aClassStmt.name, aClassStmt.methods, self)
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)
74
81
  new_var = Variable.new(aClassStmt.name, klass)
75
82
  symbol_table.insert(new_var)
76
83
  end
77
84
 
78
- def before_var_stmt(aVarStmt)
85
+ def after_var_stmt(aVarStmt)
79
86
  new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
80
87
  symbol_table.insert(new_var)
81
- end
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
 
88
89
  value = stack.pop
89
- variable.assign(value)
90
+ new_var.assign(value)
90
91
  end
91
92
 
92
93
  def before_for_stmt(aForStmt)
@@ -153,6 +154,18 @@ module Loxxy
153
154
  variable.assign(value)
154
155
  end
155
156
 
157
+ def after_set_expr(aSetExpr, aVisitor)
158
+ value = stack.pop
159
+ # Evaluate object part
160
+ aSetExpr.object.accept(aVisitor)
161
+ assignee = stack.pop
162
+ unless assignee.kind_of?(LoxInstance)
163
+ raise StandardError, 'Only instances have fields.'
164
+ end
165
+
166
+ assignee.set(aSetExpr.property, value)
167
+ end
168
+
156
169
  def after_logical_expr(aLogicalExpr, visitor)
157
170
  op = aLogicalExpr.operator
158
171
  operand1 = stack.pop # only first operand was evaluated
@@ -228,11 +241,23 @@ module Loxxy
228
241
  raise Loxxy::RuntimeError, msg
229
242
  end
230
243
  callee.call(self, aVisitor)
244
+ when LoxClass
245
+ callee.call(self, aVisitor)
231
246
  else
232
247
  raise Loxxy::RuntimeError, 'Can only call functions and classes.'
233
248
  end
234
249
  end
235
250
 
251
+ def after_get_expr(aGetExpr, aVisitor)
252
+ aGetExpr.object.accept(aVisitor)
253
+ instance = stack.pop
254
+ unless instance.kind_of?(LoxInstance)
255
+ raise StandardError, 'Only instances have properties.'
256
+ end
257
+
258
+ stack.push instance.get(aGetExpr.property)
259
+ end
260
+
236
261
  def after_grouping_expr(_groupingExpr)
237
262
  # Do nothing: work was already done by visiting /evaluating the subexpression
238
263
  end
@@ -240,7 +265,7 @@ module Loxxy
240
265
  def after_variable_expr(aVarExpr, aVisitor)
241
266
  var_name = aVarExpr.name
242
267
  var = variable_lookup(aVarExpr)
243
- raise StandardError, "Unknown variable #{var_name}" unless var
268
+ raise StandardError, "Undefined variable '#{var_name}'." unless var
244
269
 
245
270
  var.value.accept(aVisitor) # Evaluate variable value then push on stack
246
271
  end
@@ -250,6 +275,11 @@ module Loxxy
250
275
  stack.push(literalExpr.literal)
251
276
  end
252
277
 
278
+ def after_this_expr(aThisExpr, aVisitor)
279
+ var = variable_lookup(aThisExpr)
280
+ var.value.accept(aVisitor) # Evaluate this value then push on stack
281
+ end
282
+
253
283
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
254
284
  def before_visit_builtin(aValue)
255
285
  stack.push(aValue)
@@ -257,8 +287,12 @@ module Loxxy
257
287
 
258
288
  def after_fun_stmt(aFunStmt, _visitor)
259
289
  function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
260
- new_var = Variable.new(aFunStmt.name, function)
261
- symbol_table.insert(new_var)
290
+ if aFunStmt.is_method
291
+ stack.push function
292
+ else
293
+ new_var = Variable.new(aFunStmt.name, function)
294
+ symbol_table.insert(new_var)
295
+ end
262
296
  end
263
297
 
264
298
  private
@@ -37,7 +37,6 @@ module Loxxy
37
37
  # @return [BackEnd::Variable] the variable
38
38
  def insert(anEntry)
39
39
  e = validated_entry(anEntry)
40
- # e.suffix = default_suffix if e.kind_of?(BackEnd::Variable)
41
40
  defns[e.name] = e
42
41
 
43
42
  e
@@ -1,23 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../datatype/all_datatypes'
4
+ require_relative 'lox_instance'
4
5
 
5
6
  module Loxxy
6
7
  module BackEnd
7
- # Representation of a Lox class.
8
+ # Runtime representation of a Lox class.
8
9
  class LoxClass
9
10
  # @return [String] The name of the class
10
11
  attr_reader :name
11
12
 
12
- # @return [Array<>] the list of methods
13
- attr_reader :methods
13
+ # @return [Hash{String => LoxFunction}] the list of methods
14
+ attr_reader :meths
14
15
  attr_reader :stack
15
16
 
16
17
  # Create a class with given name
17
18
  # @param aName [String] The name of the class
18
19
  def initialize(aName, theMethods, anEngine)
19
20
  @name = aName.dup
20
- @methods = theMethods
21
+ @meths = {}
22
+ theMethods.each do |func|
23
+ meths[func.name] = func
24
+ end
21
25
  @stack = anEngine.stack
22
26
  end
23
27
 
@@ -25,6 +29,20 @@ module Loxxy
25
29
  stack.push self
26
30
  end
27
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
+
28
46
  # Logical negation.
29
47
  # As a function is a truthy thing, its negation is thus false.
30
48
  # @return [Datatype::False]
@@ -32,7 +50,7 @@ module Loxxy
32
50
  Datatype::False.instance
33
51
  end
34
52
 
35
- # Text representation of a Lox function
53
+ # Text representation of a Lox class
36
54
  def to_str
37
55
  name
38
56
  end
@@ -36,7 +36,6 @@ module Loxxy
36
36
  end
37
37
 
38
38
  def call(engine, aVisitor)
39
- # new_env = Environment.new(engine.symbol_table.current_env)
40
39
  new_env = Environment.new(closure)
41
40
  engine.symbol_table.enter_environment(new_env)
42
41
 
@@ -53,6 +52,16 @@ module Loxxy
53
52
  engine.symbol_table.leave_environment
54
53
  end
55
54
 
55
+ def bind(anInstance)
56
+ new_env = Environment.new(closure)
57
+ this = Variable.new('this', anInstance)
58
+ new_env.insert(this)
59
+ bound_method = dup
60
+ bound_method.instance_variable_set(:@closure, new_env)
61
+
62
+ bound_method
63
+ end
64
+
56
65
  # Logical negation.
57
66
  # As a function is a truthy thing, its negation is thus false.
58
67
  # @return [Datatype::False]
@@ -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 :engine
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
+ @engine = anEngine
22
+ @fields = {}
23
+ end
24
+
25
+ def accept(_visitor)
26
+ engine.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.bind(self)
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
@@ -26,10 +26,15 @@ module Loxxy
26
26
  # @return [Symbol] must be one of: :none, :function
27
27
  attr_reader :current_function
28
28
 
29
+ # An indicator that tells we're in the middle of a class declaration
30
+ # @return [Symbol] must be one of: :none, :class
31
+ attr_reader :current_class
32
+
29
33
  def initialize
30
34
  @scopes = []
31
35
  @locals = {}
32
36
  @current_function = :none
37
+ @current_class = :none
33
38
  end
34
39
 
35
40
  # Given an abstract syntax parse tree visitor, launch the visit
@@ -58,8 +63,17 @@ module Loxxy
58
63
  declare(aClassStmt.name)
59
64
  end
60
65
 
61
- def after_class_stmt(aClassStmt, _visitor)
66
+ def after_class_stmt(aClassStmt, aVisitor)
67
+ previous_class = current_class
68
+ @current_class = :class
62
69
  define(aClassStmt.name)
70
+ begin_scope
71
+ define('this')
72
+ aClassStmt.body.each do |fun_stmt|
73
+ resolve_function(fun_stmt, :method, aVisitor)
74
+ end
75
+ end_scope
76
+ @current_class = previous_class
63
77
  end
64
78
 
65
79
  def before_for_stmt(aForStmt)
@@ -109,6 +123,11 @@ module Loxxy
109
123
  resolve_local(anAssignExpr, aVisitor)
110
124
  end
111
125
 
126
+ def after_set_expr(aSetExpr, aVisitor)
127
+ # Evaluate object part
128
+ aSetExpr.object.accept(aVisitor)
129
+ end
130
+
112
131
  # Variable expressions require their variables resolved
113
132
  def before_variable_expr(aVarExpr)
114
133
  var_name = aVarExpr.name
@@ -127,6 +146,23 @@ module Loxxy
127
146
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
128
147
  end
129
148
 
149
+ def after_get_expr(aGetExpr, aVisitor)
150
+ # Evaluate object part
151
+ aGetExpr.object.accept(aVisitor)
152
+ end
153
+
154
+ def before_this_expr(_thisExpr)
155
+ if current_class == :none
156
+ msg = "Error at 'this': Can't use 'this' outside of a class."
157
+ raise StandardError, msg
158
+ end
159
+ end
160
+
161
+ def after_this_expr(aThisExpr, aVisitor)
162
+ # 'this' behaves closely to a local variable
163
+ resolve_local(aThisExpr, aVisitor)
164
+ end
165
+
130
166
  # function declaration creates a new scope for its body & binds its parameters for that scope
131
167
  def before_fun_stmt(aFunStmt, aVisitor)
132
168
  declare(aFunStmt.name)
@@ -85,7 +85,7 @@ module Loxxy
85
85
  name2envs[name] = [current_env]
86
86
  end
87
87
 
88
- anEntry.name # anEntry.i_name
88
+ anEntry.name
89
89
  end
90
90
 
91
91
  # Search for the object with the given name
@@ -99,23 +99,6 @@ module Loxxy
99
99
  sc.defns[aName]
100
100
  end
101
101
 
102
- # Search for the object with the given i_name
103
- # @param anIName [String]
104
- # @return [BackEnd::Variable]
105
- # def lookup_i_name(anIName)
106
- # found = nil
107
- # environment = current_env
108
-
109
- # begin
110
- # found = environment.defns.values.find { |e| e.i_name == anIName }
111
- # break if found
112
-
113
- # environment = environment.parent
114
- # end while environment
115
-
116
- # found
117
- # end
118
-
119
102
  # Return all variables defined in the current .. root chain.
120
103
  # Variables are sorted top-down and left-to-right.
121
104
  def all_variables
@@ -35,7 +35,7 @@ module Loxxy
35
35
  rule('declaration' => 'classDecl')
36
36
  rule('declaration' => 'funDecl')
37
37
  rule('declaration' => 'varDecl')
38
- rule('declaration' => 'statement')
38
+ rule('declaration' => 'stmt')
39
39
 
40
40
  rule('classDecl' => 'CLASS classNaming class_body').as 'class_decl'
41
41
  rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
@@ -52,6 +52,8 @@ module Loxxy
52
52
  rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
53
53
 
54
54
  # Statements: produce side effects, but don't introduce bindings
55
+ rule('stmt' => 'statement')
56
+ rule('stmt' => 'unbalancedStmt') # Tweak to cope with "dangling else" problem
55
57
  rule('statement' => 'exprStmt')
56
58
  rule('statement' => 'forStmt')
57
59
  rule('statement' => 'ifStmt')
@@ -70,10 +72,10 @@ module Loxxy
70
72
  rule('forTest' => 'expression_opt SEMICOLON').as 'for_test'
71
73
  rule('forUpdate' => 'expression_opt')
72
74
 
73
- rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
75
+ rule('ifStmt' => 'IF ifCondition statement ELSE statement').as 'if_else_stmt'
76
+ rule('unbalancedStmt' => 'IF ifCondition stmt').as 'if_stmt'
77
+ rule('unbalancedStmt' => 'IF ifCondition statement ELSE unbalancedStmt').as 'if_else_stmt'
74
78
  rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
75
- rule('elsePart_opt' => 'ELSE statement').as 'keep_symbol2'
76
- rule('elsePart_opt' => [])
77
79
 
78
80
  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
79
81
  rule('returnStmt' => 'RETURN expression_opt SEMICOLON').as 'return_stmt'
@@ -87,7 +89,7 @@ module Loxxy
87
89
  rule('expression' => 'assignment')
88
90
  rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
89
91
  rule('assignment' => 'logic_or')
90
- rule('owner_opt' => 'call DOT')
92
+ rule('owner_opt' => 'call DOT').as 'set_expr'
91
93
  rule('owner_opt' => [])
92
94
  rule('logic_or' => 'logic_and')
93
95
  rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
@@ -132,11 +134,11 @@ module Loxxy
132
134
  rule('refinement_plus' => 'refinement_plus refinement').as 'refinement_plus_more'
133
135
  rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
134
136
  rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
135
- rule('refinement' => 'DOT IDENTIFIER')
137
+ rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
136
138
  rule('primary' => 'TRUE').as 'literal_expr'
137
139
  rule('primary' => 'FALSE').as 'literal_expr'
138
140
  rule('primary' => 'NIL').as 'literal_expr'
139
- rule('primary' => 'THIS')
141
+ rule('primary' => 'THIS').as 'this_expr'
140
142
  rule('primary' => 'NUMBER').as 'literal_expr'
141
143
  rule('primary' => 'STRING').as 'literal_expr'
142
144
  rule('primary' => 'IDENTIFIER').as 'variable_expr'
@@ -24,6 +24,15 @@ module Loxxy
24
24
  # @param lox_input [String] Lox program to evaluate
25
25
  # @return [Loxxy::Datatype::BuiltinDatatype]
26
26
  def evaluate(lox_input)
27
+ raw_evaluate(lox_input).first
28
+ end
29
+
30
+ # Evaluate the given Lox program.
31
+ # Return the pair [result, a BackEnd::Engine instance]
32
+ # where result is the value of the last executed expression (if any)
33
+ # @param lox_input [String] Lox program to evaluate
34
+ # @return Loxxy::Datatype::BuiltinDatatype, Loxxy::BackEnd::Engine]
35
+ def raw_evaluate(lox_input)
27
36
  # Front-end scans, parses the input and blurps an AST...
28
37
  parser = FrontEnd::Parser.new
29
38
 
@@ -34,7 +43,9 @@ module Loxxy
34
43
  # Back-end launches the tree walking & responds to visit events
35
44
  # by executing the code determined by the visited AST node.
36
45
  engine = BackEnd::Engine.new(config)
37
- engine.execute(visitor)
46
+ result = engine.execute(visitor)
47
+
48
+ [result, engine]
38
49
  end
39
50
  end # class
40
51
  end # module
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.11'
4
+ VERSION = '0.1.16'
5
5
  end
@@ -34,15 +34,7 @@ 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
42
-
43
37
  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
46
38
  # Precondition: value to assign is on top of stack
47
39
  subject.stack.push(greeting)
48
40
 
@@ -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
@@ -76,8 +76,11 @@ LOX_END
76
76
  expect(decls.symbol.name).to eq('declaration_plus')
77
77
  stmt = decls.subnodes[0].subnodes[0]
78
78
  expect(stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
79
- expect(stmt.symbol.name).to eq('statement')
80
- prnt_stmt = stmt.subnodes[0]
79
+ expect(stmt.symbol.name).to eq('stmt')
80
+ statement = stmt.subnodes[0]
81
+ expect(statement).to be_kind_of(Rley::PTree::NonTerminalNode)
82
+ expect(statement.symbol.name).to eq('statement')
83
+ prnt_stmt = statement.subnodes[0]
81
84
  expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
82
85
  expect(prnt_stmt.subnodes.size).to eq(3)
83
86
  expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
@@ -227,19 +227,20 @@ module Loxxy
227
227
  # Evaluate the 'then' expression if the condition is true.
228
228
  ['if (true) print "then-branch";', 'then-branch'],
229
229
  ['if (false) print "ignored";', ''],
230
- # TODO: test with then block body
231
- # TODO: test with assignment in if condition
230
+ ['if (nil) print "ignored";', ''],
231
+ ['if (true) { print "block"; }', 'block'],
232
+ ['var a = false; if (a = true) print a;', 'true'],
232
233
 
233
234
  # Evaluate the 'else' expression if the condition is false.
234
235
  ['if (true) print "then-branch"; else print "else-branch";', 'then-branch'],
235
236
  ['if (false) print "then-branch"; else print "else-branch";', 'else-branch'],
236
237
  ['if (0) print "then-branch"; else print "else-branch";', 'then-branch'],
237
- ['if (nil) print "then-branch"; else print "else-branch";', 'else-branch']
238
- # TODO: test with else block body
238
+ ['if (nil) print "then-branch"; else print "else-branch";', 'else-branch'],
239
+ ['if (false) nil; else { print "else-branch"; }', 'else-branch'],
239
240
 
240
- # TODO: A dangling else binds to the right-most if.
241
- # ['if (true) if (false) print "bad"; else print "good";', 'good'],
242
- # ['if (false) if (true) print "bad"; else print "worse";', 'bad']
241
+ # A dangling else binds to the right-most if.
242
+ ['if (true) if (false) print "bad"; else print "good";', 'good'],
243
+ ['if (false) if (true) print "bad"; else print "worse";', '']
243
244
  ].each do |(source, predicted)|
244
245
  io = StringIO.new
245
246
  cfg = { ostream: io }
@@ -451,30 +452,120 @@ LOX_END
451
452
  end
452
453
  # rubocop: enable Style/StringConcatenation
453
454
 
454
- it 'should support class declaration' do
455
+ it 'should print the hello world message' do
455
456
  program = <<-LOX_END
457
+ var greeting = "Hello"; // Declaring a variable
458
+ print greeting + ", " + "world!"; // ... Playing with concatenation
459
+ LOX_END
460
+ expect { subject.evaluate(program) }.not_to raise_error
461
+ expect(sample_cfg[:ostream].string).to eq('Hello, world!')
462
+ end
463
+ end # context
464
+
465
+ context 'Object orientation:' do
466
+ let(:duck_class) do
467
+ snippet = <<-LOX_END
456
468
  class Duck {
457
469
  noise() {
458
- quack();
470
+ this.quack();
459
471
  }
460
472
 
461
473
  quack() {
462
474
  print "quack";
463
475
  }
464
476
  }
465
- print Duck;
477
+ LOX_END
478
+
479
+ snippet
480
+ end
481
+
482
+ it 'should support class declaration' do
483
+ program = <<-LOX_END
484
+ #{duck_class}
485
+
486
+ print Duck; // Class names can appear in statements
466
487
  LOX_END
467
488
  expect { subject.evaluate(program) }.not_to raise_error
468
489
  expect(sample_cfg[:ostream].string).to eq('Duck')
469
490
  end
470
491
 
471
- it 'should print the hello world message' do
492
+ it 'should support default instance creation' do
472
493
  program = <<-LOX_END
473
- var greeting = "Hello"; // Declaring a variable
474
- print greeting + ", " + "world!"; // ... Playing with concatenation
494
+ #{duck_class}
495
+
496
+ var daffy = Duck(); // Default constructor
497
+ print daffy;
475
498
  LOX_END
476
499
  expect { subject.evaluate(program) }.not_to raise_error
477
- expect(sample_cfg[:ostream].string).to eq('Hello, world!')
500
+ expect(sample_cfg[:ostream].string).to eq('Duck instance')
501
+ end
502
+
503
+ it 'should support calls to method' do
504
+ program = <<-LOX_END
505
+ #{duck_class}
506
+
507
+ var daffy = Duck(); // Default constructor
508
+ daffy.quack();
509
+ LOX_END
510
+ expect { subject.evaluate(program) }.not_to raise_error
511
+ expect(sample_cfg[:ostream].string).to eq('quack')
512
+ end
513
+
514
+ it "should support the 'this' keyword" do
515
+ program = <<-LOX_END
516
+ class Egotist {
517
+ speak() {
518
+ print this;
519
+ }
520
+ }
521
+
522
+ var method = Egotist().speak;
523
+ method(); // Output: Egotist instance
524
+ LOX_END
525
+ expect { subject.evaluate(program) }.not_to raise_error
526
+ expect(sample_cfg[:ostream].string).to eq('Egotist instance')
527
+ end
528
+
529
+ it 'should support a closure nested in a method' do
530
+ lox_snippet = <<-LOX_END
531
+ class Foo {
532
+ getClosure() {
533
+ fun closure() {
534
+ return this.toString();
535
+ }
536
+ return closure;
537
+ }
538
+
539
+ toString() { return "foo"; }
540
+ }
541
+
542
+ var closure = Foo().getClosure();
543
+ closure;
544
+ LOX_END
545
+ # Expected result: Backend::LoxFunction('closure')
546
+ # Expected function's closure ()environment layout):
547
+ # Environment('global')
548
+ # defns
549
+ # +- ['clock'] => BackEnd::Engine::NativeFunction
550
+ # *- ['Foo'] => BackEnd::LoxClass
551
+ # Environment
552
+ # defns
553
+ # ['this'] => BackEnd::LoxInstance
554
+ # Environment
555
+ # defns
556
+ # +- ['closure'] => Backend::LoxFunction
557
+ result = subject.evaluate(lox_snippet)
558
+ expect(result).to be_kind_of(BackEnd::LoxFunction)
559
+ expect(result.name).to eq('closure')
560
+ closure = result.closure
561
+ expect(closure).to be_kind_of(Loxxy::BackEnd::Environment)
562
+ expect(closure.defns['closure'].value).to eq(result)
563
+ expect(closure.enclosing).to be_kind_of(Loxxy::BackEnd::Environment)
564
+ expect(closure.enclosing.defns['this'].value).to be_kind_of(Loxxy::BackEnd::LoxInstance)
565
+ global_env = closure.enclosing.enclosing
566
+ expect(global_env).to be_kind_of(Loxxy::BackEnd::Environment)
567
+ expect(global_env.defns['clock'].value).to be_kind_of(BackEnd::Engine::NativeFunction)
568
+ expect(global_env.defns['Foo'].value).to be_kind_of(BackEnd::LoxClass)
478
569
  end
479
570
  end # context
480
571
  end # describe
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.11
4
+ version: 0.1.16
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-04-03 00:00:00.000000000 Z
11
+ date: 2021-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -97,6 +97,7 @@ files:
97
97
  - lib/loxxy/ast/lox_compound_expr.rb
98
98
  - lib/loxxy/ast/lox_for_stmt.rb
99
99
  - lib/loxxy/ast/lox_fun_stmt.rb
100
+ - lib/loxxy/ast/lox_get_expr.rb
100
101
  - lib/loxxy/ast/lox_grouping_expr.rb
101
102
  - lib/loxxy/ast/lox_if_stmt.rb
102
103
  - lib/loxxy/ast/lox_literal_expr.rb
@@ -106,6 +107,8 @@ files:
106
107
  - lib/loxxy/ast/lox_print_stmt.rb
107
108
  - lib/loxxy/ast/lox_return_stmt.rb
108
109
  - lib/loxxy/ast/lox_seq_decl.rb
110
+ - lib/loxxy/ast/lox_set_expr.rb
111
+ - lib/loxxy/ast/lox_this_expr.rb
109
112
  - lib/loxxy/ast/lox_unary_expr.rb
110
113
  - lib/loxxy/ast/lox_var_stmt.rb
111
114
  - lib/loxxy/ast/lox_variable_expr.rb
@@ -116,6 +119,7 @@ files:
116
119
  - lib/loxxy/back_end/environment.rb
117
120
  - lib/loxxy/back_end/lox_class.rb
118
121
  - lib/loxxy/back_end/lox_function.rb
122
+ - lib/loxxy/back_end/lox_instance.rb
119
123
  - lib/loxxy/back_end/resolver.rb
120
124
  - lib/loxxy/back_end/symbol_table.rb
121
125
  - lib/loxxy/back_end/unary_operator.rb