loxxy 0.2.06 → 0.4.00
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +68 -4
- data/lib/loxxy/ast/ast_builder.rb +4 -3
- data/lib/loxxy/ast/ast_visitor.rb +0 -2
- data/lib/loxxy/ast/lox_class_stmt.rb +8 -5
- data/lib/loxxy/ast/lox_fun_stmt.rb +12 -5
- data/lib/loxxy/ast/lox_set_expr.rb +5 -2
- data/lib/loxxy/back_end/engine.rb +147 -50
- data/lib/loxxy/back_end/environment.rb +4 -0
- data/lib/loxxy/back_end/lox_class.rb +6 -8
- data/lib/loxxy/back_end/lox_function.rb +13 -7
- data/lib/loxxy/back_end/lox_instance.rb +14 -2
- data/lib/loxxy/back_end/resolver.rb +16 -8
- data/lib/loxxy/datatype/lx_string.rb +2 -3
- data/lib/loxxy/front_end/scanner.rb +55 -32
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +13 -3
- data/spec/back_end/environment_spec.rb +0 -14
- data/spec/back_end/symbol_table_spec.rb +0 -19
- data/spec/back_end/variable_spec.rb +0 -35
- data/spec/front_end/parser_spec.rb +4 -4
- data/spec/front_end/scanner_spec.rb +13 -10
- data/spec/interpreter_spec.rb +44 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47c5d65fa1f3c2d0025013577f46edf741572df743d8986e238f49a08ea32702
|
4
|
+
data.tar.gz: 72a3beb6828362e6dc7c73072555ed6f87ed5bf57b4052ac55e06503b9582e29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e83ee511c1ead4592153bb1e8ed8167439894bfefa2bb54060100eb9d221d77c453b4bad3d3e4a36209f3761812a5b9b0aa98b02a53556314c022d1f0b605bca
|
7
|
+
data.tar.gz: 8d939d7ff60687dd64c4dc6e4beccffa512e3483f5b38042a83fffbc0a5cc83d218999f855b8765a47e24b8c0df7808ecd649e70cec11832a0591b5c141a659c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,53 @@
|
|
1
|
+
## [0.4.00] - 2021-05-24
|
2
|
+
- Version bump. `Loxxy` is capable to run the LoxLox interpreter, an interpreter written in `Lox`.
|
3
|
+
|
4
|
+
### New
|
5
|
+
- Method `BackEnd::LoxInstance#falsey?` added
|
6
|
+
- Method `BackEnd::LoxInstance#truthy?` added
|
7
|
+
|
8
|
+
### Changed
|
9
|
+
- Method `BackEnd::Engine#after_variable_expr` the error message `Undefined variable` nows gives the location of the offending variable.
|
10
|
+
- Class `Ast::LoxClassStmt`is now a subclass of `LoxNode`
|
11
|
+
|
12
|
+
- File `README.md` added an explanation on how to run `LoxLox`interpreter.
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
- Method `Ast::LoxClassStmt#initialize` fixed inconsistencies in its Yard/RDoc documentation.
|
16
|
+
- Method `Ast::LoxFunStmt#initialize` fixed inconsistencies in its Yard/RDoc documentation.
|
17
|
+
- Method `BackEnd::Engine#native_getc` now returns -1 when its reaches EOF.
|
18
|
+
- Method `BackEnd::Resolver#after_logical_expr` was missing and this caused the lack of resultation in the second operand.
|
19
|
+
|
20
|
+
## [0.3.02] - 2021-05-22
|
21
|
+
- New built-in expressions `getc`, `chr`, `exit` and `print_eeror` , fixes with deeply nested returns, set expressions
|
22
|
+
|
23
|
+
### New
|
24
|
+
- Class `BackEnd::Engine` new built-in functions `getc`, `chr`, `exit`, and `print_error`
|
25
|
+
|
26
|
+
### Changed
|
27
|
+
- Class `Ast::LoxSetExpr` value to assign is now a distinct atrrbute instead of a subnode
|
28
|
+
- Class `BackEnd::Engine` two distinct stacks: one of expression evaluation, another for argument passing
|
29
|
+
|
30
|
+
### Fixed
|
31
|
+
- Class `BackEnd::LoxClass`: in some contexts the init method returned twice 'this'.
|
32
|
+
- Method `LoxFunction#call` mismatch between deeply nested returns and environments
|
33
|
+
|
34
|
+
|
35
|
+
## [0.3.01] - 2021-05-08
|
36
|
+
- Fix in `Scanner` class, added more tests in test suite.
|
37
|
+
|
38
|
+
### New
|
39
|
+
- Added the new subfolder `extra` under `test_suite`. It will contain tests for non-standard features or tests not covered in standard test suite.
|
40
|
+
|
41
|
+
### Fixed
|
42
|
+
- Class `FrontEnd::Scanner`: Couldn't correctly recognize a plus preceding directly a number literal
|
43
|
+
|
44
|
+
## [0.3.00] - 2021-05-07
|
45
|
+
- Milestone: `Loxxy` passes all reference test suite.
|
46
|
+
|
47
|
+
### Fixed
|
48
|
+
- Method `BackEnd::Resolver#before_variable_expr`: Standard `Lox` allows re-declaration of a variable at top-level scope
|
49
|
+
|
50
|
+
|
1
51
|
## [0.2.06] - 2021-05-04
|
2
52
|
- Nearly passing the 'official' test suite, fixing non-compliant behavior, specialized exceptions for errors
|
3
53
|
|
data/README.md
CHANGED
@@ -19,11 +19,18 @@ Although __Lox__ is fairly simple, it is far from being a toy language:
|
|
19
19
|
### Loxxy gem features
|
20
20
|
- Complete tree-walking interpreter including lexer, parser and resolver
|
21
21
|
- 100% pure Ruby with clean design (not a port from some other language)
|
22
|
+
- Passes the `jox` (THE reference `Lox` implementation) test suite
|
23
|
+
- Can run a Lox imterpreter implemented in ... `Lox` [LoxLox](https://github.com/benhoyt/loxlox),
|
22
24
|
- Minimal runtime dependency (Rley gem). Won't drag a bunch of gems...
|
23
25
|
- Ruby API for integrating a Lox interpreter with your code.
|
24
26
|
- A command-line interpreter `loxxy`
|
25
27
|
- Open for your language extensions...
|
26
28
|
|
29
|
+
### Why `Loxxy` ?
|
30
|
+
- If programming languages are one of your subject interest...
|
31
|
+
- ... and you wanted learn how to implement one in Ruby...
|
32
|
+
- ... then `Loxxy` can help to understand and experiment in this rewarding craft.
|
33
|
+
|
27
34
|
## How to start in 1, 2, 3...?
|
28
35
|
... in less than 3 minutes...
|
29
36
|
|
@@ -47,6 +54,7 @@ And then execute:
|
|
47
54
|
### 2. Your first `Lox` program
|
48
55
|
Create a text file and enter the following lines:
|
49
56
|
```javascript
|
57
|
+
// hello.lox
|
50
58
|
// Your firs Lox program
|
51
59
|
print "Hello, world.";
|
52
60
|
```
|
@@ -69,6 +77,7 @@ Let's admit it, the hello world example was unimpressive.
|
|
69
77
|
To a get a taste of `Lox` object-oriented capabilities, let's try another `Hello world` variant:
|
70
78
|
|
71
79
|
```javascript
|
80
|
+
// oo_hello.lox
|
72
81
|
// Object-oriented hello world
|
73
82
|
class Greeter {
|
74
83
|
// in Lox, initializers/constructors are named `init`
|
@@ -92,6 +101,7 @@ Our next assignment: compute the first 20 elements of the Fibbonacci sequence.
|
|
92
101
|
Here's an answer using the `while` loop construct:
|
93
102
|
|
94
103
|
```javascript
|
104
|
+
// fibbonacci.lox
|
95
105
|
// Compute the first 20 elements from the Fibbonacci sequence
|
96
106
|
|
97
107
|
var a = 0; // Use the var keyword to declare a new variable
|
@@ -121,6 +131,7 @@ Fans of `for` loops will be pleased to find their favorite looping construct.
|
|
121
131
|
Here again, the Fibbonacci sequence refactored with a `for` loop:
|
122
132
|
|
123
133
|
```javascript
|
134
|
+
// fibbonacci_v2.lox
|
124
135
|
// Fibbonacci sequence - version 2
|
125
136
|
var a = 0;
|
126
137
|
var b = 1;
|
@@ -163,13 +174,69 @@ for (var i = 0; i < count; i = i + 1) {
|
|
163
174
|
}
|
164
175
|
```
|
165
176
|
|
177
|
+
### Loxxy goes meta...
|
178
|
+
The `Loxxy` is able to run the `LoxLox` interpreter.
|
179
|
+
[LoxLox](https://github.com/benhoyt/loxlox) is a Lox interpreter written in Lox by Ben Hoyt.
|
180
|
+
This interpreter with over 1900 lines long is (one of) the longest Lox pragram.
|
181
|
+
As such, it is a good testbed for any Lox interpreter.
|
182
|
+
|
183
|
+
Executing a lox program with the LoxLox interpreter that is itself running on top of Loxxy.
|
184
|
+
#### Step 1 Download `lox.lox´ file
|
185
|
+
Download the [LoxLox](https://github.com/benhoyt/loxlox) source file in Github.
|
186
|
+
|
187
|
+
#### Step 2 (alternative a): running from the command line
|
188
|
+
|
189
|
+
```
|
190
|
+
$ loxxy lox.lox
|
191
|
+
```
|
192
|
+
|
193
|
+
Once loxxy CLI starts its interpreter that, in turn, executes the LoxLox interpreter.
|
194
|
+
This may take a couple of seconds.
|
195
|
+
Don't be surprised, if the program seems unresponsive: it is waiting for you input.
|
196
|
+
Enter a line like this:
|
197
|
+
```
|
198
|
+
print "Hello, world!";
|
199
|
+
```
|
200
|
+
Then terminate with an end of file (crtl-D on Linuxes, crtl-z on Windows) followed by an enter key.
|
201
|
+
You should see the famous greeting.
|
202
|
+
|
203
|
+
#### Step 2 (alternative b): launching the interpreter from Ruby snippet
|
204
|
+
The following snippet executes the LoxLox interpreter and feeds to it the
|
205
|
+
input text. That input text is made available through a StringIO that replaces
|
206
|
+
the `$stdio` device.
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
require 'stringio'
|
210
|
+
require 'loxxy'
|
211
|
+
|
212
|
+
# Place your Lox pragram within the heredoc
|
213
|
+
program = <<-LOX_END
|
214
|
+
print "Hello, world!";
|
215
|
+
LOX_END
|
216
|
+
|
217
|
+
lox_filename = 'lox.lox'
|
218
|
+
File.open(lox_filename, 'r') do |f|
|
219
|
+
source = f.read
|
220
|
+
cfg = { istream: StringIO.new(program, 'r')}
|
221
|
+
lox = Loxxy::Interpreter.new(cfg)
|
222
|
+
lox.evaluate(source)
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
Save this snippet as a Ruby file, launch Ruby with this file in command line.
|
227
|
+
After a couple of seconds, you'll see the Ruby interpreter that executes the
|
228
|
+
Loxxy interpreter that itself executes the LoxLox interpreter written in Lox.
|
229
|
+
That last interpreter is the one that run the hello world line.
|
230
|
+
|
231
|
+
That's definitively meta...
|
232
|
+
|
166
233
|
This completes our quick tour of `Lox`, to learn more about the language,
|
167
234
|
check the online book [Crafting Interpreters](https://craftinginterpreters.com/ )
|
168
235
|
|
169
236
|
## What's the fuss about Lox?
|
170
237
|
... Nothing...
|
171
238
|
Bob Nystrom designed a language __simple__ enough so that he could present
|
172
|
-
two implementations (
|
239
|
+
two interpreter implementations (a tree-walking one, then a bytecode one) in one single book.
|
173
240
|
|
174
241
|
In other words, __Lox__ contains interesting features found in most general-purpose
|
175
242
|
languages. In addition to that, there are [numerous implementations](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations) in different languages
|
@@ -196,14 +263,11 @@ There are already a number of programming languages derived from `Lox`...
|
|
196
263
|
### Purpose of this project:
|
197
264
|
- To deliver an open source example of a programming language fully implemented in Ruby
|
198
265
|
(from the scanner and parser to an interpreter).
|
199
|
-
- The implementation should be mature enough to run [LoxLox](https://github.com/benhoyt/loxlox),
|
200
|
-
a Lox interpreter written in Lox.
|
201
266
|
|
202
267
|
### Roadmap
|
203
268
|
- Extend the test suite
|
204
269
|
- Improve the error handling
|
205
270
|
- Improve the documentation
|
206
|
-
- Ability run the LoxLox interpreter
|
207
271
|
|
208
272
|
## Hello world example
|
209
273
|
The next examples show how to use the interpreter directly from Ruby code.
|
@@ -318,7 +318,7 @@ module Loxxy
|
|
318
318
|
name_assignee = theChildren[1].token.lexeme.dup
|
319
319
|
if theChildren[0].kind_of?(Ast::LoxSetExpr)
|
320
320
|
theChildren[0].property = name_assignee
|
321
|
-
theChildren[0].
|
321
|
+
theChildren[0].value = theChildren[3]
|
322
322
|
theChildren[0]
|
323
323
|
else
|
324
324
|
Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
|
@@ -395,9 +395,10 @@ module Loxxy
|
|
395
395
|
end
|
396
396
|
|
397
397
|
# rule('primary' => 'IDENTIFIER')
|
398
|
-
def reduce_variable_expr(_production, _range,
|
398
|
+
def reduce_variable_expr(_production, _range, _tokens, theChildren)
|
399
399
|
var_name = theChildren[0].token.lexeme
|
400
|
-
|
400
|
+
pos = theChildren[0].token.position
|
401
|
+
LoxVariableExpr.new(pos, var_name)
|
401
402
|
end
|
402
403
|
|
403
404
|
# rule('primary' => 'THIS')
|
@@ -71,7 +71,6 @@ module Loxxy
|
|
71
71
|
# @param aClassStmt [AST::LOXClassStmt] the for statement node to visit
|
72
72
|
def visit_class_stmt(aClassStmt)
|
73
73
|
broadcast(:before_class_stmt, aClassStmt)
|
74
|
-
traverse_subnodes(aClassStmt) # The methods are visited here...
|
75
74
|
broadcast(:after_class_stmt, aClassStmt, self)
|
76
75
|
end
|
77
76
|
|
@@ -126,7 +125,6 @@ module Loxxy
|
|
126
125
|
# @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
|
127
126
|
def visit_set_expr(aSetExpr)
|
128
127
|
broadcast(:before_set_expr, aSetExpr, self)
|
129
|
-
traverse_subnodes(aSetExpr)
|
130
128
|
broadcast(:after_set_expr, aSetExpr, self)
|
131
129
|
end
|
132
130
|
|
@@ -1,10 +1,11 @@
|
|
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
|
+
# A parse tree node that represents a Lox class declaration.
|
8
|
+
class LoxClassStmt < LoxNode
|
8
9
|
# @return [String] the class name
|
9
10
|
attr_reader :name
|
10
11
|
|
@@ -14,11 +15,13 @@ module Loxxy
|
|
14
15
|
# @return [Array<Ast::LoxFunStmt>] the methods
|
15
16
|
attr_reader :body
|
16
17
|
|
18
|
+
# Constructor for a parse node that represents a Lox function declaration
|
17
19
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
18
|
-
# @param
|
19
|
-
# @param
|
20
|
+
# @param aName [String] the class name
|
21
|
+
# @param aSuperclassName [String] the super class name
|
22
|
+
# @param theMethods [Array<Loxxy::Ast::LoxFunStmt>] the methods
|
20
23
|
def initialize(aPosition, aName, aSuperclassName, theMethods)
|
21
|
-
super(aPosition
|
24
|
+
super(aPosition)
|
22
25
|
@name = aName.dup
|
23
26
|
@superclass = aSuperclassName
|
24
27
|
@body = theMethods
|
@@ -4,17 +4,25 @@ require_relative 'lox_node'
|
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
|
-
#
|
7
|
+
# A parse tree node that represents a Lox function declaration.
|
8
8
|
class LoxFunStmt < LoxNode
|
9
|
+
# @return [String] the function name
|
9
10
|
attr_reader :name
|
11
|
+
|
12
|
+
# @return [Array<String>] the parameter names
|
10
13
|
attr_reader :params
|
14
|
+
|
15
|
+
# @return [Ast::LoxBlockStmt] the parse tree representing the function's body
|
11
16
|
attr_reader :body
|
17
|
+
|
18
|
+
# @return [Boolean] true if the function is a method
|
12
19
|
attr_accessor :is_method
|
13
20
|
|
21
|
+
# Constructor for a parse node that represents a Lox function declaration
|
14
22
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
|
-
# @param aName [String]
|
16
|
-
# @param
|
17
|
-
# @param
|
23
|
+
# @param aName [String] the function name
|
24
|
+
# @param paramList [Array<String>] the parameter names
|
25
|
+
# @param aBody [Ast::LoxBlockStmt] the parse tree representing the function's body
|
18
26
|
def initialize(aPosition, aName, paramList, aBody)
|
19
27
|
super(aPosition)
|
20
28
|
@name = aName.dup
|
@@ -25,6 +33,5 @@ module Loxxy
|
|
25
33
|
|
26
34
|
define_accept # Add `accept` method as found in Visitor design pattern
|
27
35
|
end # class
|
28
|
-
# rubocop: enable Style/AccessorGrouping
|
29
36
|
end # module
|
30
37
|
end # module
|
@@ -4,17 +4,20 @@ require_relative 'lox_compound_expr'
|
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
|
-
class LoxSetExpr <
|
7
|
+
class LoxSetExpr < LoxNode
|
8
8
|
# @return [Ast::LoxNode] the object to which the property belongs to
|
9
9
|
attr_reader :object
|
10
10
|
|
11
11
|
# @return [String] Name of an object property
|
12
12
|
attr_accessor :property
|
13
13
|
|
14
|
+
# @return [LoxNode, Datatype] value to assign
|
15
|
+
attr_accessor :value
|
16
|
+
|
14
17
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
18
|
# @param anObject [Ast::LoxNode] The object which the given property is being set
|
16
19
|
def initialize(aPosition, anObject)
|
17
|
-
super(aPosition
|
20
|
+
super(aPosition)
|
18
21
|
@object = anObject
|
19
22
|
end
|
20
23
|
|
@@ -20,7 +20,7 @@ module Loxxy
|
|
20
20
|
# @return [BackEnd::SymbolTable]
|
21
21
|
attr_reader :symbol_table
|
22
22
|
|
23
|
-
# @return [Array<Datatype::BuiltinDatatype>] Data stack for
|
23
|
+
# @return [Array<Datatype::BuiltinDatatype>] Data stack for arguments and return results
|
24
24
|
attr_reader :stack
|
25
25
|
|
26
26
|
# @return [Hash { Symbol => UnaryOperator}]
|
@@ -35,17 +35,31 @@ module Loxxy
|
|
35
35
|
# @param theOptions [Hash]
|
36
36
|
def initialize(theOptions)
|
37
37
|
@config = theOptions
|
38
|
+
@istream = config.include?(:istream) ? config[:istream] : $stdin
|
38
39
|
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
39
40
|
@symbol_table = SymbolTable.new
|
40
41
|
@stack = []
|
41
|
-
@unary_operators = {}
|
42
|
-
@binary_operators = {}
|
43
42
|
|
43
|
+
reset_expr_stack
|
44
44
|
init_unary_operators
|
45
45
|
init_binary_operators
|
46
46
|
init_globals
|
47
47
|
end
|
48
48
|
|
49
|
+
# Returns the current environment
|
50
|
+
# @return [Loxxy::BackEnd::Environment]
|
51
|
+
def current_env
|
52
|
+
symbol_table.current_env
|
53
|
+
end
|
54
|
+
|
55
|
+
def expr_stack
|
56
|
+
current_env.expr_stack
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_expr_stack
|
60
|
+
current_env.expr_stack.clear
|
61
|
+
end
|
62
|
+
|
49
63
|
# Given an abstract syntax parse tree visitor, launch the visit
|
50
64
|
# and execute the visit events in the output stream.
|
51
65
|
# @param aVisitor [AST::ASTVisitor]
|
@@ -58,7 +72,7 @@ module Loxxy
|
|
58
72
|
aVisitor.subscribe(self)
|
59
73
|
aVisitor.start
|
60
74
|
aVisitor.unsubscribe(self)
|
61
|
-
|
75
|
+
expr_stack.empty? ? Datatype::Nil.instance : expr_stack.pop
|
62
76
|
end
|
63
77
|
|
64
78
|
##########################################################################
|
@@ -69,10 +83,14 @@ module Loxxy
|
|
69
83
|
# Do nothing, subnodes were already evaluated
|
70
84
|
end
|
71
85
|
|
86
|
+
def before_class_stmt(_class_stmt)
|
87
|
+
reset_expr_stack
|
88
|
+
end
|
89
|
+
|
72
90
|
def after_class_stmt(aClassStmt, aVisitor)
|
73
91
|
if aClassStmt.superclass
|
74
92
|
aClassStmt.superclass.accept(aVisitor)
|
75
|
-
parent =
|
93
|
+
parent = expr_stack.pop
|
76
94
|
unless parent.kind_of?(LoxClass)
|
77
95
|
raise Loxxy::RuntimeError, 'Superclass must be a class.'
|
78
96
|
end
|
@@ -89,7 +107,7 @@ module Loxxy
|
|
89
107
|
meths = aClassStmt.body.map do |func_node|
|
90
108
|
func_node.is_method = true
|
91
109
|
func_node.accept(aVisitor)
|
92
|
-
mth =
|
110
|
+
mth = expr_stack.pop
|
93
111
|
mth.is_initializer = true if mth.name == 'init'
|
94
112
|
mth
|
95
113
|
end
|
@@ -104,11 +122,15 @@ module Loxxy
|
|
104
122
|
symbol_table.insert(new_var)
|
105
123
|
end
|
106
124
|
|
125
|
+
def before_var_stmt(_var_stmt)
|
126
|
+
reset_expr_stack
|
127
|
+
end
|
128
|
+
|
107
129
|
def after_var_stmt(aVarStmt)
|
108
130
|
new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
|
109
131
|
symbol_table.insert(new_var)
|
110
132
|
|
111
|
-
value =
|
133
|
+
value = expr_stack.pop
|
112
134
|
new_var.assign(value)
|
113
135
|
end
|
114
136
|
|
@@ -116,9 +138,14 @@ module Loxxy
|
|
116
138
|
before_block_stmt(aForStmt)
|
117
139
|
end
|
118
140
|
|
141
|
+
def before_if_stmt(_if_stmt)
|
142
|
+
reset_expr_stack
|
143
|
+
end
|
144
|
+
|
119
145
|
def after_if_stmt(anIfStmt, aVisitor)
|
120
146
|
# Retrieve the result of the condition evaluation
|
121
|
-
condition = stack.pop
|
147
|
+
# condition = stack.pop
|
148
|
+
condition = expr_stack.pop
|
122
149
|
if condition.truthy?
|
123
150
|
anIfStmt.then_stmt.accept(aVisitor)
|
124
151
|
elsif anIfStmt.else_stmt
|
@@ -126,18 +153,31 @@ module Loxxy
|
|
126
153
|
end
|
127
154
|
end
|
128
155
|
|
156
|
+
def before_print_stmt(_print_stmt)
|
157
|
+
reset_expr_stack
|
158
|
+
end
|
159
|
+
|
129
160
|
def after_print_stmt(_printStmt)
|
130
|
-
tos =
|
161
|
+
tos = expr_stack.pop
|
131
162
|
@ostream.print tos ? tos.to_str : 'nil'
|
132
163
|
end
|
133
164
|
|
165
|
+
def before_return_stmt(_return_stmt)
|
166
|
+
reset_expr_stack
|
167
|
+
end
|
168
|
+
|
134
169
|
def after_return_stmt(_returnStmt, _aVisitor)
|
170
|
+
stack.push(expr_stack.pop)
|
135
171
|
throw(:return)
|
136
172
|
end
|
137
173
|
|
174
|
+
def before_while_stmt(_while_stmt)
|
175
|
+
reset_expr_stack
|
176
|
+
end
|
177
|
+
|
138
178
|
def after_while_stmt(aWhileStmt, aVisitor)
|
139
179
|
loop do
|
140
|
-
condition =
|
180
|
+
condition = expr_stack.pop
|
141
181
|
break unless condition.truthy?
|
142
182
|
|
143
183
|
aWhileStmt.body.accept(aVisitor)
|
@@ -146,6 +186,7 @@ module Loxxy
|
|
146
186
|
end
|
147
187
|
|
148
188
|
def before_block_stmt(_aBlockStmt)
|
189
|
+
reset_expr_stack
|
149
190
|
new_env = Environment.new
|
150
191
|
symbol_table.enter_environment(new_env)
|
151
192
|
end
|
@@ -159,40 +200,42 @@ module Loxxy
|
|
159
200
|
variable = variable_lookup(anAssignExpr)
|
160
201
|
raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable
|
161
202
|
|
162
|
-
value =
|
203
|
+
value = expr_stack.last # ToS remains since an assignment produces a value
|
163
204
|
variable.assign(value)
|
164
205
|
end
|
165
206
|
|
166
|
-
def before_set_expr(
|
167
|
-
|
168
|
-
aSetExpr.object.accept(aVisitor)
|
207
|
+
def before_set_expr(_set_expr, _visitor)
|
208
|
+
reset_expr_stack
|
169
209
|
end
|
170
210
|
|
171
|
-
def after_set_expr(aSetExpr,
|
172
|
-
|
173
|
-
|
211
|
+
def after_set_expr(aSetExpr, aVisitor)
|
212
|
+
# Evaluate receiver object part (i.e. 'this')
|
213
|
+
aSetExpr.object.accept(aVisitor)
|
214
|
+
assignee = expr_stack.pop
|
174
215
|
unless assignee.kind_of?(LoxInstance)
|
175
216
|
raise Loxxy::RuntimeError, 'Only instances have fields.'
|
176
217
|
end
|
177
218
|
|
219
|
+
aSetExpr.value.accept(aVisitor)
|
220
|
+
value = expr_stack.pop
|
221
|
+
|
178
222
|
assignee.set(aSetExpr.property, value)
|
179
|
-
|
223
|
+
expr_stack.push value
|
180
224
|
end
|
181
225
|
|
182
226
|
def after_logical_expr(aLogicalExpr, visitor)
|
183
227
|
op = aLogicalExpr.operator
|
184
|
-
operand1 =
|
228
|
+
operand1 = expr_stack.pop # only first operand was evaluated
|
185
229
|
result = nil
|
186
230
|
if ((op == :and) && operand1.falsey?) || ((op == :or) && operand1.truthy?)
|
187
231
|
result = operand1
|
188
232
|
else
|
189
233
|
raw_operand2 = aLogicalExpr.subnodes[1]
|
190
234
|
raw_operand2.accept(visitor) # Visit means operand2 is evaluated
|
191
|
-
operand2 =
|
235
|
+
operand2 = expr_stack.pop
|
192
236
|
result = logical_2nd_arg(operand2)
|
193
237
|
end
|
194
|
-
|
195
|
-
stack.push result
|
238
|
+
expr_stack.push result
|
196
239
|
end
|
197
240
|
|
198
241
|
def logical_2nd_arg(operand2)
|
@@ -212,14 +255,14 @@ module Loxxy
|
|
212
255
|
end
|
213
256
|
|
214
257
|
def after_binary_expr(aBinaryExpr)
|
215
|
-
operand2 =
|
216
|
-
operand1 =
|
258
|
+
operand2 = expr_stack.pop
|
259
|
+
operand1 = expr_stack.pop
|
217
260
|
op = aBinaryExpr.operator
|
218
261
|
operator = binary_operators[op]
|
219
262
|
operator.validate_operands(operand1, operand2)
|
220
263
|
if operand1.respond_to?(op)
|
221
264
|
result = operand1.send(op, operand2)
|
222
|
-
|
265
|
+
expr_stack.push convert2lox_datatype(result)
|
223
266
|
else
|
224
267
|
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
225
268
|
raise StandardError, msg1
|
@@ -227,13 +270,13 @@ module Loxxy
|
|
227
270
|
end
|
228
271
|
|
229
272
|
def after_unary_expr(anUnaryExpr)
|
230
|
-
operand =
|
273
|
+
operand = expr_stack.pop
|
231
274
|
op = anUnaryExpr.operator
|
232
275
|
operator = unary_operators[op]
|
233
276
|
operator.validate_operand(operand)
|
234
277
|
if operand.respond_to?(op)
|
235
278
|
result = operand.send(op)
|
236
|
-
|
279
|
+
expr_stack.push convert2lox_datatype(result)
|
237
280
|
else
|
238
281
|
msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
|
239
282
|
raise StandardError, msg1
|
@@ -243,12 +286,17 @@ module Loxxy
|
|
243
286
|
def after_call_expr(aCallExpr, aVisitor)
|
244
287
|
# Evaluate callee part
|
245
288
|
aCallExpr.callee.accept(aVisitor)
|
246
|
-
callee =
|
289
|
+
callee = expr_stack.pop
|
290
|
+
before_size = expr_stack.size
|
247
291
|
aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
|
292
|
+
after_size = expr_stack.size
|
293
|
+
if after_size > before_size
|
294
|
+
stack.concat(expr_stack.pop(after_size - before_size))
|
295
|
+
end
|
248
296
|
|
249
297
|
case callee
|
250
298
|
when NativeFunction
|
251
|
-
|
299
|
+
expr_stack.push callee.call # Pass arguments
|
252
300
|
when LoxFunction, LoxClass
|
253
301
|
arg_count = aCallExpr.arguments.size
|
254
302
|
if arg_count != callee.arity
|
@@ -263,12 +311,12 @@ module Loxxy
|
|
263
311
|
|
264
312
|
def after_get_expr(aGetExpr, aVisitor)
|
265
313
|
aGetExpr.object.accept(aVisitor)
|
266
|
-
instance =
|
314
|
+
instance = expr_stack.pop
|
267
315
|
unless instance.kind_of?(LoxInstance)
|
268
316
|
raise Loxxy::RuntimeError, 'Only instances have properties.'
|
269
317
|
end
|
270
318
|
|
271
|
-
|
319
|
+
expr_stack.push instance.get(aGetExpr.property)
|
272
320
|
end
|
273
321
|
|
274
322
|
def after_grouping_expr(_groupingExpr)
|
@@ -278,14 +326,17 @@ module Loxxy
|
|
278
326
|
def after_variable_expr(aVarExpr, aVisitor)
|
279
327
|
var_name = aVarExpr.name
|
280
328
|
var = variable_lookup(aVarExpr)
|
281
|
-
|
329
|
+
unless var
|
330
|
+
pos = "line #{aVarExpr.position.line}:#{aVarExpr.position.column}"
|
331
|
+
raise Loxxy::RuntimeError, "[#{pos}] Undefined variable '#{var_name}'."
|
332
|
+
end
|
282
333
|
|
283
334
|
var.value.accept(aVisitor) # Evaluate variable value then push on stack
|
284
335
|
end
|
285
336
|
|
286
337
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
287
338
|
def before_literal_expr(literalExpr)
|
288
|
-
|
339
|
+
expr_stack.push(literalExpr.literal)
|
289
340
|
end
|
290
341
|
|
291
342
|
def after_this_expr(aThisExpr, aVisitor)
|
@@ -304,18 +355,22 @@ module Loxxy
|
|
304
355
|
raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
|
305
356
|
end
|
306
357
|
|
307
|
-
|
358
|
+
expr_stack.push method.bind(instance)
|
308
359
|
end
|
309
360
|
|
310
361
|
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
311
362
|
def before_visit_builtin(aValue)
|
312
|
-
|
363
|
+
expr_stack.push(aValue)
|
364
|
+
end
|
365
|
+
|
366
|
+
def before_fun_stmt(_fun_stmt, _visitor)
|
367
|
+
reset_expr_stack
|
313
368
|
end
|
314
369
|
|
315
370
|
def after_fun_stmt(aFunStmt, _visitor)
|
316
371
|
function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
|
317
372
|
if aFunStmt.is_method
|
318
|
-
|
373
|
+
expr_stack.push function
|
319
374
|
else
|
320
375
|
new_var = Variable.new(aFunStmt.name, function)
|
321
376
|
symbol_table.insert(new_var)
|
@@ -337,21 +392,9 @@ module Loxxy
|
|
337
392
|
env.defns[aVarNode.name]
|
338
393
|
end
|
339
394
|
|
340
|
-
NativeFunction = Struct.new(:callable, :interp) do
|
341
|
-
def accept(_visitor)
|
342
|
-
interp.stack.push self
|
343
|
-
end
|
344
|
-
|
345
|
-
def call
|
346
|
-
callable.call
|
347
|
-
end
|
348
|
-
|
349
|
-
def to_str
|
350
|
-
'<native fn>'
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
395
|
def init_unary_operators
|
396
|
+
@unary_operators = {}
|
397
|
+
|
355
398
|
negate_op = UnaryOperator.new('-', [Datatype::Number])
|
356
399
|
unary_operators[:-@] = negate_op
|
357
400
|
|
@@ -361,6 +404,8 @@ module Loxxy
|
|
361
404
|
end
|
362
405
|
|
363
406
|
def init_binary_operators
|
407
|
+
@binary_operators = {}
|
408
|
+
|
364
409
|
plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
|
365
410
|
[Datatype::LXString, :idem]])
|
366
411
|
binary_operators[:+] = plus_op
|
@@ -395,6 +440,24 @@ module Loxxy
|
|
395
440
|
|
396
441
|
def init_globals
|
397
442
|
add_native_fun('clock', native_clock)
|
443
|
+
add_native_fun('getc', native_getc)
|
444
|
+
add_native_fun('chr', native_chr)
|
445
|
+
add_native_fun('exit', native_exit)
|
446
|
+
add_native_fun('print_error', native_print_error)
|
447
|
+
end
|
448
|
+
|
449
|
+
NativeFunction = Struct.new(:callable, :interp) do
|
450
|
+
def accept(_visitor)
|
451
|
+
interp.expr_stack.push self
|
452
|
+
end
|
453
|
+
|
454
|
+
def call
|
455
|
+
callable.call
|
456
|
+
end
|
457
|
+
|
458
|
+
def to_str
|
459
|
+
'<native fn>'
|
460
|
+
end
|
398
461
|
end
|
399
462
|
|
400
463
|
def add_native_fun(aName, aProc)
|
@@ -411,6 +474,40 @@ module Loxxy
|
|
411
474
|
end
|
412
475
|
end
|
413
476
|
|
477
|
+
# Read a single character and return the character code as an integer.
|
478
|
+
# LoxLox requires the end of input to be a negative number
|
479
|
+
def native_getc
|
480
|
+
proc do
|
481
|
+
ch = @istream.getc
|
482
|
+
val = ch ? ch.codepoints[0] : -1
|
483
|
+
Datatype::Number.new(val)
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
# chr(ch): Convert given character code number to a single-character string.
|
488
|
+
def native_chr
|
489
|
+
proc do
|
490
|
+
codepoint = stack.pop
|
491
|
+
Datatype::LXString.new(codepoint.value.chr)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# exit(status): Exit with given status code.
|
496
|
+
def native_exit
|
497
|
+
proc do
|
498
|
+
status = stack.pop
|
499
|
+
exit(status.value)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# print_error(message): Print message string on stderr.
|
504
|
+
def native_print_error
|
505
|
+
proc do
|
506
|
+
message = stack.pop
|
507
|
+
$stderr.print message.value
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
414
511
|
def convert2lox_datatype(item)
|
415
512
|
case item
|
416
513
|
when TrueClass then Datatype::True.instance
|