loxxy 0.1.10 → 0.1.15

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: 1fc5399ac3346487808eeb40403b3324cca384e3c6820d965b75f2e320759d2c
4
- data.tar.gz: f9b6497d87036f6c88ed09a72113a1ea004df9db8fd9bb945672c370993f7fde
3
+ metadata.gz: c3994a3225b7dbc4a39d24cc646c20e4a7a19b074e85fe2dd9bc87d9ca1d61cb
4
+ data.tar.gz: 965c328057f51fb7d13886255955e782e51b1f030f3053a6f5f6f4534962855b
5
5
  SHA512:
6
- metadata.gz: 0b0fa0313a0b37882d0398238a65f10b6ea8ab4325447e1f4690ea7100bdb85dead8cb15d8e1e56b422741697d636c7e2c114cf17ad43781dc23462ab51c93af
7
- data.tar.gz: b93b3d01a0e80def496ecfeb955e64000cc88149229ceb97e2743ccda863ec23677958aad9423dd0ca433d72451e120be6fa40247db7cbc49710ac8c17ea8987
6
+ metadata.gz: 9db05948e45f7903ca71d7d0e3722f3c9b1fd3f50931e17b6bb1666c34cf03e1dc7f6cf32abee461dd6e6ec6824cd89b8ef5b2b56ae41fcd40068228d8cbaba2
7
+ data.tar.gz: eaa762982bd89f2b4cb85e8d6831017e702b87501b7c29132f25f79fa592bf804ec95736f518d304f43caf814aa55eeadd5041ffcba1afa3aaaed631d23027af
data/CHANGELOG.md CHANGED
@@ -1,3 +1,90 @@
1
+ ## [0.1.15] - 2021-04-08
2
+ - Fixed the `dangling else`by tweaking the grammar rules
3
+
4
+ ### Changed
5
+ - Method `Ast::ASTBuilder#reduce_if__else_stmt` parse action specific for if with else branch
6
+
7
+ ### Fixed
8
+ - File `grammar.rb` changed rules to cope with `dangling else` issue
9
+
10
+ ### Changed
11
+ - Method `Ast::ASTBuilder#reduce_if_stmt` parse action for if without else branch
12
+ - File `README.md` removed the section about the `dangling else` issue.
13
+
14
+
15
+ ## [0.1.14] - 2021-04-05
16
+ - `Loxxy` now implements the 'this' keyword
17
+
18
+ ### New
19
+ - Class `Ast::LoxThisExpr` a syntax node that represents an occurrence of the `this` keyword
20
+ - Method `Ast::ASTBuilder#reduce_this_expr` parse action for this keyword
21
+ - Method `Ast::Visitor#visit_this_expr` visit of an `Ast::LoxThisExpr` node
22
+ - Method `BackEnd::Engine#after_this_expr` runtime action for this keyword (i.e. refers to current instance)
23
+ - Method `BackEnd::LoxFunction#bind` implementation of bound method
24
+
25
+ ### Changed
26
+ - Class `BackEnd::Resolver` implementing semantic actions for `this` keyword
27
+ - File `grammar.rb` added name to a syntax rule
28
+
29
+ ## [0.1.13] - 2021-04-05
30
+ - `Loxxy` now implements method calls
31
+
32
+ ### New
33
+ - Class `Ast::LoxGetExpr` a syntax node that represents a read access to an object property
34
+ - Class `Ast::LoxSetExpr` a syntax node that represents a write access to an object property
35
+ - Method `Ast::ASTBuilder#reduce_set_expr` parse action for write access to an object property
36
+ - Method `Ast::ASTBuilder#reduce_get_expr` parse action for read access to an object property
37
+ - Method `Ast::Visitor#visit_set_expr` visit of an `Ast::LoxSetExpr` node
38
+ - Method `Ast::Visitor#visit_get_expr` visit of an `Ast::LoxGetExpr` node
39
+ - Method `BackEnd::Engine#after_set_expr` runtime action for property setting
40
+ - Method `BackEnd::Engine#after_get_expr` runtime action for property getting
41
+ - Method `BackEnd::LoxInstance#set` implementation of write accessor
42
+ - Method `BackEnd::LoxInstance#get` implementation of read accessor
43
+ - Method `BackEnd::Resolver#after_set_expr` resolve action for property setting
44
+ - Method `BackEnd::Resolver#after_get_expr` resolve action for property getting
45
+
46
+ ### Changed
47
+ - Method `Ast::ASTBuilder#reduce_assign_expr` expanded to support write access to an object property
48
+ - Class `LoxClassStmt`: methods are now aggregate under the `body` attribute
49
+ - Class `LoxFunStmt`: has a new attribute `is_method` and inherits from `Ast::LoxNode`
50
+ - Method `BackEnd::Engine#after_class_stmt` methods are aggregated into the classes
51
+ - Method `BackEnd::Engine#after_fun_stmt` extension for method
52
+ - File `grammar.rb` added names to two syntax rules
53
+
54
+
55
+ ## [0.1.12] - 2021-04-03
56
+ - Intermediate version: `Loxxy` does instance creation (default constructor)
57
+
58
+ ### New
59
+ - Method `BackEnd::LoxClass#call` made class callable (for invoking the constructor)
60
+ - Class `BackEnd::LoxInstance` runtime representation of a Lox instance (object).
61
+
62
+ ### Changed
63
+ - Method `BackEnd::Engine#after_call_expr` Added Lox class as callable thing.
64
+
65
+ ### Fixed
66
+ - Method `Ast::ASTBuilder#reduce_class_body` couldn't handle properly empty classes
67
+
68
+ ## [0.1.11] - 2021-04-03
69
+ - Intermediate version: `Loxxy` does class declarations
70
+
71
+ ### New
72
+ - Class `Ast::LoxClassStmt` a syntax node that represents a class declaration
73
+ - Method `Ast::ASTBuilder#reduce_class_decl` creates a `LoxClassStmt` instance
74
+ - Method `Ast::ASTBuilder#reduce_class_name`
75
+ - Method `Ast::ASTBuilder#reduce_reduce_class_body` collect the methods of the class
76
+ - Method `Ast::ASTBuilder#reduce_method_plus_more` for dealing with methods
77
+ - Method `Ast::ASTBuilder#reduce_method_plus_end`
78
+ - Method `Ast::ASTVisitor#visit_class_stmt` for visiting an `Ast::LoxClassStmt` node
79
+ - Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
80
+ - Method `BackEnd::Engine#after_class_stmt`
81
+ - Method `BackEnd::Resolver#after_class_stmt`
82
+ - Method `BackEnd::Resolver#before_class_stmt`
83
+ - Class `BackEnd::LoxClass` runtime representation of a Lox class.
84
+
85
+ ### Changed
86
+ - File `grammar.rb` refactoring of class declaration syntax rules
87
+
1
88
  ## [0.1.10] - 2021-03-31
