loxxy 0.1.10 → 0.1.15

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