loxxy 0.1.0 → 0.1.05
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 +52 -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 +1 -0
- data/lib/loxxy/ast/ast_builder.rb +7 -2
- data/lib/loxxy/ast/ast_visitor.rb +8 -0
- data/lib/loxxy/ast/lox_fun_stmt.rb +2 -0
- 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 +78 -16
- data/lib/loxxy/back_end/function.rb +25 -6
- data/lib/loxxy/back_end/unary_operator.rb +41 -0
- data/lib/loxxy/error.rb +9 -0
- data/lib/loxxy/front_end/grammar.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 +25 -0
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8eae41b5b093b55c02a81bbe81b08202eb4ecd64c1dd288246c0cd4af44c627
|
4
|
+
data.tar.gz: 208158a80e89e789d74c66f295ee4cbec6d37e6d9f3185dedfbcaa30a156fe35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc1393417a3cdb013fdbcc085dcb3ba7dda45784e73a26ef44c1ee21b4322a9a18063d16c0cae7d3266be829ce31c6090773ba1631712211c388d84b21e0161d
|
7
|
+
data.tar.gz: 2f07cb2e882fbff31b73eee169a99849729b8f4136888335b2915a183ff32ed8aa78a66d93ea7765f0b09f0747b4b09cfae2aece5ca7478f41892b173a0ce2ab
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,53 @@
|
|
1
|
+
## [0.1.05] - 2021-03-05
|
2
|
+
- Test for Fibbonacci recursive function is now passing.
|
3
|
+
|
4
|
+
### Fixed
|
5
|
+
- Method `BackEnd::Function#call` a call doesn't no more generate of TWO scopes
|
6
|
+
|
7
|
+
## [0.1.04] - 2021-02-28
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Class `Ast::LoxReturnStmt` a node that represents a return statement
|
11
|
+
- Method `Ast::ASTBuilder#reduce_return_stmt`
|
12
|
+
- Method `Ast::ASTVisitor#visit_return_stmt` for visiting an `Ast::LoxReturnStmt` node
|
13
|
+
- Method `BackEnd::Engine#after_return_stmt` to handle return statement
|
14
|
+
- Method `BackEnd::Function#!` implementing the logical negation of a function (as value).
|
15
|
+
- Test suite for logical operators (in project repository)
|
16
|
+
- Test suite for block code
|
17
|
+
- Test suite for call and function declaration (initial)
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
- Method `BackEnd::Engine#after_call_expr` now generate a `catch` and `throw` events
|
21
|
+
|
22
|
+
## [0.1.03] - 2021-02-26
|
23
|
+
- Runtime argument checking for arithmetic and comparison operators
|
24
|
+
|
25
|
+
### Added
|
26
|
+
- Test suite for arithmetic and comparison operators (in project repository)
|
27
|
+
- Class `BackEnd::UnaryOperator`: runtime argument validation
|
28
|
+
- Class `BackEnd::BinaryOperator`: runtime argument validation
|
29
|
+
|
30
|
+
### Changed
|
31
|
+
- File `console` renamed to `loxxy`. Very basic command-line interface.
|
32
|
+
- Custom exception classes
|
33
|
+
- File `README.md` updated list of supported `Lox` keywords.
|
34
|
+
|
35
|
+
|
36
|
+
## [0.1.02] - 2021-02-21
|
37
|
+
- Function definition and call documented in `README.md`
|
38
|
+
|
39
|
+
### Changed
|
40
|
+
- File `README.md` updated description of function definition and function call.
|
41
|
+
|
42
|
+
### Fixed
|
43
|
+
- Method `BackEnd::Engine#after_print_stmt` now handles of empty stack or nil data.
|
44
|
+
- Method `BackEnd::Engine#after_call_expr` was pushing one spurious item onto data stack.
|
45
|
+
|
46
|
+
## [0.1.01] - 2021-02-20
|
47
|
+
### Fixed
|
48
|
+
- Fixed most offences for Rubocop.
|
49
|
+
|
50
|
+
|
1
51
|
## [0.1.00] - 2021-02-20
|
2
52
|
- Version number bumped, `Loxxy` supports function definitions
|
3
53
|
|
@@ -192,11 +242,11 @@
|
|
192
242
|
- The interpreter can evaluate substraction between two numbers.
|
193
243
|
|
194
244
|
### Added
|
195
|
-
- Method `Datatype::Number#-`
|
245
|
+
- Method `Datatype::Number#-` implementing the subtraction operation
|
196
246
|
|
197
247
|
### Changed
|
198
248
|
- File `README.md` minor editorial changes.
|
199
|
-
- File `lx_string_spec.rb` Added test for string
|
249
|
+
- File `lx_string_spec.rb` Added test for string concatenation
|
200
250
|
- File `number_spec.rb` Added tests for addition and subtraction operations
|
201
251
|
- File `interpreter_spec.rb` Added tests for subtraction operation
|
202
252
|
|
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]
|
@@ -12,6 +12,7 @@ require_relative 'lox_logical_expr'
|
|
12
12
|
require_relative 'lox_assign_expr'
|
13
13
|
require_relative 'lox_block_stmt'
|
14
14
|
require_relative 'lox_while_stmt'
|
15
|
+
require_relative 'lox_return_stmt'
|
15
16
|
require_relative 'lox_print_stmt'
|
16
17
|
require_relative 'lox_if_stmt'
|
17
18
|
require_relative 'lox_for_stmt'
|
@@ -221,6 +221,11 @@ module Loxxy
|
|
221
221
|
Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
|
222
222
|
end
|
223
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
|
+
|
224
229
|
# rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as ''
|
225
230
|
def reduce_while_stmt(_production, _range, tokens, theChildren)
|
226
231
|
Ast::LoxWhileStmt.new(tokens[1].position, theChildren[2], theChildren[4])
|
@@ -233,7 +238,7 @@ module Loxxy
|
|
233
238
|
end
|
234
239
|
|
235
240
|
# rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
|
236
|
-
def reduce_block_empty(_production, _range, tokens,
|
241
|
+
def reduce_block_empty(_production, _range, tokens, _children)
|
237
242
|
Ast::LoxBlockStmt.new(tokens[0].position, nil)
|
238
243
|
end
|
239
244
|
|
@@ -298,7 +303,7 @@ module Loxxy
|
|
298
303
|
def reduce_function(_production, _range, _tokens, theChildren)
|
299
304
|
first_child = theChildren.first
|
300
305
|
pos = first_child.token.position
|
301
|
-
|
306
|
+
LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
|
302
307
|
end
|
303
308
|
|
304
309
|
# rule('parameters' => 'parameters COMMA IDENTIFIER')
|
@@ -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)
|
@@ -4,6 +4,7 @@ require_relative 'lox_compound_expr'
|
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
|
+
# rubocop: disable Style/AccessorGrouping
|
7
8
|
class LoxFunStmt < LoxCompoundExpr
|
8
9
|
attr_reader :name
|
9
10
|
attr_reader :params
|
@@ -28,5 +29,6 @@ module Loxxy
|
|
28
29
|
|
29
30
|
alias operands subnodes
|
30
31
|
end # class
|
32
|
+
# rubocop: enable Style/AccessorGrouping
|
31
33
|
end # module
|
32
34
|
end # module
|
@@ -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,8 +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'
|
5
6
|
require_relative 'function'
|
6
7
|
require_relative 'symbol_table'
|
8
|
+
require_relative 'unary_operator'
|
7
9
|
|
8
10
|
module Loxxy
|
9
11
|
module BackEnd
|
@@ -17,16 +19,26 @@ module Loxxy
|
|
17
19
|
# @return [BackEnd::SymbolTable]
|
18
20
|
attr_reader :symbol_table
|
19
21
|
|
20
|
-
# @return [Array<Datatype::
|
22
|
+
# @return [Array<Datatype::BuiltinDatatype>] Data stack for the expression results
|
21
23
|
attr_reader :stack
|
22
24
|
|
25
|
+
# @return [Hash { Symbol => UnaryOperator}]
|
26
|
+
attr_reader :unary_operators
|
27
|
+
|
28
|
+
# @return [Hash { Symbol => BinaryOperator}]
|
29
|
+
attr_reader :binary_operators
|
30
|
+
|
23
31
|
# @param theOptions [Hash]
|
24
32
|
def initialize(theOptions)
|
25
33
|
@config = theOptions
|
26
34
|
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
27
35
|
@symbol_table = SymbolTable.new
|
28
36
|
@stack = []
|
37
|
+
@unary_operators = {}
|
38
|
+
@binary_operators = {}
|
29
39
|
|
40
|
+
init_unary_operators
|
41
|
+
init_binary_operators
|
30
42
|
init_globals
|
31
43
|
end
|
32
44
|
|
@@ -83,7 +95,11 @@ module Loxxy
|
|
83
95
|
|
84
96
|
def after_print_stmt(_printStmt)
|
85
97
|
tos = stack.pop
|
86
|
-
@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)
|
87
103
|
end
|
88
104
|
|
89
105
|
def after_while_stmt(aWhileStmt, aVisitor)
|
@@ -148,9 +164,11 @@ module Loxxy
|
|
148
164
|
end
|
149
165
|
|
150
166
|
def after_binary_expr(aBinaryExpr)
|
151
|
-
op = aBinaryExpr.operator
|
152
167
|
operand2 = stack.pop
|
153
168
|
operand1 = stack.pop
|
169
|
+
op = aBinaryExpr.operator
|
170
|
+
operator = binary_operators[op]
|
171
|
+
operator.validate_operands(operand1, operand2)
|
154
172
|
if operand1.respond_to?(op)
|
155
173
|
stack.push operand1.send(op, operand2)
|
156
174
|
else
|
@@ -160,12 +178,14 @@ module Loxxy
|
|
160
178
|
end
|
161
179
|
|
162
180
|
def after_unary_expr(anUnaryExpr)
|
163
|
-
op = anUnaryExpr.operator
|
164
181
|
operand = stack.pop
|
182
|
+
op = anUnaryExpr.operator
|
183
|
+
operator = unary_operators[op]
|
184
|
+
operator.validate_operand(operand)
|
165
185
|
if operand.respond_to?(op)
|
166
186
|
stack.push operand.send(op)
|
167
187
|
else
|
168
|
-
msg1 = "`#{op}': Unimplemented operator for a #{
|
188
|
+
msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
|
169
189
|
raise StandardError, msg1
|
170
190
|
end
|
171
191
|
end
|
@@ -176,21 +196,21 @@ module Loxxy
|
|
176
196
|
callee = stack.pop
|
177
197
|
aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
|
178
198
|
|
179
|
-
|
199
|
+
case callee
|
200
|
+
when NativeFunction
|
180
201
|
stack.push callee.call # Pass arguments
|
202
|
+
when Function
|
203
|
+
callee.call(self, aVisitor)
|
181
204
|
else
|
182
|
-
|
183
|
-
symbol_table.enter_environment(new_env)
|
184
|
-
callee.parameters&.each do |param_name|
|
185
|
-
local = Variable.new(param_name, stack.pop)
|
186
|
-
symbol_table.insert(local)
|
187
|
-
end
|
188
|
-
stack.push callee.call(aVisitor)
|
189
|
-
|
190
|
-
symbol_table.leave_environment
|
205
|
+
raise Loxxy::RuntimeError, 'Can only call functions and classes.'
|
191
206
|
end
|
192
207
|
end
|
193
208
|
|
209
|
+
def complete_call
|
210
|
+
callee = ret_stack.pop
|
211
|
+
symbol_table.leave_environment if callee.kind_of?(Function)
|
212
|
+
end
|
213
|
+
|
194
214
|
def after_grouping_expr(_groupingExpr)
|
195
215
|
# Do nothing: work was already done by visiting /evaluating the subexpression
|
196
216
|
end
|
@@ -213,7 +233,7 @@ module Loxxy
|
|
213
233
|
stack.push(aValue)
|
214
234
|
end
|
215
235
|
|
216
|
-
def after_fun_stmt(aFunStmt,
|
236
|
+
def after_fun_stmt(aFunStmt, _visitor)
|
217
237
|
function = Function.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
|
218
238
|
new_var = Variable.new(aFunStmt.name, function)
|
219
239
|
symbol_table.insert(new_var)
|
@@ -235,6 +255,48 @@ module Loxxy
|
|
235
255
|
end
|
236
256
|
end
|
237
257
|
|
258
|
+
def init_unary_operators
|
259
|
+
negate_op = UnaryOperator.new('-', [Datatype::Number])
|
260
|
+
unary_operators[:-@] = negate_op
|
261
|
+
|
262
|
+
negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype,
|
263
|
+
BackEnd::Function])
|
264
|
+
unary_operators[:!] = negation_op
|
265
|
+
end
|
266
|
+
|
267
|
+
def init_binary_operators
|
268
|
+
plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
|
269
|
+
[Datatype::LXString, :idem]])
|
270
|
+
binary_operators[:+] = plus_op
|
271
|
+
|
272
|
+
minus_op = BinaryOperator.new('-', [[Datatype::Number, :idem]])
|
273
|
+
binary_operators[:-] = minus_op
|
274
|
+
|
275
|
+
star_op = BinaryOperator.new('*', [[Datatype::Number, :idem]])
|
276
|
+
binary_operators[:*] = star_op
|
277
|
+
|
278
|
+
slash_op = BinaryOperator.new('/', [[Datatype::Number, :idem]])
|
279
|
+
binary_operators[:/] = slash_op
|
280
|
+
|
281
|
+
equal_equal_op = BinaryOperator.new('==', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
|
282
|
+
binary_operators[:==] = equal_equal_op
|
283
|
+
|
284
|
+
not_equal_op = BinaryOperator.new('!=', [[Datatype::BuiltinDatatype, Datatype::BuiltinDatatype]])
|
285
|
+
binary_operators[:!=] = not_equal_op
|
286
|
+
|
287
|
+
less_op = BinaryOperator.new('<', [[Datatype::Number, :idem]])
|
288
|
+
binary_operators[:<] = less_op
|
289
|
+
|
290
|
+
less_equal_op = BinaryOperator.new('<=', [[Datatype::Number, :idem]])
|
291
|
+
binary_operators[:<=] = less_equal_op
|
292
|
+
|
293
|
+
greater_op = BinaryOperator.new('>', [[Datatype::Number, :idem]])
|
294
|
+
binary_operators[:>] = greater_op
|
295
|
+
|
296
|
+
greater_equal_op = BinaryOperator.new('>=', [[Datatype::Number, :idem]])
|
297
|
+
binary_operators[:>=] = greater_equal_op
|
298
|
+
end
|
299
|
+
|
238
300
|
def init_globals
|
239
301
|
add_native_fun('clock', native_clock)
|
240
302
|
end
|
@@ -4,18 +4,16 @@ require_relative '../datatype/all_datatypes'
|
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module BackEnd
|
7
|
+
# rubocop: disable Style/AccessorGrouping
|
7
8
|
# Representation of a Lox function.
|
8
9
|
# It is a named slot that can be associated with a value at the time.
|
9
10
|
class Function
|
10
|
-
|
11
11
|
# @return [String]
|
12
12
|
attr_reader :name
|
13
13
|
|
14
14
|
# @return [Array<>] the parameters
|
15
15
|
attr_reader :parameters
|
16
|
-
|
17
16
|
attr_reader :body
|
18
|
-
|
19
17
|
attr_reader :stack
|
20
18
|
|
21
19
|
# Create a variable with given name and initial value
|
@@ -24,7 +22,7 @@ module Loxxy
|
|
24
22
|
def initialize(aName, parameterList, aBody, aStack)
|
25
23
|
@name = aName.dup
|
26
24
|
@parameters = parameterList
|
27
|
-
@body = aBody
|
25
|
+
@body = aBody.kind_of?(Ast::LoxNoopExpr) ? aBody : aBody.subnodes[0]
|
28
26
|
@stack = aStack
|
29
27
|
end
|
30
28
|
|
@@ -32,8 +30,28 @@ module Loxxy
|
|
32
30
|
stack.push self
|
33
31
|
end
|
34
32
|
|
35
|
-
def call(aVisitor)
|
36
|
-
|
33
|
+
def call(engine, aVisitor)
|
34
|
+
new_env = Environment.new(engine.symbol_table.current_env)
|
35
|
+
engine.symbol_table.enter_environment(new_env)
|
36
|
+
|
37
|
+
parameters&.each do |param_name|
|
38
|
+
local = Variable.new(param_name, stack.pop)
|
39
|
+
engine.symbol_table.insert(local)
|
40
|
+
end
|
41
|
+
|
42
|
+
catch(:return) do
|
43
|
+
(body.nil? || body.kind_of?(Ast::LoxNoopExpr)) ? Datatype::Nil.instance : body.accept(aVisitor)
|
44
|
+
throw(:return)
|
45
|
+
end
|
46
|
+
|
47
|
+
engine.symbol_table.leave_environment
|
48
|
+
end
|
49
|
+
|
50
|
+
# Logical negation.
|
51
|
+
# As a function is a truthy thing, its negation is thus false.
|
52
|
+
# @return [Datatype::False]
|
53
|
+
def !
|
54
|
+
Datatype::False.instance
|
37
55
|
end
|
38
56
|
|
39
57
|
# Text representation of a Lox function
|
@@ -41,5 +59,6 @@ module Loxxy
|
|
41
59
|
"<fn #{name}>"
|
42
60
|
end
|
43
61
|
end # class
|
62
|
+
# rubocop: enable Style/AccessorGrouping
|
44
63
|
end # module
|
45
64
|
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
@@ -74,7 +74,7 @@ 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
80
|
rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
|
@@ -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 }
|
@@ -423,10 +424,34 @@ LOX_END
|
|
423
424
|
expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
|
424
425
|
end
|
425
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
|
440
|
+
|
426
441
|
it 'should print the hello world message' do
|
427
442
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
428
443
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
429
444
|
end
|
430
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
|
431
455
|
end # describe
|
456
|
+
# rubocop: enable Metrics/BlockLength
|
432
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.1.
|
4
|
+
version: 0.1.05
|
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-
|
11
|
+
date: 2021-03-05 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
|
@@ -101,16 +103,19 @@ files:
|
|
101
103
|
- lib/loxxy/ast/lox_node.rb
|
102
104
|
- lib/loxxy/ast/lox_noop_expr.rb
|
103
105
|
- lib/loxxy/ast/lox_print_stmt.rb
|
106
|
+
- lib/loxxy/ast/lox_return_stmt.rb
|
104
107
|
- lib/loxxy/ast/lox_seq_decl.rb
|
105
108
|
- lib/loxxy/ast/lox_unary_expr.rb
|
106
109
|
- lib/loxxy/ast/lox_var_stmt.rb
|
107
110
|
- lib/loxxy/ast/lox_variable_expr.rb
|
108
111
|
- lib/loxxy/ast/lox_while_stmt.rb
|
112
|
+
- lib/loxxy/back_end/binary_operator.rb
|
109
113
|
- lib/loxxy/back_end/engine.rb
|
110
114
|
- lib/loxxy/back_end/entry.rb
|
111
115
|
- lib/loxxy/back_end/environment.rb
|
112
116
|
- lib/loxxy/back_end/function.rb
|
113
117
|
- lib/loxxy/back_end/symbol_table.rb
|
118
|
+
- lib/loxxy/back_end/unary_operator.rb
|
114
119
|
- lib/loxxy/back_end/variable.rb
|
115
120
|
- lib/loxxy/datatype/all_datatypes.rb
|
116
121
|
- lib/loxxy/datatype/boolean.rb
|
@@ -120,6 +125,7 @@ files:
|
|
120
125
|
- lib/loxxy/datatype/nil.rb
|
121
126
|
- lib/loxxy/datatype/number.rb
|
122
127
|
- lib/loxxy/datatype/true.rb
|
128
|
+
- lib/loxxy/error.rb
|
123
129
|
- lib/loxxy/front_end/grammar.rb
|
124
130
|
- lib/loxxy/front_end/literal.rb
|
125
131
|
- lib/loxxy/front_end/parser.rb
|