2
89
  - Flag return statements occurring outside functions as an error
3
90
 
@@ -8,7 +95,7 @@
8
95
  ## [0.1.09] - 2021-03-28
9
96
  - Fix and test suite for return statements
10
97
 
11
- ### Changed
98
+ ### CHANGED
12
99
  - `Loxxy` reports an error when a return statement occurs in top-level scope
13
100
 
14
101
  ### Fixed
@@ -20,7 +107,7 @@
20
107
  ### New
21
108
  - Class `BackEnd::Resolver` implements the variable resolution (whenever a variable is in use, locate the declaration of that variable)
22
109
 
23
- ### Changed
110
+ ### CHANGED
24
111
  - Class `Ast::Visitor` changes in some method signatures
25
112
  - Class `BackEnd::Engine` new attribute `resolver` that points to a `BackEnd::Resolver` instance
26
113
  - Class `BackEnd::Engine` several methods dealing with variables have been adapted to take the resolver into account.
@@ -110,9 +197,9 @@
110
197
  - Method `Ast::ASTVisitor#visit_fun_stmt` for visiting an `Ast::LoxFunStmt` node
111
198
  - Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
112
199
  - Method `BackEnd::Engine#after_fun_stmt`
113
- - Method `Backend::NativeFunction#call`
114
- - Method `Backend::NativeFunction#to_str`
115
- - Method `Backend::Function` implementation of a function object.
200
+ - Method `BackEnd::NativeFunction#call`
201
+ - Method `BackEnd::NativeFunction#to_str`
202
+ - Method `BackEnd::LoxFunction` runtime representation of a Lox function.
116
203
 
117
204
  ### Changed
118
205
  - 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'
@@ -17,4 +20,5 @@ require_relative 'lox_print_stmt'
17
20
  require_relative 'lox_if_stmt'
18
21
  require_relative 'lox_for_stmt'
19
22
  require_relative 'lox_var_stmt'
23
+ require_relative 'lox_class_stmt'
20
24
  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]
@@ -208,11 +233,20 @@ module Loxxy
208
233
  return_first_child(range, tokens, theChildren)
209
234
  end
210
235
 
211
- # 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 ''
212
246
  def reduce_if_stmt(_production, _range, tokens, theChildren)
213
247
  condition = theChildren[1]
214
248
  then_stmt = theChildren[2]
