loxxy 0.0.28 → 0.1.04
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 +81 -2
- data/README.md +53 -11
- data/bin/loxxy +11 -0
- data/lib/loxxy.rb +0 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +2 -0
- data/lib/loxxy/ast/ast_builder.rb +32 -0
- data/lib/loxxy/ast/ast_visitor.rb +26 -10
- data/lib/loxxy/ast/lox_block_stmt.rb +5 -1
- data/lib/loxxy/ast/lox_for_stmt.rb +0 -1
- data/lib/loxxy/ast/lox_fun_stmt.rb +34 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +3 -1
- data/lib/loxxy/ast/lox_return_stmt.rb +21 -0
- data/lib/loxxy/back_end/binary_operator.rb +57 -0
- data/lib/loxxy/back_end/engine.rb +109 -11
- data/lib/loxxy/back_end/function.rb +51 -0
- data/lib/loxxy/back_end/unary_operator.rb +41 -0
- data/lib/loxxy/error.rb +9 -0
- data/lib/loxxy/front_end/grammar.rb +6 -6
- data/lib/loxxy/front_end/literal.rb +1 -1
- data/lib/loxxy/front_end/scanner.rb +2 -0
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +2 -2
- data/spec/interpreter_spec.rb +55 -1
- metadata +12 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16614a276ddf1553d4373e5da9ab78d7f03f2fc8cadec6190c28bb27bdd95b99
|
4
|
+
data.tar.gz: 98903f6ec0c9e010fd429f6b1753b34f52b4cfc67c7eafd21b1103c6ac429bba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8278546ef246d8f4d5db72b18d5c91dfa3dc1d7f18f81b7491ca01df38a64495a3b5eb26df0fa18b652f59d56784efb8fe9348b5b65d091aa27a09e6b8f144a4
|
7
|
+
data.tar.gz: deaa7b43f225788b9366610572f5994eec8950a8e66c8159b83a18b874139d322270f274c4753dc28b6926b7c295d42a2f77600211dfbd0df0e24b82e1a155b5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,82 @@
|
|
1
|
+
## [0.1.04] - 2021-02-28
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- Class `Ast::LoxReturnStmt` a node that represents a return statement
|
5
|
+
- Method `Ast::ASTBuilder#reduce_return_stmt`
|
6
|
+
- Method `Ast::ASTVisitor#visit_return_stmt` for visiting an `Ast::LoxReturnStmt` node
|
7
|
+
- Method `BackEnd::Engine#after_return_stmt` to handle return statement
|
8
|
+
- Method `BackEnd::Function#!` implementing the logical negation of a function (as value).
|
9
|
+
- Test suite for logical operators (in project repository)
|
10
|
+
- Test suite for block code
|
11
|
+
- Test suite for call and function declaration (initial)
|
12
|
+
|
13
|
+
### Changed
|
14
|
+
- Method `BackEnd::Engine#after_call_expr` now generate a `catch` and `throw` events
|
15
|
+
|
16
|
+
## [0.1.03] - 2021-02-26
|
17
|
+
- Runtime argument checking for arithmetic and comparison operators
|
18
|
+
|
19
|
+
### Added
|
20
|
+
- Test suite for arithmetic and comparison operators (in project repository)
|
21
|
+
- Class `BackEnd::UnaryOperator`: runtime argument validation
|
22
|
+
- Class `BackEnd::BinaryOperator`: runtime argument validation
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
- File `console` renamed to `loxxy`. Very basic command-line interface.
|
26
|
+
- Custom exception classes
|
27
|
+
- File `README.md` updated list of supported `Lox` keywords.
|
28
|
+
|
29
|
+
|
30
|
+
## [0.1.02] - 2021-02-21
|
31
|
+
- Function definition and call documented in `README.md`
|
32
|
+
|
33
|
+
### Changed
|
34
|
+
- File `README.md` updated description of function definition and function call.
|
35
|
+
|
36
|
+
### Fixed
|
37
|
+
- Method `BackEnd::Engine#after_print_stmt` now handles of empty stack or nil data.
|
38
|
+
- Method `BackEnd::Engine#after_call_expr` was pushing one spurious item onto data stack.
|
39
|
+
|
40
|
+
## [0.1.01] - 2021-02-20
|
41
|
+
### Fixed
|
42
|
+
- Fixed most offences for Rubocop.
|
43
|
+
|
44
|
+
|
45
|
+
## [0.1.00] - 2021-02-20
|
46
|
+
- Version number bumped, `Loxxy` supports function definitions
|
47
|
+
|
48
|
+
### Added
|
49
|
+
- Class `Ast::LoxFunStmt` a node that represents a function declaration
|
50
|
+
- Method `Ast::ASTBuilder#reduce_fun_decl`
|
51
|
+
- Method `Ast::ASTBuilder#reduce_function` creates a `LoxFunStmt` instance
|
52
|
+
- Method `Ast::ASTBuilder#reduce_parameters_plus_more` for dealing with function parameters
|
53
|
+
- Method `Ast::ASTBuilder#reduce_parameters_plus_end`
|
54
|
+
- Method `Ast::ASTVisitor#visit_fun_stmt` for visiting an `Ast::LoxFunStmt` node
|
55
|
+
- Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
|
56
|
+
- Method `BackEnd::Engine#after_fun_stmt`
|
57
|
+
- Method `Backend::NativeFunction#call`
|
58
|
+
- Method `Backend::NativeFunction#to_str`
|
59
|
+
- Method `Backend::Function` implementation of a function object.
|
60
|
+
|
61
|
+
### Changed
|
62
|
+
- Method `BackEnd::Engine#after_call_expr`
|
63
|
+
|
64
|
+
### Fixed
|
65
|
+
- Fixed inconsistencies in documentation comments.
|
66
|
+
|
67
|
+
## [0.0.28] - 2021-02-15
|
68
|
+
- The interpreter implements function calls (to a native function).
|
69
|
+
|
70
|
+
### Added
|
71
|
+
- Class `Ast::LoxCallExpr` a node that represents a function call expression
|
72
|
+
- Method `Ast::ASTBuilder#reduce_call_expr`
|
73
|
+
- Method `Ast::ASTBuilder#reduce_refinement_plus_end`
|
74
|
+
- Method `Ast::ASTBuilder#reduce_call_arglist` creates a `LoxCallExpr` node
|
75
|
+
- Method `Ast::ASTBuilder#reduce_arguments_plus_more` builds the function argument array
|
76
|
+
- Method `Ast::ASTVisitor#visit_call_expr` for visiting an `Ast::LoxCallExpr` node
|
77
|
+
- Method `BackEnd::Engine#after_call_expr`implements the evaluation of a function call.
|
78
|
+
- Method `BackEnd::Engine#after_for_stmt` implements most of the `for` control flow
|
79
|
+
|
1
80
|
## [0.0.27] - 2021-01-24
|
2
81
|
- The interpreter implements `while` loops.
|
3
82
|
|
@@ -157,11 +236,11 @@
|
|
157
236
|
- The interpreter can evaluate substraction between two numbers.
|
158
237
|
|
159
238
|
### Added
|
160
|
-
- Method `Datatype::Number#-`
|
239
|
+
- Method `Datatype::Number#-` implementing the subtraction operation
|
161
240
|
|
162
241
|
### Changed
|
163
242
|
- File `README.md` minor editorial changes.
|
164
|
-
- File `lx_string_spec.rb` Added test for string
|
243
|
+
- File `lx_string_spec.rb` Added test for string concatenation
|
165
244
|
- File `number_spec.rb` Added tests for addition and subtraction operations
|
166
245
|
- File `interpreter_spec.rb` Added tests for subtraction operation
|
167
246
|
|
data/README.md
CHANGED
@@ -14,14 +14,13 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
|
|
14
14
|
|
15
15
|
### Current status
|
16
16
|
The project is still in inception and the interpreter is being implemented...
|
17
|
-
Currently it can execute all allowed __Lox__ expressions and
|
18
|
-
-
|
17
|
+
Currently it can execute all allowed __Lox__ expressions and statements except:
|
18
|
+
- Closures,
|
19
19
|
- Classes and objects.
|
20
20
|
|
21
21
|
These will be implemented soon.
|
22
22
|
|
23
23
|
|
24
|
-
|
25
24
|
## What's the fuss about Lox?
|
26
25
|
... Nothing...
|
27
26
|
Bob Nystrom designed a language __simple__ enough so that he could present
|
@@ -36,11 +35,11 @@ Although __Lox__ is fairly simple, it is far from a toy language:
|
|
36
35
|
- Functions and closures
|
37
36
|
- Object-orientation (classes, methods, inheritance).
|
38
37
|
|
39
|
-
In other words, __Lox__ contains interesting features
|
38
|
+
In other words, __Lox__ contains interesting features found in most general-purpose
|
40
39
|
languages.
|
41
40
|
|
42
41
|
### What's missing in Lox?
|
43
|
-
__Lox__ was constrained by design and therefore
|
42
|
+
__Lox__ was constrained by design and was therefore not aimed to be a language used in real-world applications.
|
44
43
|
Here are some missing parts to make it a _practical_ language:
|
45
44
|
- Collections (arrays, maps, ...)
|
46
45
|
- Modules (importing stuff from other packages/files)
|
@@ -70,6 +69,23 @@ lox = Loxxy::Interpreter.new
|
|
70
69
|
lox.evaluate(lox_program) # Output: Hello, world!
|
71
70
|
```
|
72
71
|
|
72
|
+
## A function definition example
|
73
|
+
```ruby
|
74
|
+
require 'loxxy'
|
75
|
+
|
76
|
+
lox_program = <<LOX_END
|
77
|
+
fun add4(n) {
|
78
|
+
n + 4;
|
79
|
+
}
|
80
|
+
|
81
|
+
print add4(6); // Output: 10
|
82
|
+
LOX_END
|
83
|
+
|
84
|
+
lox = Loxxy::Interpreter.new
|
85
|
+
lox.evaluate(lox_program) # Output 10
|
86
|
+
```
|
87
|
+
|
88
|
+
|
73
89
|
## Retrieving the result from a Lox program
|
74
90
|
The __Loxxy__ interpreter returns the value of the last evaluated expression.
|
75
91
|
|
@@ -151,6 +167,7 @@ Here are the language features currently supported by the interpreter:
|
|
151
167
|
- [Print Statement](#print-statement)
|
152
168
|
- [While Statement](#while-statement)
|
153
169
|
- [Block Statement](#block-statement)
|
170
|
+
- [Function declaration](#func-statement)
|
154
171
|
|
155
172
|
### Comments
|
156
173
|
|
@@ -163,7 +180,8 @@ Loxxy supports single line C-style comments.
|
|
163
180
|
### Keywords
|
164
181
|
Loxxy implements the following __Lox__ reserved keywords:
|
165
182
|
```lang-none
|
166
|
-
and, false,
|
183
|
+
and, else, false, for, fun, if,
|
184
|
+
nil, or, print, true, var, while
|
167
185
|
```
|
168
186
|
|
169
187
|
### Datatypes
|
@@ -177,19 +195,21 @@ loxxy supports all the standard __Lox__ datatypes:
|
|
177
195
|
### Statements
|
178
196
|
|
179
197
|
Loxxy supports the following statements:
|
180
|
-
- [Expressions](#expressions)
|
198
|
+
- [Expressions](#expressions)
|
181
199
|
-[Arithmetic expressions](#arithmetic-expressions)
|
182
200
|
-[String concatenation](#string-concatenation)
|
183
201
|
-[Comparison expressions](#comparison-expressions)
|
184
202
|
-[Logical expressions](#logical-expressions)
|
185
203
|
-[Grouping expressions](#grouping-expressions)
|
186
|
-
-[Variable expressions and assignments](#variable-expressions)
|
187
|
-
|
204
|
+
-[Variable expressions and assignments](#variable-expressions)
|
205
|
+
-[Function call](#function-call)
|
206
|
+
|
188
207
|
-[Variable declarations](#var-statement)
|
189
208
|
-[If Statement](#if-statement)
|
190
209
|
-[Print Statement](#print-statement)
|
191
|
-
-[While Statement](#while-statement)
|
192
|
-
-[Block Statement](#block-statement)
|
210
|
+
-[While Statement](#while-statement)
|
211
|
+
-[Block Statement](#block-statement)
|
212
|
+
-[Function Declaration](#function-declaration)
|
193
213
|
|
194
214
|
#### Expressions
|
195
215
|
|
@@ -284,6 +304,15 @@ var iAmNil; // __Lox__ initializes variables to nil by default;
|
|
284
304
|
print iAmNil; // output: nil
|
285
305
|
```
|
286
306
|
|
307
|
+
#### Function call
|
308
|
+
``` javascript
|
309
|
+
// Calling a function without argument
|
310
|
+
print clock();
|
311
|
+
|
312
|
+
// Assumption: there exists a function `add` that takes two arguments
|
313
|
+
print add(2, 3);
|
314
|
+
```
|
315
|
+
|
287
316
|
#### For statement
|
288
317
|
|
289
318
|
Similar to the `for` statement in `C` language
|
@@ -362,6 +391,19 @@ var a = "outer";
|
|
362
391
|
print a; // output: outer
|
363
392
|
```
|
364
393
|
|
394
|
+
#### Function Declaration
|
395
|
+
The keyword `fun` is used to begin a function declaration.
|
396
|
+
In __Lox__ a function has a name and a body (which may be empty).
|
397
|
+
|
398
|
+
``` javascript
|
399
|
+
fun add4(n) // `add4` will be the name of the function
|
400
|
+
{
|
401
|
+
n + 4;
|
402
|
+
}
|
403
|
+
|
404
|
+
print add4(6); // output: 10
|
405
|
+
```
|
406
|
+
|
365
407
|
## Installation
|
366
408
|
|
367
409
|
Add this line to your application's Gemfile:
|
data/bin/loxxy
ADDED
data/lib/loxxy.rb
CHANGED
@@ -6,8 +6,6 @@ require_relative 'loxxy/front_end/raw_parser'
|
|
6
6
|
|
7
7
|
# Namespace for all classes and constants of __loxxy__ gem.
|
8
8
|
module Loxxy
|
9
|
-
class Error < StandardError; end
|
10
|
-
|
11
9
|
# Shorthand method. Returns the sole object that represents
|
12
10
|
# a Lox false literal.
|
13
11
|
# @return [Loxxy::Datatype::False]
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'lox_fun_stmt'
|
3
4
|
require_relative 'lox_variable_expr'
|
4
5
|
require_relative 'lox_literal_expr'
|
5
6
|
require_relative 'lox_noop_expr'
|
@@ -11,6 +12,7 @@ require_relative 'lox_logical_expr'
|
|
11
12
|
require_relative 'lox_assign_expr'
|
12
13
|
require_relative 'lox_block_stmt'
|
13
14
|
require_relative 'lox_while_stmt'
|
15
|
+
require_relative 'lox_return_stmt'
|
14
16
|
require_relative 'lox_print_stmt'
|
15
17
|
require_relative 'lox_if_stmt'
|
16
18
|
require_relative 'lox_for_stmt'
|
@@ -163,6 +163,11 @@ module Loxxy
|
|
163
163
|
[theChildren[0]]
|
164
164
|
end
|
165
165
|
|
166
|
+
# rule('funDecl' => 'FUN function')
|
167
|
+
def reduce_fun_decl(_production, _range, _tokens, theChildren)
|
168
|
+
theChildren[1]
|
169
|
+
end
|
170
|
+
|
166
171
|
# rule('exprStmt' => 'expression SEMICOLON')
|
167
172
|
def reduce_exprStmt(_production, range, tokens, theChildren)
|
168
173
|
return_first_child(range, tokens, theChildren) # Discard the semicolon
|
@@ -216,6 +221,11 @@ module Loxxy
|
|
216
221
|
Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
|
217
222
|
end
|
218
223
|
|
224
|
+
# rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
|
225
|
+
def reduce_return_stmt(_production, _range, tokens, theChildren)
|
226
|
+
Ast::LoxReturnStmt.new(tokens[1].position, theChildren[1])
|
227
|
+
end
|
228
|
+
|
219
229
|
# rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as ''
|
220
230
|
def reduce_while_stmt(_production, _range, tokens, theChildren)
|
221
231
|
Ast::LoxWhileStmt.new(tokens[1].position, theChildren[2], theChildren[4])
|
@@ -227,6 +237,11 @@ module Loxxy
|
|
227
237
|
Ast::LoxBlockStmt.new(tokens[1].position, decls)
|
228
238
|
end
|
229
239
|
|
240
|
+
# rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
|
241
|
+
def reduce_block_empty(_production, _range, tokens, _children)
|
242
|
+
Ast::LoxBlockStmt.new(tokens[0].position, nil)
|
243
|
+
end
|
244
|
+
|
230
245
|
# rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
|
231
246
|
def reduce_assign_expr(_production, _range, tokens, theChildren)
|
232
247
|
var_name = theChildren[1].token.lexeme.dup
|
@@ -284,6 +299,23 @@ module Loxxy
|
|
284
299
|
LoxVariableExpr.new(tokens[0].position, var_name)
|
285
300
|
end
|
286
301
|
|
302
|
+
# rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
|
303
|
+
def reduce_function(_production, _range, _tokens, theChildren)
|
304
|
+
first_child = theChildren.first
|
305
|
+
pos = first_child.token.position
|
306
|
+
LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
|
307
|
+
end
|
308
|
+
|
309
|
+
# rule('parameters' => 'parameters COMMA IDENTIFIER')
|
310
|
+
def reduce_parameters_plus_more(_production, _range, _tokens, theChildren)
|
311
|
+
theChildren[0] << theChildren[2].token.lexeme
|
312
|
+
end
|
313
|
+
|
314
|
+
# rule('parameters' => 'IDENTIFIER')
|
315
|
+
def reduce_parameters_plus_end(_production, _range, _tokens, theChildren)
|
316
|
+
[theChildren[0].token.lexeme]
|
317
|
+
end
|
318
|
+
|
287
319
|
# rule('arguments' => 'arguments COMMA expression')
|
288
320
|
def reduce_arguments_plus_more(_production, _range, _tokens, theChildren)
|
289
321
|
theChildren[0] << theChildren[2]
|
@@ -12,7 +12,7 @@ module Loxxy
|
|
12
12
|
# attr_reader(:runtime)
|
13
13
|
|
14
14
|
# Build a visitor for the given top.
|
15
|
-
# @param
|
15
|
+
# @param aTop [AST::LoxNode] the parse tree to visit.
|
16
16
|
def initialize(aTop)
|
17
17
|
raise StandardError if aTop.nil?
|
18
18
|
|
@@ -52,7 +52,7 @@ module Loxxy
|
|
52
52
|
end
|
53
53
|
|
54
54
|
# Visit event. The visitor is about to visit a variable declaration statement.
|
55
|
-
# @param
|
55
|
+
# @param aSeqDecls [AST::LOXSeqDecl] the variable declaration node to visit
|
56
56
|
def visit_seq_decl(aSeqDecls)
|
57
57
|
broadcast(:before_seq_decl, aSeqDecls)
|
58
58
|
traverse_subnodes(aSeqDecls)
|
@@ -60,7 +60,7 @@ module Loxxy
|
|
60
60
|
end
|
61
61
|
|
62
62
|
# Visit event. The visitor is about to visit a variable declaration statement.
|
63
|
-
# @param
|
63
|
+
# @param aVarStmt [AST::LOXVarStmt] the variable declaration node to visit
|
64
64
|
def visit_var_stmt(aVarStmt)
|
65
65
|
broadcast(:before_var_stmt, aVarStmt)
|
66
66
|
traverse_subnodes(aVarStmt)
|
@@ -91,6 +91,14 @@ module Loxxy
|
|
91
91
|
broadcast(:after_print_stmt, aPrintStmt)
|
92
92
|
end
|
93
93
|
|
94
|
+
# Visit event. The visitor is about to visit a return statement.
|
95
|
+
# @param aReturnStmt [AST::LOXReturnStmt] the return statement node to visit
|
96
|
+
def visit_return_stmt(aReturnStmt)
|
97
|
+
broadcast(:before_return_stmt, aReturnStmt)
|
98
|
+
traverse_subnodes(aReturnStmt)
|
99
|
+
broadcast(:after_return_stmt, aReturnStmt, self)
|
100
|
+
end
|
101
|
+
|
94
102
|
# Visit event. The visitor is about to visit a while statement node.
|
95
103
|
# @param aWhileStmt [AST::LOXWhileStmt] the while statement node to visit
|
96
104
|
def visit_while_stmt(aWhileStmt)
|
@@ -103,12 +111,12 @@ module Loxxy
|
|
103
111
|
# @param aBlockStmt [AST::LOXBlockStmt] the print statement node to visit
|
104
112
|
def visit_block_stmt(aBlockStmt)
|
105
113
|
broadcast(:before_block_stmt, aBlockStmt)
|
106
|
-
traverse_subnodes(aBlockStmt)
|
114
|
+
traverse_subnodes(aBlockStmt) unless aBlockStmt.empty?
|
107
115
|
broadcast(:after_block_stmt, aBlockStmt)
|
108
116
|
end
|
109
117
|
|
110
118
|
# Visit event. The visitor is visiting an assignment node
|
111
|
-
# @param
|
119
|
+
# @param anAssignExpr [AST::LoxAssignExpr] the variable assignment node to visit.
|
112
120
|
def visit_assign_expr(anAssignExpr)
|
113
121
|
broadcast(:before_assign_expr, anAssignExpr)
|
114
122
|
traverse_subnodes(anAssignExpr)
|
@@ -118,7 +126,7 @@ module Loxxy
|
|
118
126
|
# Visit event. The visitor is about to visit a logical expression.
|
119
127
|
# Since logical expressions may take shorcuts by not evaluating all their
|
120
128
|
# sub-expressiosns, they are responsible for visiting or not their children.
|
121
|
-
# @param
|
129
|
+
# @param aLogicalExpr [AST::LOXLogicalExpr] the logical expression node to visit
|
122
130
|
def visit_logical_expr(aLogicalExpr)
|
123
131
|
broadcast(:before_logical_expr, aLogicalExpr)
|
124
132
|
|
@@ -170,25 +178,33 @@ module Loxxy
|
|
170
178
|
end
|
171
179
|
|
172
180
|
# Visit event. The visitor is visiting a variable usage node
|
173
|
-
# @param
|
181
|
+
# @param aVariableExpr [AST::LoxVariableExpr] the variable reference node to visit.
|
174
182
|
def visit_variable_expr(aVariableExpr)
|
175
183
|
broadcast(:before_variable_expr, aVariableExpr)
|
176
184
|
broadcast(:after_variable_expr, aVariableExpr, self)
|
177
185
|
end
|
178
186
|
|
179
187
|
# Visit event. The visitor is about to visit the given terminal datatype value.
|
180
|
-
# @param
|
188
|
+
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
181
189
|
def visit_builtin(aValue)
|
182
190
|
broadcast(:before_visit_builtin, aValue)
|
183
191
|
broadcast(:after_visit_builtin, aValue)
|
184
192
|
end
|
185
193
|
|
194
|
+
# Visit event. The visitor is about to visit a function statement node.
|
195
|
+
# @param aFunStmt [AST::LoxFunStmt] function declaration to visit
|
196
|
+
def visit_fun_stmt(aFunStmt)
|
197
|
+
broadcast(:before_fun_stmt, aFunStmt)
|
198
|
+
traverse_subnodes(aFunStmt)
|
199
|
+
broadcast(:after_fun_stmt, aFunStmt, self)
|
200
|
+
end
|
201
|
+
|
186
202
|
# Visit event. The visitor is about to visit the given non terminal node.
|
187
203
|
# @param aNonTerminalNode [Rley::PTree::NonTerminalNode] the node to visit.
|
188
|
-
def visit_nonterminal(
|
204
|
+
def visit_nonterminal(aNonTerminalNode)
|
189
205
|
# Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
|
190
206
|
# that it cannot handle.
|
191
|
-
symb =
|
207
|
+
symb = aNonTerminalNode.symbol.name
|
192
208
|
msg = "Loxxy cannot execute this code yet for non-terminal symbol '#{symb}'."
|
193
209
|
raise NotImplementedError, msg
|
194
210
|
end
|
@@ -6,11 +6,15 @@ module Loxxy
|
|
6
6
|
module Ast
|
7
7
|
class LoxBlockStmt < LoxCompoundExpr
|
8
8
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
-
# @param
|
9
|
+
# @param decls [Loxxy::Ast::LoxSeqDecl]
|
10
10
|
def initialize(aPosition, decls)
|
11
11
|
super(aPosition, [decls])
|
12
12
|
end
|
13
13
|
|
14
|
+
def empty?
|
15
|
+
subnodes.size == 1 && subnodes[0].nil?
|
16
|
+
end
|
17
|
+
|
14
18
|
# Part of the 'visitee' role in Visitor design pattern.
|
15
19
|
# @param visitor [Ast::ASTVisitor] the visitor
|
16
20
|
def accept(visitor)
|
@@ -18,7 +18,6 @@ module Loxxy
|
|
18
18
|
# @param initialization [Loxxy::Ast::LoxNode]
|
19
19
|
# @param testExpr [Loxxy::Ast::LoxNode]
|
20
20
|
# @param updateExpr [Loxxy::Ast::LoxNode]
|
21
|
-
# @param body [Loxxy::Ast::LoxNode]
|
22
21
|
def initialize(aPosition, initialization, testExpr, updateExpr)
|
23
22
|
child = initialization ? [initialization] : []
|
24
23
|
super(aPosition, child)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
# rubocop: disable Style/AccessorGrouping
|
8
|
+
class LoxFunStmt < LoxCompoundExpr
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :params
|
11
|
+
attr_reader :body
|
12
|
+
|
13
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
14
|
+
# @param aName [String]
|
15
|
+
# @param arguments [Array<String>]
|
16
|
+
# @param body [Ast::LoxBlockStmt]
|
17
|
+
def initialize(aPosition, aName, paramList, aBody)
|
18
|
+
super(aPosition, [])
|
19
|
+
@name = aName
|
20
|
+
@params = paramList
|
21
|
+
@body = aBody
|
22
|
+
end
|
23
|
+
|
24
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
25
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
26
|
+
def accept(visitor)
|
27
|
+
visitor.visit_fun_stmt(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias operands subnodes
|
31
|
+
end # class
|
32
|
+
# rubocop: enable Style/AccessorGrouping
|
33
|
+
end # module
|
34
|
+
end # module
|
@@ -12,7 +12,9 @@ module Loxxy
|
|
12
12
|
attr_reader :else_stmt
|
13
13
|
|
14
14
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
|
-
# @param
|
15
|
+
# @param condExpr [Loxxy::Ast::LoxNode]
|
16
|
+
# @param thenStmt [Loxxy::Ast::LoxNode]
|
17
|
+
# @param elseStmt [Loxxy::Ast::LoxNode]
|
16
18
|
def initialize(aPosition, condExpr, thenStmt, elseStmt)
|
17
19
|
super(aPosition, [condExpr])
|
18
20
|
@then_stmt = thenStmt
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxReturnStmt < LoxCompoundExpr
|
8
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
+
# @param anExpression [Ast::LoxNode] expression to return
|
10
|
+
def initialize(aPosition, anExpression)
|
11
|
+
super(aPosition, [anExpression])
|
12
|
+
end
|
13
|
+
|
14
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
15
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
16
|
+
def accept(visitor)
|
17
|
+
visitor.visit_return_stmt(self)
|
18
|
+
end
|
19
|
+
end # class
|
20
|
+
end # module
|
21
|
+
end # module
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../error'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module BackEnd
|
7
|
+
Signature = Struct.new(:parameter_types)
|
8
|
+
|
9
|
+
# A Lox binary operator
|
10
|
+
class BinaryOperator
|
11
|
+
# @return [String] text representation of the operator
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
# @return [Array<Class>]
|
15
|
+
attr_reader :signatures
|
16
|
+
|
17
|
+
# @param aName [String] "name" of operator
|
18
|
+
# @param theSignatures [Array<Signature>] allowed signatures
|
19
|
+
def initialize(aName, theSignatures)
|
20
|
+
@name = aName
|
21
|
+
@signatures = theSignatures
|
22
|
+
end
|
23
|
+
|
24
|
+
# rubocop: disable Style/ClassEqualityComparison
|
25
|
+
def validate_operands(operand1, operand2)
|
26
|
+
compliant = signatures.find do |(type1, type2)|
|
27
|
+
next unless operand1.kind_of?(type1)
|
28
|
+
|
29
|
+
if type2 == :idem
|
30
|
+
(operand2.class == operand1.class)
|
31
|
+
else
|
32
|
+
operand2.kind_of?(type2)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# rubocop: enable Style/ClassEqualityComparison
|
36
|
+
|
37
|
+
unless compliant
|
38
|
+
err = Loxxy::RuntimeError
|
39
|
+
if signatures.size == 1 && signatures[0].last == :idem
|
40
|
+
raise err, "Operands must be #{datatype_name(signatures[0].first)}s."
|
41
|
+
elsif signatures.size == 2 && signatures.all? { |(_, second)| second == :idem }
|
42
|
+
type1 = datatype_name(signatures[0].first)
|
43
|
+
type2 = datatype_name(signatures[1].first)
|
44
|
+
raise err, "Operands must be two #{type1}s or two #{type2}s."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def datatype_name(aClass)
|
52
|
+
# (?:(?:[^:](?!:|(?<=LX))))+$
|
53
|
+
aClass.name.sub(/^.+(?=::)::(?:LX)?/, '').downcase
|
54
|
+
end
|
55
|
+
end # class
|
56
|
+
end # module
|
57
|
+
end # module
|
@@ -2,7 +2,10 @@
|
|
2
2
|
|
3
3
|
# Load all the classes implementing AST nodes
|
4
4
|
require_relative '../ast/all_lox_nodes'
|
5
|
+
require_relative 'binary_operator'
|
6
|
+
require_relative 'function'
|
5
7
|
require_relative 'symbol_table'
|
8
|
+
require_relative 'unary_operator'
|
6
9
|
|
7
10
|
module Loxxy
|
8
11
|
module BackEnd
|
@@ -16,16 +19,26 @@ module Loxxy
|
|
16
19
|
# @return [BackEnd::SymbolTable]
|
17
20
|
attr_reader :symbol_table
|
18
21
|
|
19
|
-
# @return [Array<Datatype::
|
22
|
+
# @return [Array<Datatype::BuiltinDatatype>] Data stack for the expression results
|
20
23
|
attr_reader :stack
|
21
24
|
|
25
|
+
# @return [Hash { Symbol => UnaryOperator}]
|
26
|
+
attr_reader :unary_operators
|
27
|
+
|
28
|
+
# @return [Hash { Symbol => BinaryOperator}]
|
29
|
+
attr_reader :binary_operators
|
30
|
+
|
22
31
|
# @param theOptions [Hash]
|
23
32
|
def initialize(theOptions)
|
24
33
|
@config = theOptions
|
25
34
|
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
26
35
|
@symbol_table = SymbolTable.new
|
27
36
|
@stack = []
|
37
|
+
@unary_operators = {}
|
38
|
+
@binary_operators = {}
|
28
39
|
|
40
|
+
init_unary_operators
|
41
|
+
init_binary_operators
|
29
42
|
init_globals
|
30
43
|
end
|
31
44
|
|
@@ -82,7 +95,11 @@ module Loxxy
|
|
82
95
|
|
83
96
|
def after_print_stmt(_printStmt)
|
84
97
|
tos = stack.pop
|
85
|
-
@ostream.print tos.to_str
|
98
|
+
@ostream.print tos ? tos.to_str : 'nil'
|
99
|
+
end
|
100
|
+
|
101
|
+
def after_return_stmt(_returnStmt, _aVisitor)
|
102
|
+
throw(:return)
|
86
103
|
end
|
87
104
|
|
88
105
|
def after_while_stmt(aWhileStmt, aVisitor)
|
@@ -147,9 +164,11 @@ module Loxxy
|
|
147
164
|
end
|
148
165
|
|
149
166
|
def after_binary_expr(aBinaryExpr)
|
150
|
-
op = aBinaryExpr.operator
|
151
167
|
operand2 = stack.pop
|
152
168
|
operand1 = stack.pop
|
169
|
+
op = aBinaryExpr.operator
|
170
|
+
operator = binary_operators[op]
|
171
|
+
operator.validate_operands(operand1, operand2)
|
153
172
|
if operand1.respond_to?(op)
|
154
173
|
stack.push operand1.send(op, operand2)
|
155
174
|
else
|
@@ -159,12 +178,14 @@ module Loxxy
|
|
159
178
|
end
|
160
179
|
|
161
180
|
def after_unary_expr(anUnaryExpr)
|
162
|
-
op = anUnaryExpr.operator
|
163
181
|
operand = stack.pop
|
182
|
+
op = anUnaryExpr.operator
|
183
|
+
operator = unary_operators[op]
|
184
|
+
operator.validate_operand(operand)
|
164
185
|
if operand.respond_to?(op)
|
165
186
|
stack.push operand.send(op)
|
166
187
|
else
|
167
|
-
msg1 = "`#{op}': Unimplemented operator for a #{
|
188
|
+
msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
|
168
189
|
raise StandardError, msg1
|
169
190
|
end
|
170
191
|
end
|
@@ -173,11 +194,32 @@ module Loxxy
|
|
173
194
|
# Evaluate callee part
|
174
195
|
aCallExpr.callee.accept(aVisitor)
|
175
196
|
callee = stack.pop
|
176
|
-
|
177
|
-
|
178
|
-
|
197
|
+
aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
|
198
|
+
|
199
|
+
case callee
|
200
|
+
when NativeFunction
|
201
|
+
stack.push callee.call # Pass arguments
|
202
|
+
when Function
|
203
|
+
new_env = Environment.new(symbol_table.current_env)
|
204
|
+
symbol_table.enter_environment(new_env)
|
205
|
+
callee.parameters&.each do |param_name|
|
206
|
+
local = Variable.new(param_name, stack.pop)
|
207
|
+
symbol_table.insert(local)
|
208
|
+
end
|
209
|
+
catch(:return) do
|
210
|
+
callee.call(aVisitor)
|
211
|
+
throw(:return)
|
212
|
+
end
|
213
|
+
|
214
|
+
symbol_table.leave_environment
|
215
|
+
else
|
216
|
+
raise Loxxy::RuntimeError, 'Can only call functions and classes.'
|
179
217
|
end
|
180
|
-
|
218
|
+
end
|
219
|
+
|
220
|
+
def complete_call
|
221
|
+
callee = ret_stack.pop
|
222
|
+
symbol_table.leave_environment if callee.kind_of?(Function)
|
181
223
|
end
|
182
224
|
|
183
225
|
def after_grouping_expr(_groupingExpr)
|
@@ -197,17 +239,73 @@ module Loxxy
|
|
197
239
|
stack.push(literalExpr.literal)
|
198
240
|
end
|
199
241
|
|
200
|
-
# @param
|
242
|
+
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
201
243
|
def before_visit_builtin(aValue)
|
202
244
|
stack.push(aValue)
|
203
245
|
end
|
204
246
|
|
247
|
+
def after_fun_stmt(aFunStmt, _visitor)
|
248
|
+
function = Function.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
|
249
|
+
new_var = Variable.new(aFunStmt.name, function)
|
250
|
+
symbol_table.insert(new_var)
|
251
|
+
end
|
252
|
+
|
205
253
|
private
|
206
254
|
|
207
255
|
NativeFunction = Struct.new(:callable, :interp) do
|
208
256
|
def accept(_visitor)
|
209
|
-
interp.stack.push
|
257
|
+
interp.stack.push self
|
258
|
+
end
|
259
|
+
|
260
|
+
def call
|
261
|
+
callable.call
|
210
262
|
end
|
263
|
+
|
264
|
+
def to_str
|
265
|
+
'<native fn>'
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def init_unary_operators
|
270
|
+
negate_op = UnaryOperator.new('-', [Datatype::Number])
|
271
|
+
unary_operators[:-@] = negate_op
|
272
|
+
|
273
|
+
negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype,
|
274
|
+
BackEnd::Function])
|
275
|
+
unary_operators[:!] = negation_op
|
276
|
+
end
|
277
|
+
|
278
|
+
def init_binary_operators
|
279
|
+
plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
|
280
|
+
[Datatype::LXString, :idem]])
|
281
|
+
binary_operators[:+] = plus_op
|
282
|
+
|
283
|
+
minus_op = BinaryOperator.new('-', [[Datatype::Number, :idem]])
|
284
|
+
binary_operators[:-] = minus_op
|
285
|
+
|
286
|
+
star_op = BinaryOperator.new('*', [[Datatype::Number, :idem]])
|
287
|
+
binary_operators[:*] = star_op
|
288
|
+
|
289
|
+
slash_op = BinaryOperator.new('/', [[Datatype::Number, :idem]])
|
290
|
+
binary_operators[:/] = slash_op
|
291
|
+
|
292
|
+
equal_equal_op = BinaryOperator.new('==', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
|
293
|
+
binary_operators[:==] = equal_equal_op
|
294
|
+
|
295
|
+
not_equal_op = BinaryOperator.new('!=', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
|
296
|
+
binary_operators[:!=] = not_equal_op
|
297
|
+
|
298
|
+
less_op = BinaryOperator.new('<', [[Datatype::Number, :idem]])
|
299
|
+
binary_operators[:<] = less_op
|
300
|
+
|
301
|
+
less_equal_op = BinaryOperator.new('<=', [[Datatype::Number, :idem]])
|
302
|
+
binary_operators[:<=] = less_equal_op
|
303
|
+
|
304
|
+
greater_op = BinaryOperator.new('>', [[Datatype::Number, :idem]])
|
305
|
+
binary_operators[:>] = greater_op
|
306
|
+
|
307
|
+
greater_equal_op = BinaryOperator.new('>=', [[Datatype::Number, :idem]])
|
308
|
+
binary_operators[:>=] = greater_equal_op
|
211
309
|
end
|
212
310
|
|
213
311
|
def init_globals
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../datatype/all_datatypes'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module BackEnd
|
7
|
+
# rubocop: disable Style/AccessorGrouping
|
8
|
+
# Representation of a Lox function.
|
9
|
+
# It is a named slot that can be associated with a value at the time.
|
10
|
+
class Function
|
11
|
+
# @return [String]
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
# @return [Array<>] the parameters
|
15
|
+
attr_reader :parameters
|
16
|
+
attr_reader :body
|
17
|
+
attr_reader :stack
|
18
|
+
|
19
|
+
# Create a variable with given name and initial value
|
20
|
+
# @param aName [String] The name of the variable
|
21
|
+
# @param aValue [Datatype::BuiltinDatatype] the initial assigned value
|
22
|
+
def initialize(aName, parameterList, aBody, aStack)
|
23
|
+
@name = aName.dup
|
24
|
+
@parameters = parameterList
|
25
|
+
@body = aBody
|
26
|
+
@stack = aStack
|
27
|
+
end
|
28
|
+
|
29
|
+
def accept(_visitor)
|
30
|
+
stack.push self
|
31
|
+
end
|
32
|
+
|
33
|
+
def call(aVisitor)
|
34
|
+
body.empty? ? Datatype::Nil.instance : body.accept(aVisitor)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Logical negation.
|
38
|
+
# As a function is a truthy thing, its negation is thus false.
|
39
|
+
# @return [Datatype::False]
|
40
|
+
def !
|
41
|
+
Datatype::False.instance
|
42
|
+
end
|
43
|
+
|
44
|
+
# Text representation of a Lox function
|
45
|
+
def to_str
|
46
|
+
"<fn #{name}>"
|
47
|
+
end
|
48
|
+
end # class
|
49
|
+
# rubocop: enable Style/AccessorGrouping
|
50
|
+
end # module
|
51
|
+
end # module
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../error'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module BackEnd
|
7
|
+
# A Lox unary operator
|
8
|
+
class UnaryOperator
|
9
|
+
# @return [String] text representation of the operator
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @return [Array<Class>]
|
13
|
+
attr_reader :signatures
|
14
|
+
|
15
|
+
# @param aName [String] "name" of operator
|
16
|
+
# @param theSignatures [Array<Class>] allowed signatures
|
17
|
+
def initialize(aName, theSignatures)
|
18
|
+
@name = aName
|
19
|
+
@signatures = theSignatures
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_operand(operand1)
|
23
|
+
compliant = signatures.find { |some_type| operand1.kind_of?(some_type) }
|
24
|
+
|
25
|
+
unless compliant
|
26
|
+
err = Loxxy::RuntimeError
|
27
|
+
# if signatures.size == 1
|
28
|
+
raise err, "Operand must be a #{datatype_name(signatures[0])}."
|
29
|
+
# end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def datatype_name(aClass)
|
36
|
+
# (?:(?:[^:](?!:|(?<=LX))))+$
|
37
|
+
aClass.name.sub(/^.+(?=::)::(?:LX)?/, '').downcase
|
38
|
+
end
|
39
|
+
end # class
|
40
|
+
end # module
|
41
|
+
end # module
|
data/lib/loxxy/error.rb
ADDED
@@ -44,7 +44,7 @@ module Loxxy
|
|
44
44
|
rule('function_star' => 'function_star function')
|
45
45
|
rule('function_star' => [])
|
46
46
|
|
47
|
-
rule('funDecl' => 'FUN function')
|
47
|
+
rule('funDecl' => 'FUN function').as 'fun_decl'
|
48
48
|
|
49
49
|
rule('varDecl' => 'VAR IDENTIFIER SEMICOLON').as 'var_declaration'
|
50
50
|
rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
|
@@ -74,10 +74,10 @@ module Loxxy
|
|
74
74
|
rule('elsePart_opt' => [])
|
75
75
|
|
76
76
|
rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
|
77
|
-
rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
|
77
|
+
rule('returnStmt' => 'RETURN expression_opt SEMICOLON').as 'return_stmt'
|
78
78
|
rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as 'while_stmt'
|
79
79
|
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE').as 'block_stmt'
|
80
|
-
rule('block' => 'LEFT_BRACE RIGHT_BRACE')
|
80
|
+
rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
|
81
81
|
|
82
82
|
# Expressions: produce values
|
83
83
|
rule('expression_opt' => 'expression')
|
@@ -142,11 +142,11 @@ module Loxxy
|
|
142
142
|
rule('primary' => 'SUPER DOT IDENTIFIER')
|
143
143
|
|
144
144
|
# Utility rules
|
145
|
-
rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block')
|
145
|
+
rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
|
146
146
|
rule('params_opt' => 'parameters')
|
147
147
|
rule('params_opt' => [])
|
148
|
-
rule('parameters' => 'parameters COMMA IDENTIFIER')
|
149
|
-
rule('parameters' => 'IDENTIFIER')
|
148
|
+
rule('parameters' => 'parameters COMMA IDENTIFIER').as 'parameters_plus_more'
|
149
|
+
rule('parameters' => 'IDENTIFIER').as 'parameters_plus_end'
|
150
150
|
rule('arguments_opt' => 'arguments')
|
151
151
|
rule('arguments_opt' => [])
|
152
152
|
rule('arguments' => 'arguments COMMA expression').as 'arguments_plus_more'
|
@@ -11,7 +11,7 @@ module Loxxy
|
|
11
11
|
|
12
12
|
# Constructor.
|
13
13
|
# @param aValue [Datatype::BuiltinDatatype] the Lox data value
|
14
|
-
# @param
|
14
|
+
# @param aLexeme [String] the lexeme (= piece of text from input)
|
15
15
|
# @param aTerminal [Rley::Syntax::Terminal, String]
|
16
16
|
# The terminal symbol corresponding to the lexeme.
|
17
17
|
# @param aPosition [Rley::Lexical::Position] The position of lexeme
|
@@ -92,6 +92,7 @@ module Loxxy
|
|
92
92
|
|
93
93
|
private
|
94
94
|
|
95
|
+
# rubocop: disable Lint/DuplicateBranch
|
95
96
|
def _next_token
|
96
97
|
skip_intertoken_spaces
|
97
98
|
curr_ch = scanner.peek(1)
|
@@ -125,6 +126,7 @@ module Loxxy
|
|
125
126
|
|
126
127
|
return token
|
127
128
|
end
|
129
|
+
# rubocop: enable Lint/DuplicateBranch
|
128
130
|
|
129
131
|
def build_token(aSymbolName, aLexeme)
|
130
132
|
begin
|
data/lib/loxxy/version.rb
CHANGED
data/loxxy.gemspec
CHANGED
@@ -46,8 +46,8 @@ Gem::Specification.new do |spec|
|
|
46
46
|
spec.license = 'MIT'
|
47
47
|
spec.required_ruby_version = '~> 2.4'
|
48
48
|
|
49
|
-
spec.bindir = '
|
50
|
-
spec.executables =
|
49
|
+
spec.bindir = 'bin'
|
50
|
+
spec.executables = ['loxxy']
|
51
51
|
spec.require_paths = ['lib']
|
52
52
|
|
53
53
|
PkgExtending.pkg_files(spec)
|
data/spec/interpreter_spec.rb
CHANGED
@@ -9,6 +9,7 @@ require_relative '../lib/loxxy/interpreter'
|
|
9
9
|
module Loxxy
|
10
10
|
# This spec contains the bare bones test for the Interpreter class.
|
11
11
|
# The execution of Lox code is tested elsewhere.
|
12
|
+
# rubocop: disable Metrics/BlockLength
|
12
13
|
describe Interpreter do
|
13
14
|
let(:sample_cfg) do
|
14
15
|
{ ostream: StringIO.new }
|
@@ -386,18 +387,71 @@ LOX_END
|
|
386
387
|
|
387
388
|
it 'should implement nullary function calls' do
|
388
389
|
program = <<-LOX_END
|
389
|
-
print clock(); // Lox
|
390
|
+
print clock(); // Lox expects the 'clock' predefined native function
|
390
391
|
LOX_END
|
391
392
|
expect { subject.evaluate(program) }.not_to raise_error
|
392
393
|
tick = sample_cfg[:ostream].string
|
393
394
|
expect(Time.now.to_f - tick.to_f).to be < 0.1
|
394
395
|
end
|
395
396
|
|
397
|
+
it 'should implement function definition' do
|
398
|
+
program = <<-LOX_END
|
399
|
+
fun printSum(a, b) {
|
400
|
+
print a + b;
|
401
|
+
}
|
402
|
+
printSum(1, 2);
|
403
|
+
LOX_END
|
404
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
405
|
+
expect(sample_cfg[:ostream].string).to eq('3')
|
406
|
+
end
|
407
|
+
|
408
|
+
it 'should support functions with empty body' do
|
409
|
+
program = <<-LOX_END
|
410
|
+
fun f() {}
|
411
|
+
print f();
|
412
|
+
LOX_END
|
413
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
414
|
+
expect(sample_cfg[:ostream].string).to eq('nil')
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'should provide print representation of functions' do
|
418
|
+
program = <<-LOX_END
|
419
|
+
fun foo() {}
|
420
|
+
print foo; // output: <fn foo>
|
421
|
+
print clock; // output: <native fn>
|
422
|
+
LOX_END
|
423
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
424
|
+
expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'should support return statements' do
|
428
|
+
program = <<-LOX_END
|
429
|
+
fun max(a, b) {
|
430
|
+
if (a > b) return a;
|
431
|
+
|
432
|
+
return b;
|
433
|
+
}
|
434
|
+
|
435
|
+
max(3, 2);
|
436
|
+
LOX_END
|
437
|
+
result = subject.evaluate(program)
|
438
|
+
expect(result).to eq(3)
|
439
|
+
end
|
396
440
|
|
397
441
|
it 'should print the hello world message' do
|
398
442
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
399
443
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
400
444
|
end
|
401
445
|
end # context
|
446
|
+
|
447
|
+
context 'Test suite:' do
|
448
|
+
it "should complain if one argument isn't a number" do
|
449
|
+
source = '1 + nil;'
|
450
|
+
err = Loxxy::RuntimeError
|
451
|
+
err_msg = 'Operands must be two numbers or two strings.'
|
452
|
+
expect { subject.evaluate(source) }.to raise_error(err, err_msg)
|
453
|
+
end
|
454
|
+
end # context
|
402
455
|
end # describe
|
456
|
+
# rubocop: enable Metrics/BlockLength
|
403
457
|
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.
|
4
|
+
version: 0.1.04
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -69,7 +69,8 @@ dependencies:
|
|
69
69
|
description: An implementation of the Lox programming language. WIP
|
70
70
|
email:
|
71
71
|
- famished.tiger@yahoo.com
|
72
|
-
executables:
|
72
|
+
executables:
|
73
|
+
- loxxy
|
73
74
|
extensions: []
|
74
75
|
extra_rdoc_files:
|
75
76
|
- README.md
|
@@ -83,6 +84,7 @@ files:
|
|
83
84
|
- LICENSE.txt
|
84
85
|
- README.md
|
85
86
|
- Rakefile
|
87
|
+
- bin/loxxy
|
86
88
|
- lib/loxxy.rb
|
87
89
|
- lib/loxxy/ast/all_lox_nodes.rb
|
88
90
|
- lib/loxxy/ast/ast_builder.rb
|
@@ -93,6 +95,7 @@ files:
|
|
93
95
|
- lib/loxxy/ast/lox_call_expr.rb
|
94
96
|
- lib/loxxy/ast/lox_compound_expr.rb
|
95
97
|
- lib/loxxy/ast/lox_for_stmt.rb
|
98
|
+
- lib/loxxy/ast/lox_fun_stmt.rb
|
96
99
|
- lib/loxxy/ast/lox_grouping_expr.rb
|
97
100
|
- lib/loxxy/ast/lox_if_stmt.rb
|
98
101
|
- lib/loxxy/ast/lox_literal_expr.rb
|
@@ -100,15 +103,19 @@ files:
|
|
100
103
|
- lib/loxxy/ast/lox_node.rb
|
101
104
|
- lib/loxxy/ast/lox_noop_expr.rb
|
102
105
|
- lib/loxxy/ast/lox_print_stmt.rb
|
106
|
+
- lib/loxxy/ast/lox_return_stmt.rb
|
103
107
|
- lib/loxxy/ast/lox_seq_decl.rb
|
104
108
|
- lib/loxxy/ast/lox_unary_expr.rb
|
105
109
|
- lib/loxxy/ast/lox_var_stmt.rb
|
106
110
|
- lib/loxxy/ast/lox_variable_expr.rb
|
107
111
|
- lib/loxxy/ast/lox_while_stmt.rb
|
112
|
+
- lib/loxxy/back_end/binary_operator.rb
|
108
113
|
- lib/loxxy/back_end/engine.rb
|
109
114
|
- lib/loxxy/back_end/entry.rb
|
110
115
|
- lib/loxxy/back_end/environment.rb
|
116
|
+
- lib/loxxy/back_end/function.rb
|
111
117
|
- lib/loxxy/back_end/symbol_table.rb
|
118
|
+
- lib/loxxy/back_end/unary_operator.rb
|
112
119
|
- lib/loxxy/back_end/variable.rb
|
113
120
|
- lib/loxxy/datatype/all_datatypes.rb
|
114
121
|
- lib/loxxy/datatype/boolean.rb
|
@@ -118,6 +125,7 @@ files:
|
|
118
125
|
- lib/loxxy/datatype/nil.rb
|
119
126
|
- lib/loxxy/datatype/number.rb
|
120
127
|
- lib/loxxy/datatype/true.rb
|
128
|
+
- lib/loxxy/error.rb
|
121
129
|
- lib/loxxy/front_end/grammar.rb
|
122
130
|
- lib/loxxy/front_end/literal.rb
|
123
131
|
- lib/loxxy/front_end/parser.rb
|