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 +4 -4
- data/CHANGELOG.md +92 -5
- data/README.md +2 -26
- data/lib/loxxy/ast/all_lox_nodes.rb +4 -0
- data/lib/loxxy/ast/ast_builder.rb +60 -4
- data/lib/loxxy/ast/ast_visitor.rb +28 -1
- data/lib/loxxy/ast/lox_class_stmt.rb +30 -0
- data/lib/loxxy/ast/lox_fun_stmt.rb +6 -4
- data/lib/loxxy/ast/lox_get_expr.rb +30 -0
- data/lib/loxxy/ast/lox_set_expr.rb +28 -0
- data/lib/loxxy/ast/lox_this_expr.rb +22 -0
- data/lib/loxxy/back_end/engine.rb +50 -3
- data/lib/loxxy/back_end/environment.rb +0 -1
- data/lib/loxxy/back_end/lox_class.rb +59 -0
- data/lib/loxxy/back_end/lox_function.rb +11 -3
- data/lib/loxxy/back_end/lox_instance.rb +58 -0
- data/lib/loxxy/back_end/resolver.rb +46 -1
- data/lib/loxxy/front_end/grammar.rb +16 -12
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/parser_spec.rb +77 -0
- data/spec/front_end/raw_parser_spec.rb +5 -2
- data/spec/interpreter_spec.rb +73 -7
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3994a3225b7dbc4a39d24cc646c20e4a7a19b074e85fe2dd9bc87d9ca1d61cb
|
4
|
+
data.tar.gz: 965c328057f51fb7d13886255955e782e51b1f030f3053a6f5f6f4534962855b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
###
|
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
|
-
###
|
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 `
|
114
|
-
- Method `
|
115
|
-
- Method `
|
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
|
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 =
|
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
|
-
|
248
|
-
|
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 '
|
3
|
+
require_relative 'lox_node'
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
7
|
# rubocop: disable Style/AccessorGrouping
|
8
|
-
class LoxFunStmt <
|
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, "
|
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
|
-
|
254
|
-
|
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
|
@@ -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
|
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
|
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' => '
|
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
|
44
|
-
rule('
|
45
|
-
rule('
|
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
|
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
@@ -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('
|
80
|
-
|
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)
|
data/spec/interpreter_spec.rb
CHANGED
@@ -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
|
-
|
231
|
-
|
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
|
-
|
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
|
-
#
|
241
|
-
|
242
|
-
|
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.
|
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-
|
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
|