215
- else_stmt = theChildren[3]
249
+ else_stmt = nil
216
250
  LoxIfStmt.new(tokens[0].position, condition, then_stmt, else_stmt)
217
251
  end
218
252
 
@@ -244,10 +278,22 @@ module Loxxy
244
278
 
245
279
  # rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
246
280
  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])
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
249
289
  end
250
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])
294
+ end
295
+
296
+
251
297
  # rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
252
298
  # TODO: is it meaningful to implement this rule?
253
299
 
@@ -292,6 +338,11 @@ module Loxxy
292
338
  LoxCallExpr.new(tokens[0].position, args)
293
339
  end
294
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
+
295
346
  # rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
296
347
  def reduce_grouping_expr(_production, _range, tokens, theChildren)
297
348
  subexpr = theChildren[1]
@@ -312,6 +363,11 @@ module Loxxy
312
363
  LoxVariableExpr.new(tokens[0].position, var_name)
313
364
  end
314
365
 
366
+ # rule('primary' => 'THIS')
367
+ def reduce_this_expr(_production, _range, tokens, _children)
368
+ LoxThisExpr.new(tokens[0].position)
369
+ end
370
+
315
371
  # rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
316
372
  def reduce_function(_production, _range, _tokens, theChildren)
317
373
  first_child = theChildren.first
@@ -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)
@@ -184,6 +205,13 @@ module Loxxy
184
205
  broadcast(:after_variable_expr, aVariableExpr, self)
185
206
  end
186
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
+
187
215
  # Visit event. The visitor is about to visit the given terminal datatype value.
188
216
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
189
217
  def visit_builtin(aValue)
@@ -195,7 +223,6 @@ module Loxxy
195
223
  # @param aFunStmt [AST::LoxFunStmt] function declaration to visit
196
224
  def visit_fun_stmt(aFunStmt)
197
225
  broadcast(:before_fun_stmt, aFunStmt, self)
198
- traverse_subnodes(aFunStmt)
199
226
  broadcast(:after_fun_stmt, aFunStmt, self)
200
227
  end
201
228
 
@@ -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
@@ -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
@@ -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
@@ -243,6 +281,11 @@ module Loxxy
243
281
  stack.push(literalExpr.literal)
244
282
  end
245
283
 
284
+ def after_this_expr(aThisExpr, aVisitor)
285
+ var = variable_lookup(aThisExpr)
286
+ var.value.accept(aVisitor) # Evaluate this value then push on stack
287
+ end
288
+
246
289
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
247
290
  def before_visit_builtin(aValue)
248
291
  stack.push(aValue)
@@ -250,8 +293,12 @@ module Loxxy
250
293
 
251
294
  def after_fun_stmt(aFunStmt, _visitor)
252
295
  function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
253
- new_var = Variable.new(aFunStmt.name, function)
254
- symbol_table.insert(new_var)
296
+ if aFunStmt.is_method
297
+ stack.push function
298
+ else
299
+ new_var = Variable.new(aFunStmt.name, function)
300
+ symbol_table.insert(new_var)
301
+ end
255
302
  end
256
303
 
257
304
  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
@@ -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
 
@@ -54,6 +52,16 @@ module Loxxy
54
52
  engine.symbol_table.leave_environment
55
53
  end
56
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
+
57
65
  # Logical negation.
58
66
  # As a function is a truthy thing, its negation is thus false.
59
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
@@ -53,6 +58,24 @@ module Loxxy
53
58
  end_scope
54
59
  end
55
60
 
61
+ # A class declaration adds a new variable to current scope
62
+ def before_class_stmt(aClassStmt)
63
+ declare(aClassStmt.name)
64
+ end
65
+
66
+ def after_class_stmt(aClassStmt, aVisitor)
67
+ previous_class = current_class
68
+ @current_class = :class
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
77
+ end
78
+
56
79
  def before_for_stmt(aForStmt)
57
80
  before_block_stmt(aForStmt)
58
81
  end
@@ -100,6 +123,11 @@ module Loxxy
100
123
  resolve_local(anAssignExpr, aVisitor)
101
124
  end
102
125
 
126
+ def after_set_expr(aSetExpr, aVisitor)
127
+ # Evaluate object part
128
+ aSetExpr.object.accept(aVisitor)
129
+ end
130
+
103
131
  # Variable expressions require their variables resolved
104
132
  def before_variable_expr(aVarExpr)
105
133
  var_name = aVarExpr.name
@@ -118,11 +146,28 @@ module Loxxy
118
146
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
119
147
  end
120
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
+
121
166
  # function declaration creates a new scope for its body & binds its parameters for that scope
