loxxy 0.1.01 → 0.1.06
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 +56 -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 +10 -1
- data/lib/loxxy/ast/ast_visitor.rb +8 -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 +79 -17
- data/lib/loxxy/back_end/{function.rb → lox_function.rb} +28 -4
- data/lib/loxxy/back_end/unary_operator.rb +41 -0
- data/lib/loxxy/error.rb +12 -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 +23 -0
- metadata +11 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 293bf5f6fb0eb5a1daf2723484a2867909c03cc99b20270e562d288d334f0bdc
|
4
|
+
data.tar.gz: 9e26854c6d4334f9869d0cd420ebd4365c0f978666e7ede8310d25e600cf5834
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ac692f16a41b162001fee869d426abf634f1a2bb82bf17eac26d8c35939a8ca63234246a6ff88cc04db3833057b445e2650773652ce102a0dcc4f3aa90182e8
|
7
|
+
data.tar.gz: 2764cb3d1703fdc948180cb2060f93a0ef72148f2877f5b711ecacd17d2ba52a9d3b9f7736eb53130850635cb0d127f2a5161a8361cb4a2268fc6275d15a6b1c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,57 @@
|
|
1
|
+
## [0.1.06] - 2021-03-06
|
2
|
+
- Parameters/arguments checks in function declaration and call
|
3
|
+
|
4
|
+
### Changed
|
5
|
+
- Method `Ast::AstBuilder#reduce_call_arglist` raises a `Loxxy::RuntimeError` when more than 255 arguments are used.
|
6
|
+
- Method `BackEnd::Engine#after_call_expr` raises a `Loxxy::RuntimeError` when argument count doesn't match the arity of function.
|
7
|
+
|
8
|
+
- Class `BackEnd::Function` renamed to `LoxFunction`
|
9
|
+
|
10
|
+
## [0.1.05] - 2021-03-05
|
11
|
+
- Test for Fibbonacci recursive function is now passing.
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
- Method `BackEnd::Function#call` a call doesn't no more generate of TWO scopes
|
15
|
+
|
16
|
+
## [0.1.04] - 2021-02-28
|
17
|
+
|
18
|
+
### Added
|
19
|
+
- Class `Ast::LoxReturnStmt` a node that represents a return statement
|
20
|
+
- Method `Ast::ASTBuilder#reduce_return_stmt`
|
21
|
+
- Method `Ast::ASTVisitor#visit_return_stmt` for visiting an `Ast::LoxReturnStmt` node
|
22
|
+
- Method `BackEnd::Engine#after_return_stmt` to handle return statement
|
23
|
+
- Method `BackEnd::Function#!` implementing the logical negation of a function (as value).
|
24
|
+
- Test suite for logical operators (in project repository)
|
25
|
+
- Test suite for block code
|
26
|
+
- Test suite for call and function declaration (initial)
|
27
|
+
|
28
|
+
### Changed
|
29
|
+
- Method `BackEnd::Engine#after_call_expr` now generate a `catch` and `throw` events
|
30
|
+
|
31
|
+
## [0.1.03] - 2021-02-26
|
32
|
+
- Runtime argument checking for arithmetic and comparison operators
|
33
|
+
|
34
|
+
### Added
|
35
|
+
- Test suite for arithmetic and comparison operators (in project repository)
|
36
|
+
- Class `BackEnd::UnaryOperator`: runtime argument validation
|
37
|
+
- Class `BackEnd::BinaryOperator`: runtime argument validation
|
38
|
+
|
39
|
+
### Changed
|
40
|
+
- File `console` renamed to `loxxy`. Very basic command-line interface.
|
41
|
+
- Custom exception classes
|
42
|
+
- File `README.md` updated list of supported `Lox` keywords.
|
43
|
+
|
44
|
+
|
45
|
+
## [0.1.02] - 2021-02-21
|
46
|
+
- Function definition and call documented in `README.md`
|
47
|
+
|
48
|
+
### Changed
|
49
|
+
- File `README.md` updated description of function definition and function call.
|
50
|
+
|
51
|
+
### Fixed
|
52
|
+
- Method `BackEnd::Engine#after_print_stmt` now handles of empty stack or nil data.
|
53
|
+
- Method `BackEnd::Engine#after_call_expr` was pushing one spurious item onto data stack.
|
54
|
+
|
1
55
|
## [0.1.01] - 2021-02-20
|
2
56
|
### Fixed
|
3
57
|
- Fixed most offences for Rubocop.
|
@@ -197,11 +251,11 @@
|
|
197
251
|
- The interpreter can evaluate substraction between two numbers.
|
198
252
|
|
199
253
|
### Added
|
200
|
-
- Method `Datatype::Number#-`
|
254
|
+
- Method `Datatype::Number#-` implementing the subtraction operation
|
201
255
|
|
202
256
|
### Changed
|
203
257
|
- File `README.md` minor editorial changes.
|
204
|
-
- File `lx_string_spec.rb` Added test for string
|
258
|
+
- File `lx_string_spec.rb` Added test for string concatenation
|
205
259
|
- File `number_spec.rb` Added tests for addition and subtraction operations
|
206
260
|
- File `interpreter_spec.rb` Added tests for subtraction operation
|
207
261
|
|
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])
|
@@ -268,7 +273,7 @@ module Loxxy
|
|
268
273
|
def reduce_call_arglist(_production, _range, tokens, theChildren)
|
269
274
|
args = theChildren[1] || []
|
270
275
|
if args.size > 255
|
271
|
-
raise
|
276
|
+
raise Loxxy::RuntimeError, "Can't have more than 255 arguments."
|
272
277
|
end
|
273
278
|
|
274
279
|
LoxCallExpr.new(tokens[0].position, args)
|
@@ -298,6 +303,10 @@ module Loxxy
|
|
298
303
|
def reduce_function(_production, _range, _tokens, theChildren)
|
299
304
|
first_child = theChildren.first
|
300
305
|
pos = first_child.token.position
|
306
|
+
if theChildren[2] && theChildren[2].size > 255
|
307
|
+
msg = "Can't have more than 255 parameters."
|
308
|
+
raise Loxxy::SyntaxError, msg
|
309
|
+
end
|
301
310
|
LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
|
302
311
|
end
|
303
312
|
|
@@ -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)
|
@@ -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 '
|
5
|
+
require_relative 'binary_operator'
|
6
|
+
require_relative 'lox_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,18 +196,18 @@ 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
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
symbol_table.insert(local)
|
202
|
+
when LoxFunction
|
203
|
+
arg_count = aCallExpr.arguments.size
|
204
|
+
if arg_count != callee.arity
|
205
|
+
msg = "Expected #{callee.arity} arguments but got #{arg_count}."
|
206
|
+
raise Loxxy::RuntimeError, msg
|
187
207
|
end
|
188
|
-
|
189
|
-
|
190
|
-
|
208
|
+
callee.call(self, aVisitor)
|
209
|
+
else
|
210
|
+
raise Loxxy::RuntimeError, 'Can only call functions and classes.'
|
191
211
|
end
|
192
212
|
end
|
193
213
|
|
@@ -214,7 +234,7 @@ module Loxxy
|
|
214
234
|
end
|
215
235
|
|
216
236
|
def after_fun_stmt(aFunStmt, _visitor)
|
217
|
-
function =
|
237
|
+
function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
|
218
238
|
new_var = Variable.new(aFunStmt.name, function)
|
219
239
|
symbol_table.insert(new_var)
|
220
240
|
end
|
@@ -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::LoxFunction])
|
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
|
@@ -7,7 +7,7 @@ module Loxxy
|
|
7
7
|
# rubocop: disable Style/AccessorGrouping
|
8
8
|
# Representation of a Lox function.
|
9
9
|
# It is a named slot that can be associated with a value at the time.
|
10
|
-
class
|
10
|
+
class LoxFunction
|
11
11
|
# @return [String]
|
12
12
|
attr_reader :name
|
13
13
|
|
@@ -22,16 +22,40 @@ module Loxxy
|
|
22
22
|
def initialize(aName, parameterList, aBody, aStack)
|
23
23
|
@name = aName.dup
|
24
24
|
@parameters = parameterList
|
25
|
-
@body = aBody
|
25
|
+
@body = aBody.kind_of?(Ast::LoxNoopExpr) ? aBody : aBody.subnodes[0]
|
26
26
|
@stack = aStack
|
27
27
|
end
|
28
28
|
|
29
|
+
def arity
|
30
|
+
parameters ? parameters.size : 0
|
31
|
+
end
|
32
|
+
|
29
33
|
def accept(_visitor)
|
30
34
|
stack.push self
|
31
35
|
end
|
32
36
|
|
33
|
-
def call(aVisitor)
|
34
|
-
|
37
|
+
def call(engine, aVisitor)
|
38
|
+
new_env = Environment.new(engine.symbol_table.current_env)
|
39
|
+
engine.symbol_table.enter_environment(new_env)
|
40
|
+
|
41
|
+
parameters&.each do |param_name|
|
42
|
+
local = Variable.new(param_name, stack.pop)
|
43
|
+
engine.symbol_table.insert(local)
|
44
|
+
end
|
45
|
+
|
46
|
+
catch(:return) do
|
47
|
+
(body.nil? || body.kind_of?(Ast::LoxNoopExpr)) ? Datatype::Nil.instance : body.accept(aVisitor)
|
48
|
+
throw(:return)
|
49
|
+
end
|
50
|
+
|
51
|
+
engine.symbol_table.leave_environment
|
52
|
+
end
|
53
|
+
|
54
|
+
# Logical negation.
|
55
|
+
# As a function is a truthy thing, its negation is thus false.
|
56
|
+
# @return [Datatype::False]
|
57
|
+
def !
|
58
|
+
Datatype::False.instance
|
35
59
|
end
|
36
60
|
|
37
61
|
# Text representation of a Lox function
|
@@ -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
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Loxxy
|
4
|
+
# Abstract class. Generalization of Loxxy error classes.
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Error occurring while Loxxy executes some invalid Lox code.
|
8
|
+
class RuntimeError < Error; end
|
9
|
+
|
10
|
+
# Error occurring while Loxxy parses some invalid Lox code.
|
11
|
+
class SyntaxError < Error; end
|
12
|
+
end
|
@@ -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
@@ -424,11 +424,34 @@ LOX_END
|
|
424
424
|
expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
|
425
425
|
end
|
426
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
|
+
|
427
441
|
it 'should print the hello world message' do
|
428
442
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
429
443
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
430
444
|
end
|
431
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
|
432
455
|
end # describe
|
433
456
|
# rubocop: enable Metrics/BlockLength
|
434
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.06
|
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-06 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
|
-
- lib/loxxy/back_end/
|
116
|
+
- lib/loxxy/back_end/lox_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
|