122
167
  def before_fun_stmt(aFunStmt, aVisitor)
123
168
  declare(aFunStmt.name)
124
169
  define(aFunStmt.name)
125
- resolve_function(aFunStmt, :function ,aVisitor)
170
+ resolve_function(aFunStmt, :function, aVisitor)
126
171
  end
127
172
 
128
173
  private
@@ -35,14 +35,16 @@ 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
- 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
 
@@ -50,6 +52,8 @@ module Loxxy
50
52
  rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
51
53
 
52
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
53
57
  rule('statement' => 'exprStmt')
54
58
  rule('statement' => 'forStmt')
55
59
  rule('statement' => 'ifStmt')
@@ -68,10 +72,10 @@ module Loxxy
68
72
  rule('forTest' => 'expression_opt SEMICOLON').as 'for_test'
69
73
  rule('forUpdate' => 'expression_opt')
70
74
 
71
- 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'
72
78
  rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
73
- rule('elsePart_opt' => 'ELSE statement').as 'keep_symbol2'
74
- rule('elsePart_opt' => [])
75
79
 
76
80
  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
77
81
  rule('returnStmt' => 'RETURN expression_opt SEMICOLON').as 'return_stmt'
@@ -85,7 +89,7 @@ module Loxxy
85
89
  rule('expression' => 'assignment')
86
90
  rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
87
91
  rule('assignment' => 'logic_or')
88
- rule('owner_opt' => 'call DOT')
92
+ rule('owner_opt' => 'call DOT').as 'set_expr'
89
93
  rule('owner_opt' => [])
90
94
  rule('logic_or' => 'logic_and')
91
95
  rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
@@ -130,11 +134,11 @@ module Loxxy
130
134
  rule('refinement_plus' => 'refinement_plus refinement').as 'refinement_plus_more'
131
135
  rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
132
136
  rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
133
- rule('refinement' => 'DOT IDENTIFIER')
137
+ rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
134
138
  rule('primary' => 'TRUE').as 'literal_expr'
135
139
  rule('primary' => 'FALSE').as 'literal_expr'
136
140
  rule('primary' => 'NIL').as 'literal_expr'
137
- rule('primary' => 'THIS')
141
+ rule('primary' => 'THIS').as 'this_expr'
138
142
  rule('primary' => 'NUMBER').as 'literal_expr'
139
143
  rule('primary' => 'STRING').as 'literal_expr'
140
144
  rule('primary' => 'IDENTIFIER').as 'variable_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.10'
4
+ VERSION = '0.1.15'
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
@@ -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 }
@@ -460,6 +461,71 @@ LOX_END
460
461
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')
461
462
  end
462
463
  end # context
464
+
465
+ context 'Object orientation:' do
466
+ let(:duck_class) do
467
+ snippet = <<-LOX_END
468
+ class Duck {
469
+ noise() {
470
+ this.quack();
471
+ }
472
+
473
+ quack() {
474
+ print "quack";
475
+ }
476
+ }
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
487
+ LOX_END
488
+ expect { subject.evaluate(program) }.not_to raise_error
489
+ expect(sample_cfg[:ostream].string).to eq('Duck')
490
+ end
491
+
492
+ it 'should support default instance creation' do
493
+ program = <<-LOX_END
494
+ #{duck_class}
495
+
496
+ var daffy = Duck(); // Default constructor
497
+ print daffy;
498
+ LOX_END
499
+ expect { subject.evaluate(program) }.not_to raise_error
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
+ end # context
463
529
  end # describe
464
530
  # rubocop: enable Metrics/BlockLength
465
531
  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.10
4
+ version: 0.1.15
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-31 00:00:00.000000000 Z
11
+ date: 2021-04-08 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,8 @@ 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
111
+ - lib/loxxy/ast/lox_this_expr.rb
108
112
  - lib/loxxy/ast/lox_unary_expr.rb
109
113
  - lib/loxxy/ast/lox_var_stmt.rb
110
114
  - lib/loxxy/ast/lox_variable_expr.rb
@@ -113,7 +117,9 @@ files:
113
117
  - lib/loxxy/back_end/engine.rb
114
118
  - lib/loxxy/back_end/entry.rb
115
119
  - lib/loxxy/back_end/environment.rb
120
+ - lib/loxxy/back_end/lox_class.rb
116
121
  - lib/loxxy/back_end/lox_function.rb
122
+ - lib/loxxy/back_end/lox_instance.rb
117
123
  - lib/loxxy/back_end/resolver.rb
118
124
  - lib/loxxy/back_end/symbol_table.rb
119
125
  - lib/loxxy/back_end/unary_operator.rb