loxxy 0.0.14 → 0.0.19
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/.rubocop.yml +1 -1
- data/CHANGELOG.md +82 -0
- data/README.md +106 -53
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -1
- data/lib/loxxy/ast/ast_builder.rb +36 -3
- data/lib/loxxy/ast/ast_visitor.rb +46 -2
- data/lib/loxxy/ast/lox_binary_expr.rb +1 -1
- data/lib/loxxy/ast/lox_grouping_expr.rb +23 -0
- data/lib/loxxy/ast/lox_logical_expr.rb +28 -0
- data/lib/loxxy/ast/lox_unary_expr.rb +27 -0
- data/lib/loxxy/back_end/engine.rb +49 -3
- data/lib/loxxy/datatype/builtin_datatype.rb +55 -0
- data/lib/loxxy/datatype/false.rb +22 -0
- data/lib/loxxy/datatype/lx_string.rb +3 -4
- data/lib/loxxy/datatype/nil.rb +22 -0
- data/lib/loxxy/datatype/number.rb +113 -14
- data/lib/loxxy/datatype/true.rb +8 -0
- data/lib/loxxy/front_end/grammar.rb +2 -2
- data/lib/loxxy/version.rb +1 -1
- data/spec/datatype/lx_string_spec.rb +5 -0
- data/spec/datatype/number_spec.rb +10 -0
- data/spec/front_end/parser_spec.rb +4 -4
- data/spec/interpreter_spec.rb +184 -6
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eced55bddfd7918cbc7aea8eff5f7ecd37aa88baa6e5a6f7350f5bd7606e99af
|
4
|
+
data.tar.gz: f9b8104a0f3a273466ff568a014e345437f4c568621828de8da44961fb52f0e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5b23defe380228bda4e32d9cb94909767fdc42242111f00be214bfbead6323a5e6acda0d78dcaf4e1c2e1fa7d13cd0363661af959a62b093bfcc50c34c290a0
|
7
|
+
data.tar.gz: 96a6a77824cd97b248ad6a78d66d4757c81549fa199021f446883d690f2702dbf4d9879950505d9aa54f45376bd7e6204be8b054e27b43a5576aca744be40047
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,85 @@
|
|
1
|
+
## [0.0.18] - 2021-01-14
|
2
|
+
- The interpreter supports expressions between parentheses (grouping).
|
3
|
+
|
4
|
+
## Added
|
5
|
+
- Class `Ast::LoxLogicalExpr`
|
6
|
+
- Method `Ast::ASTBuilder#reduce_grouping_expr` as semantic action for grouping expression
|
7
|
+
- Method `Ast::ASTVisitor#visit_grouping_expr` for visiting grouping expressions
|
8
|
+
- Method `Engine::after_grouping_expr`for the evaluation of grouping expressions
|
9
|
+
|
10
|
+
## [0.0.18] - 2021-01-13
|
11
|
+
- The interpreter can evaluate `and`, `or`expressions.
|
12
|
+
|
13
|
+
## Added
|
14
|
+
- Class `Ast::LoxLogicalExpr`
|
15
|
+
- Method `Ast::ASTBuilder#reduce_logical_expr` for the semantic action require for `and`, `or`
|
16
|
+
- Method `Ast::ASTVisitor#visit_logical_expr` for visiting logical expressions
|
17
|
+
- Method `Backend::Engine#after_logical_expr` implements the evaluation of the logical expressions
|
18
|
+
|
19
|
+
## [0.0.17] - 2021-01-12
|
20
|
+
- The interpreter can evaluate all arithmetic and comparison operations.
|
21
|
+
- It implements `==`, `!=` and the unary operations `!`, `-`
|
22
|
+
|
23
|
+
## Added
|
24
|
+
- Class `Ast::LoxUnaryExpr`
|
25
|
+
- Method `Ast::ASTBuilder#reduce_unary_expr` to support the evaluation of `!` and ``-@`
|
26
|
+
- Method `Ast::ASTVisitor#visit_unnary_expr` for visiting unary expressions
|
27
|
+
- Method `Backend::Engine#after_unary_expr` evaluating an unary expression
|
28
|
+
- In class `Datatype::BuiltinDatatype` the methods `falsey?`, `truthy?`, `!`, `!=`
|
29
|
+
- In class `Datatype::Number`the methods `<`, `<=`, ´>´, `>=` and `-@`
|
30
|
+
|
31
|
+
## Changed
|
32
|
+
- File `README.md` updated.
|
33
|
+
|
34
|
+
## [0.0.16] - 2021-01-11
|
35
|
+
- The interpreter can evaluate product and division of two numbers.
|
36
|
+
- It also implements equality `==` and inequality `!=` operators
|
37
|
+
|
38
|
+
## Added
|
39
|
+
- Method `Datatype::False#==` for equality testing
|
40
|
+
- Method `Datatype::False#!=` for inequality testing
|
41
|
+
- Method `Datatype::LXString#==` for equality testing
|
42
|
+
- Method `Datatype::LXString#!=` for inequality testing
|
43
|
+
- Method `Datatype::Nil#==` for equality testing
|
44
|
+
- Method `Datatype::Nil#!=` for inequality testing
|
45
|
+
- Method `Datatype::Number#==` for equality testing
|
46
|
+
- Method `Datatype::Number#!=` for inequality testing
|
47
|
+
- Method `Datatype::Number#*` for multiply operator
|
48
|
+
- Method `Datatype::Number#/` for divide operator
|
49
|
+
- Method `Datatype::True#==` for equality testing
|
50
|
+
- Method `Datatype::True#!=` for inequality testing
|
51
|
+
|
52
|
+
## Changed
|
53
|
+
- Method `BackEnd::Engine#after_binary_expr` to allow `*`, `/`, `==`, `!=` operators
|
54
|
+
- File `README.md` updated for the newly implemented operators
|
55
|
+
|
56
|
+
## [0.0.15] - 2021-01-11
|
57
|
+
- The interpreter can evaluate substraction between two numbers.
|
58
|
+
|
59
|
+
## Added
|
60
|
+
- Method `Datatype::Number#-` implmenting the subtraction operation
|
61
|
+
|
62
|
+
## Changed
|
63
|
+
- File `README.md` minor editorial changes.
|
64
|
+
- File `lx_string_spec.rb` Added test for string concatentation
|
65
|
+
- File `number_spec.rb` Added tests for addition and subtraction operations
|
66
|
+
- File `interpreter_spec.rb` Added tests for subtraction operation
|
67
|
+
|
68
|
+
## [0.0.14] - 2021-01-10
|
69
|
+
- The interpreter can evaluate addition of numbers and string concatenation
|
70
|
+
|
71
|
+
## Added
|
72
|
+
- Method `Ast::ASTVisitor#visit_binary_expr` for visiting binary expressions
|
73
|
+
- Method `Ast::LoxBinaryExpr#accept` for visitor pattern
|
74
|
+
- Method `BackEnd::Engine#after_binary_expr` to trigger execution of binary operator
|
75
|
+
- `Boolean` class hierarchy: added methos `true?` and `false?` to ease spec test writing
|
76
|
+
- Method `Datatype::LXString#+` implementation of the string concatenation
|
77
|
+
- Method `Datatype::Number#+` implementation of the addition of numbers
|
78
|
+
|
79
|
+
## Changed
|
80
|
+
- File `interpreter_spec.rb` Added tests for addition operation and string concatenation
|
81
|
+
|
82
|
+
|
1
83
|
## [0.0.13] - 2021-01-10
|
2
84
|
- The interpreter can evaluate directly simple literals.
|
3
85
|
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
[](https://badge.fury.io/rb/loxxy)
|
3
3
|
[](https://github.com/famished-tiger/loxxy/blob/main/LICENSE.txt)
|
4
4
|
|
5
|
-
|
5
|
+
### What is loxxy?
|
6
6
|
A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html ),
|
7
7
|
a simple language used in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/ ).
|
8
8
|
|
@@ -16,7 +16,7 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
|
|
16
16
|
The project is still in inception and the interpreter is being implemented...
|
17
17
|
Currently it can execute a tiny subset of __Lox__ language.
|
18
18
|
|
19
|
-
|
19
|
+
But the __loxxy__ gem hosts also a parser class `RawPaser` that can parse, in principle, any valid Lox input.
|
20
20
|
|
21
21
|
## What's the fuss about Lox?
|
22
22
|
... Nothing...
|
@@ -74,7 +74,7 @@ require 'loxxy'
|
|
74
74
|
|
75
75
|
lox = Loxxy::Interpreter.new
|
76
76
|
|
77
|
-
lox_program = '
|
77
|
+
lox_program = '47 - 5; // THE answer'
|
78
78
|
result = lox.evaluate(lox_program) # => Loxxy::Datatype::Number
|
79
79
|
|
80
80
|
# `result` is a Ruby object, so let's use it...
|
@@ -128,17 +128,20 @@ program
|
|
128
128
|
```
|
129
129
|
|
130
130
|
## Suppported Lox language features
|
131
|
-
|
132
|
-
|
131
|
+
On one hand, the parser covers the complete Lox grammar and should therefore, in principle,
|
132
|
+
parse any valid Lox program.
|
133
|
+
|
134
|
+
On the other hand, the interpreter is under development and currently it can evaluate only a tiny subset of __Lox__.
|
135
|
+
But the situation is changing almost daily, stay tuned...
|
133
136
|
|
134
137
|
Here are the language features currently supported by the interpreter:
|
135
138
|
|
136
139
|
- [Comments](#comments)
|
137
140
|
- [Keywords](#keywords)
|
138
|
-
- [Operators and Special Chars](#operators-and-special-chars)
|
139
141
|
- [Datatypes](#datatypes)
|
140
|
-
- [Statements](#statements)
|
141
|
-
-
|
142
|
+
- [Statements](#statements)
|
143
|
+
-[Expressions](#expressions)
|
144
|
+
-[Print Statement](#print-statement)
|
142
145
|
|
143
146
|
### Comments
|
144
147
|
|
@@ -149,66 +152,116 @@ Loxxy supports single line C-style comments.
|
|
149
152
|
```
|
150
153
|
|
151
154
|
### Keywords
|
152
|
-
|
153
|
-
The parser knows all the __Lox__ reserved keywords:
|
155
|
+
Loxxy implements the following __Lox__ reserved keywords:
|
154
156
|
```lang-none
|
155
|
-
and,
|
156
|
-
print, return, super, this, true, var, while
|
157
|
+
and, false, nil, or, print, true
|
157
158
|
```
|
158
|
-
Of these, the interpreter implements: `false`, `nil`, `true`
|
159
|
-
|
160
|
-
### Operators and Special Chars
|
161
|
-
#### Operators
|
162
|
-
The parser recognizes all the __Lox__ operators, delimiters and separators:
|
163
|
-
- Arithmetic operators: `+`, `-`, `*`, `/`
|
164
|
-
- Comparison operators: `>`, `>=`, `<`, `<=`
|
165
|
-
- Equality operators: `==`, `!=`
|
166
|
-
|
167
|
-
Of these, the interpreter implements:
|
168
|
-
`+` (addition of two numbers or string concatenation)
|
169
|
-
|
170
|
-
#### Delimiters
|
171
|
-
The parser knows all the __Lox__ grouping delimiters:
|
172
|
-
(`, ), `{`, `}`
|
173
|
-
|
174
|
-
These aren't yet implemented in the interpreter.
|
175
|
-
|
176
|
-
The other characters that have a special meaning in __Lox__ are:
|
177
|
-
- `,` Used in parameter list
|
178
|
-
- `.` For the dot notation (i.e. calling a method)
|
179
|
-
- `;` The semicolon is used to terminates expressions
|
180
|
-
- `=` Assignment
|
181
|
-
|
182
|
-
The parser recognizes them all but the interpreter accepts the semicolons only.
|
183
159
|
|
184
160
|
### Datatypes
|
185
161
|
|
186
|
-
|
162
|
+
loxxy supports all the standard __Lox__ datatypes:
|
187
163
|
- `Boolean`: Can be `true` or `false`
|
188
164
|
- `Number`: Can be an integer or a floating-point numbers. For example: `123, 12.34, -45.67`
|
189
165
|
- `String`: Sequence of characters surrounded by `"`. For example: `"Hello!"`
|
190
166
|
- `Nil`: Used to define a null value, denoted by the `nil` keyword
|
191
167
|
|
192
|
-
|
168
|
+
### Statements
|
169
|
+
|
170
|
+
Loxxy supports the following statements:
|
171
|
+
-[Expressions](#expressions)
|
172
|
+
-[Arithmetic expressions](#arithmetic-expressions)
|
173
|
+
-[String concatenation](#string-concatenation)
|
174
|
+
-[Comparison expressions](#comparison-expressions)
|
175
|
+
-[Logical expressions](#logical-expressions)
|
176
|
+
-[Grouping expressions](#grouping-expressions)
|
177
|
+
-[Print Statement](#print-statement)
|
178
|
+
|
179
|
+
#### Expressions
|
180
|
+
|
181
|
+
##### Arithmetic expressions
|
182
|
+
Loxxy supports the following operators for arithmetic expressions:
|
183
|
+
|
184
|
+
- `+`: Adds of two numbers. Both operands must be of the type Number
|
185
|
+
E.g. `37 + 5; // => 42`
|
186
|
+
`7 + -3; // => 4`
|
187
|
+
- `-`: (Binary) Subtracts right operand from left operand. Both operands must be numbers.
|
188
|
+
E.g. `47 - 5; // => 42`
|
189
|
+
- `-`: (Unary) Negates (= changes the sign) of the given number operand.
|
190
|
+
E.g. `- -3; // => 3`
|
191
|
+
- `*`: Multiplies two numbers
|
192
|
+
E.g. `2 * 3; // => 6`
|
193
|
+
- `/`: Divides two numbers
|
194
|
+
E.g. `8 / 2; // => 4`
|
195
|
+
`5 / 2; // => 2.5`
|
196
|
+
|
197
|
+
##### String concatenation
|
198
|
+
- `+`: Concatenates two strings. Both operands must be of the String type.
|
199
|
+
E.g. `"Hello" + ", " + "world! // => "Hello, world!"`
|
200
|
+
|
201
|
+
##### Comparison expressions
|
202
|
+
|
203
|
+
- `==`: Returns `true` if left operand is equal to right operand, otherwise `false`
|
204
|
+
E.g. `false == false; // => true`
|
205
|
+
`5 + 2 == 3 + 4; // => true`
|
206
|
+
`"" == ""; // => true`
|
207
|
+
- `!=`: Returns `true` if left operand is not equal to right operand, otherwise `false`
|
208
|
+
E.g. `false != "false"; // => true`
|
209
|
+
`5 + 2 != 4 + 3; // => false`
|
210
|
+
- `<`: Returns `true` if left operand is less than right operand, otherwise `false`. Both operands must be numbers
|
211
|
+
E.g. `1 < 3; // => true`
|
212
|
+
`1 < 0; // => false`
|
213
|
+
`2 < 2; // => false`
|
214
|
+
- `<=`: Returns `true` if left operand is equal to right operand, otherwise `false`. Both operands must be numbers
|
215
|
+
E.g. `1 <= 3; // => true`
|
216
|
+
`1 <= 0; // => false`
|
217
|
+
`2 <= 2; // => true`
|
218
|
+
- `>`: Returns `true` if left operand is equal to right operand, otherwise `false`. Both operands must be numbers
|
219
|
+
E.g. `1 > 3; // => false`
|
220
|
+
`1 > 0; // => true`
|
221
|
+
`2 > 2; // => false`
|
222
|
+
- `>=`: Returns `true` if left operand is equal to right operand, otherwise `false`. Both operands must be numbers
|
223
|
+
E.g. `1 > 3; // => false`
|
224
|
+
`1 > 0; // => true`
|
225
|
+
`2 > 2; // => false`
|
226
|
+
|
227
|
+
##### Logical expressions
|
228
|
+
|
229
|
+
REMINDER: In __Lox__, `false` and `nil` are considered falsey, everything else is truthy.
|
230
|
+
|
231
|
+
- `and`: When both operands are booleans, then returns `true` if both left and right operands are truthy, otherwise `false`.
|
232
|
+
If at least one operand isn't a boolean then returns first falsey operand else (both operands are truthy) returns the second operand.
|
233
|
+
truthy returns the second operand.
|
234
|
+
E.g. `false and true; // => false`
|
235
|
+
`true and nil; // => nil`
|
236
|
+
`0 and true and ""; // => ""`
|
237
|
+
- `or`: When both operands are booleans, then returns `true` if left or right operands are truthy, otherwise `false`.
|
238
|
+
If at least one operand isn't a boolean then returns first truthy operand else (both operands are truthy) returns the second operand.
|
239
|
+
E.g. `false or true; // => true`
|
240
|
+
`true or nil; // => nil`
|
241
|
+
`false or nil; // => nil`
|
242
|
+
`0 or true or ""; // => 0`
|
243
|
+
- `!`: Performs a logical negation on its operand
|
244
|
+
E.g. `!false; // => true`
|
245
|
+
`!!true; // => true`
|
246
|
+
`!0; // => false`
|
247
|
+
|
248
|
+
##### Grouping expressions
|
249
|
+
Use parentheses `(` `)` for a better control in expression/operator precedence.
|
250
|
+
|
251
|
+
``` javascript
|
252
|
+
print 3 + 4 * 5; // => 23
|
253
|
+
print (3 + 4) * 5; // => 35
|
254
|
+
```
|
193
255
|
|
194
|
-
## Statements
|
195
|
-
### Implemented expressions
|
196
|
-
Loxxy implements expressions:
|
197
|
-
- Consisting of literals only; or,
|
198
|
-
- Addition of two numbers; or,
|
199
|
-
- Concatenation of two strings
|
200
256
|
|
201
|
-
|
202
|
-
Loxxy implements the following statements:
|
203
|
-
- Expressions (see above sub-section)
|
204
|
-
- Print statement
|
257
|
+
##### Print Statement
|
205
258
|
|
206
|
-
|
207
|
-
// Print statement with nested concatenation
|
208
|
-
print "Hello" + ", " + "world!";
|
209
|
-
```
|
259
|
+
The statement print + expression + ; prints the result of the expression to stdout.
|
210
260
|
|
261
|
+
``` javascript
|
262
|
+
print "Hello, world!"; // Output: Hello, world!
|
211
263
|
|
264
|
+
```
|
212
265
|
|
213
266
|
## Installation
|
214
267
|
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'lox_print_stmt'
|
4
3
|
require_relative 'lox_literal_expr'
|
5
4
|
require_relative 'lox_noop_expr'
|
5
|
+
require_relative 'lox_grouping_expr'
|
6
|
+
require_relative 'lox_unary_expr'
|
7
|
+
require_relative 'lox_binary_expr'
|
8
|
+
require_relative 'lox_logical_expr'
|
9
|
+
require_relative 'lox_print_stmt'
|
@@ -15,7 +15,6 @@ module Loxxy
|
|
15
15
|
# @return [Hash{String => String}]
|
16
16
|
Name2special = {
|
17
17
|
'AND' => 'and',
|
18
|
-
'BANG' => '!',
|
19
18
|
'BANG_EQUAL' => '!=',
|
20
19
|
'COMMA' => ',',
|
21
20
|
'DOT' => '.',
|
@@ -36,6 +35,11 @@ module Loxxy
|
|
36
35
|
'SLASH' => '/',
|
37
36
|
'STAR' => '*'
|
38
37
|
}.freeze
|
38
|
+
|
39
|
+
Name2unary = {
|
40
|
+
'BANG' => '!',
|
41
|
+
'MINUS' => '-@'
|
42
|
+
}.freeze
|
39
43
|
end # defined
|
40
44
|
|
41
45
|
attr_reader :strict
|
@@ -92,6 +96,17 @@ module Loxxy
|
|
92
96
|
node
|
93
97
|
end
|
94
98
|
|
99
|
+
def reduce_logical_expr(_production, _range, tokens, theChildren)
|
100
|
+
operand1 = theChildren[0]
|
101
|
+
|
102
|
+
# Second child is array with couples [operator, operand2]
|
103
|
+
theChildren[1].each do |(operator, operand2)|
|
104
|
+
operand1 = LoxLogicalExpr.new(tokens[0].position, operator, operand1, operand2)
|
105
|
+
end
|
106
|
+
|
107
|
+
operand1
|
108
|
+
end
|
109
|
+
|
95
110
|
# rule('lhs' => 'nonterm_i nonterm_k_plus')
|
96
111
|
def reduce_binary_operator(_production, _range, tokens, theChildren)
|
97
112
|
operand1 = theChildren[0]
|
@@ -119,6 +134,11 @@ module Loxxy
|
|
119
134
|
[[operator, operand2]]
|
120
135
|
end
|
121
136
|
|
137
|
+
# Return the AST node corresponding to the second symbol in the rhs
|
138
|
+
def reduce_keep_symbol2(_production, _range, _tokens, theChildren)
|
139
|
+
theChildren[1]
|
140
|
+
end
|
141
|
+
|
122
142
|
#####################################
|
123
143
|
# SEMANTIC ACTIONS
|
124
144
|
#####################################
|
@@ -145,7 +165,7 @@ module Loxxy
|
|
145
165
|
|
146
166
|
# rule('logic_or' => 'logic_and disjunct_plus')
|
147
167
|
def reduce_logic_or_plus(production, range, tokens, theChildren)
|
148
|
-
|
168
|
+
reduce_logical_expr(production, range, tokens, theChildren)
|
149
169
|
end
|
150
170
|
|
151
171
|
# rule('disjunct_plus' => 'disjunct_plus OR logic_and')
|
@@ -160,7 +180,7 @@ module Loxxy
|
|
160
180
|
|
161
181
|
# rule('logic_and' => 'equality conjunct_plus')
|
162
182
|
def reduce_logic_and_plus(production, range, tokens, theChildren)
|
163
|
-
|
183
|
+
reduce_logical_expr(production, range, tokens, theChildren)
|
164
184
|
end
|
165
185
|
|
166
186
|
# rule('conjunct_plus' => 'conjunct_plus AND equality')
|
@@ -231,6 +251,19 @@ module Loxxy
|
|
231
251
|
reduce_binary_plus_end(production, range, tokens, theChildren)
|
232
252
|
end
|
233
253
|
|
254
|
+
# rule('unary' => 'unaryOp unary')
|
255
|
+
def reduce_unary_expr(_production, _range, tokens, theChildren)
|
256
|
+
operator = Name2unary[theChildren[0].symbol.name].to_sym
|
257
|
+
operand = theChildren[1]
|
258
|
+
LoxUnaryExpr.new(tokens[0].position, operator, operand)
|
259
|
+
end
|
260
|
+
|
261
|
+
# rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
262
|
+
def reduce_grouping_expr(_production, _range, tokens, theChildren)
|
263
|
+
subexpr = theChildren[1]
|
264
|
+
LoxGroupingExpr.new(tokens[0].position, subexpr)
|
265
|
+
end
|
266
|
+
|
234
267
|
# rule('primary' => 'FALSE' | TRUE').as 'literal_expr'
|
235
268
|
def reduce_literal_expr(_production, _range, _tokens, theChildren)
|
236
269
|
first_child = theChildren.first
|
@@ -51,7 +51,7 @@ module Loxxy
|
|
51
51
|
broadcast(:after_ptree, aParseTree)
|
52
52
|
end
|
53
53
|
|
54
|
-
# Visit event. The visitor is about to visit a print statement
|
54
|
+
# Visit event. The visitor is about to visit a print statement.
|
55
55
|
# @param aPrintStmt [AST::LOXPrintStmt] the print statement node to visit
|
56
56
|
def visit_print_stmt(aPrintStmt)
|
57
57
|
broadcast(:before_print_stmt, aPrintStmt)
|
@@ -59,7 +59,21 @@ module Loxxy
|
|
59
59
|
broadcast(:after_print_stmt, aPrintStmt)
|
60
60
|
end
|
61
61
|
|
62
|
-
# Visit event. The visitor is about to visit a
|
62
|
+
# Visit event. The visitor is about to visit a logical expression.
|
63
|
+
# Since logical expressions may take shorcuts by not evaluating all their
|
64
|
+
# sub-expressiosns, they are responsible for visiting or not their children.
|
65
|
+
# @param aBinaryExpr [AST::LOXBinaryExpr] the logical expression node to visit
|
66
|
+
def visit_logical_expr(aLogicalExpr)
|
67
|
+
broadcast(:before_logical_expr, aLogicalExpr)
|
68
|
+
|
69
|
+
# As logical connectors may take a shortcut only the first argument is visited
|
70
|
+
traverse_given_subnode(aLogicalExpr, 0)
|
71
|
+
|
72
|
+
# The second child could be visited: this action is deferred in handler
|
73
|
+
broadcast(:after_logical_expr, aLogicalExpr, self)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Visit event. The visitor is about to visit a binary expression.
|
63
77
|
# @param aBinaryExpr [AST::LOXBinaryExpr] the binary expression node to visit
|
64
78
|
def visit_binary_expr(aBinaryExpr)
|
65
79
|
broadcast(:before_binary_expr, aBinaryExpr)
|
@@ -67,6 +81,22 @@ module Loxxy
|
|
67
81
|
broadcast(:after_binary_expr, aBinaryExpr)
|
68
82
|
end
|
69
83
|
|
84
|
+
# Visit event. The visitor is about to visit an unary expression.
|
85
|
+
# @param anUnaryExpr [AST::anUnaryExpr] unary expression node to visit
|
86
|
+
def visit_unary_expr(anUnaryExpr)
|
87
|
+
broadcast(:before_unary_expr, anUnaryExpr)
|
88
|
+
traverse_subnodes(anUnaryExpr)
|
89
|
+
broadcast(:after_unary_expr, anUnaryExpr)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Visit event. The visitor is about to visit a grouping expression.
|
93
|
+
# @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
|
94
|
+
def visit_grouping_expr(aGroupingExpr)
|
95
|
+
broadcast(:before_grouping_expr, aGroupingExpr)
|
96
|
+
traverse_subnodes(aGroupingExpr)
|
97
|
+
broadcast(:after_grouping_expr, aGroupingExpr)
|
98
|
+
end
|
99
|
+
|
70
100
|
# Visit event. The visitor is visiting the
|
71
101
|
# given terminal node containing a datatype object.
|
72
102
|
# @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
|
@@ -98,6 +128,20 @@ module Loxxy
|
|
98
128
|
broadcast(:after_subnodes, aParentNode, subnodes)
|
99
129
|
end
|
100
130
|
|
131
|
+
# Visit event. The visitor is about to visit one given subnode of a non
|
132
|
+
# terminal node.
|
133
|
+
# @param aParentNode [Ast::LocCompoundExpr] the parent node.
|
134
|
+
# @param index [integer] index of child subnode
|
135
|
+
def traverse_given_subnode(aParentNode, index)
|
136
|
+
subnode = aParentNode.subnodes[index]
|
137
|
+
broadcast(:before_given_subnode, aParentNode, subnode)
|
138
|
+
|
139
|
+
# Now, let's proceed with the visit of that subnode
|
140
|
+
subnode.accept(self)
|
141
|
+
|
142
|
+
broadcast(:after_given_subnode, aParentNode, subnode)
|
143
|
+
end
|
144
|
+
|
101
145
|
# Send a notification to all subscribers.
|
102
146
|
# @param msg [Symbol] event to notify
|
103
147
|
# @param args [Array] arguments of the notification.
|
@@ -5,7 +5,7 @@ require_relative 'lox_compound_expr'
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
7
|
class LoxBinaryExpr < LoxCompoundExpr
|
8
|
-
# @return [Symbol]
|
8
|
+
# @return [Symbol] message name to be sent to receiver
|
9
9
|
attr_reader :operator
|
10
10
|
|
11
11
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxGroupingExpr < LoxCompoundExpr
|
8
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
+
# @param subExpr [Loxxy::Ast::LoxNode]
|
10
|
+
def initialize(aPosition, subExpr)
|
11
|
+
super(aPosition, [subExpr])
|
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_grouping_expr(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
alias operands subnodes
|
21
|
+
end # class
|
22
|
+
end # module
|
23
|
+
end # module
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxLogicalExpr < LoxCompoundExpr
|
8
|
+
# @return [Symbol] message name to be sent to receiver
|
9
|
+
attr_reader :operator
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param operand1 [Loxxy::Ast::LoxNode]
|
13
|
+
# @param operand2 [Loxxy::Ast::LoxNode]
|
14
|
+
def initialize(aPosition, anOperator, operand1, operand2)
|
15
|
+
super(aPosition, [operand1, operand2])
|
16
|
+
@operator = anOperator
|
17
|
+
end
|
18
|
+
|
19
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
20
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
21
|
+
def accept(visitor)
|
22
|
+
visitor.visit_logical_expr(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
alias operands subnodes
|
26
|
+
end # class
|
27
|
+
end # module
|
28
|
+
end # module
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxUnaryExpr < LoxCompoundExpr
|
8
|
+
# @return [Symbol] message name to be sent to receiver
|
9
|
+
attr_reader :operator
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param operand [Loxxy::Ast::LoxNode]
|
13
|
+
def initialize(aPosition, anOperator, operand)
|
14
|
+
super(aPosition, [operand])
|
15
|
+
@operator = anOperator
|
16
|
+
end
|
17
|
+
|
18
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
19
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
20
|
+
def accept(visitor)
|
21
|
+
visitor.visit_unary_expr(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
alias operands subnodes
|
25
|
+
end # class
|
26
|
+
end # module
|
27
|
+
end # module
|
@@ -22,7 +22,7 @@ module Loxxy
|
|
22
22
|
@stack = []
|
23
23
|
end
|
24
24
|
|
25
|
-
# Given an abstract syntax parse tree visitor,
|
25
|
+
# Given an abstract syntax parse tree visitor, launch the visit
|
26
26
|
# and execute the visit events in the output stream.
|
27
27
|
# @param aVisitor [AST::ASTVisitor]
|
28
28
|
# @return [Loxxy::Datatype::BuiltinDatatype]
|
@@ -40,12 +40,43 @@ module Loxxy
|
|
40
40
|
@ostream.print tos.to_str
|
41
41
|
end
|
42
42
|
|
43
|
+
def after_logical_expr(aLogicalExpr, visitor)
|
44
|
+
op = aLogicalExpr.operator
|
45
|
+
operand1 = stack.pop # only first operand was evaluated
|
46
|
+
result = nil
|
47
|
+
if ((op == :and) && operand1.falsey?) || ((op == :or) && operand1.truthy?)
|
48
|
+
result = operand1
|
49
|
+
else
|
50
|
+
raw_operand2 = aLogicalExpr.subnodes[1]
|
51
|
+
raw_operand2.accept(visitor) # Visit means operand2 is evaluated
|
52
|
+
operand2 = stack.pop
|
53
|
+
result = logical_2nd_arg(operand2)
|
54
|
+
end
|
55
|
+
|
56
|
+
stack.push result
|
57
|
+
end
|
58
|
+
|
59
|
+
def logical_2nd_arg(operand2)
|
60
|
+
case operand2
|
61
|
+
when false
|
62
|
+
False.instance # Convert to Lox equivalent
|
63
|
+
when nil
|
64
|
+
Nil.instance # Convert to Lox equivalent
|
65
|
+
when true
|
66
|
+
True.instance # Convert to Lox equivalent
|
67
|
+
when Proc
|
68
|
+
# Second operand wasn't yet evaluated...
|
69
|
+
operand2.call
|
70
|
+
else
|
71
|
+
operand2
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
43
75
|
def after_binary_expr(aBinaryExpr)
|
44
76
|
op = aBinaryExpr.operator
|
45
77
|
operand2 = stack.pop
|
46
78
|
operand1 = stack.pop
|
47
|
-
|
48
|
-
if implemented && operand1.respond_to?(op)
|
79
|
+
if operand1.respond_to?(op)
|
49
80
|
stack.push operand1.send(op, operand2)
|
50
81
|
else
|
51
82
|
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
@@ -53,6 +84,21 @@ module Loxxy
|
|
53
84
|
end
|
54
85
|
end
|
55
86
|
|
87
|
+
def after_unary_expr(anUnaryExpr)
|
88
|
+
op = anUnaryExpr.operator
|
89
|
+
operand = stack.pop
|
90
|
+
if operand.respond_to?(op)
|
91
|
+
stack.push operand.send(op)
|
92
|
+
else
|
93
|
+
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
94
|
+
raise StandardError, msg1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def after_grouping_expr(_groupingExpr)
|
99
|
+
# Do nothing: work was already done by visiting /evaluating the subexpression
|
100
|
+
end
|
101
|
+
|
56
102
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
57
103
|
def before_literal_expr(literalExpr)
|
58
104
|
stack.push(literalExpr.literal)
|
@@ -14,6 +14,48 @@ module Loxxy
|
|
14
14
|
@value = validated_value(aValue)
|
15
15
|
end
|
16
16
|
|
17
|
+
# Is the value considered falsey in Lox?
|
18
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
19
|
+
# This test used in conditional statements (i.e. if, while)
|
20
|
+
def falsey?
|
21
|
+
false # Default implementation
|
22
|
+
end
|
23
|
+
|
24
|
+
# Is the value considered truthy in Lox?
|
25
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
26
|
+
# This test used in conditional statements (i.e. if, while)
|
27
|
+
def truthy?
|
28
|
+
true # Default implementation
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check for inequality of this object with another Lox object
|
32
|
+
# @param other [Datatype::BuiltinDatatype, Object]
|
33
|
+
# @return [Datatype::Boolean]
|
34
|
+
def !=(other)
|
35
|
+
!(self == other)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Negation ('not')
|
39
|
+
# Returns a boolean with opposite truthiness value.
|
40
|
+
# @return [Datatype::Boolean]
|
41
|
+
def !
|
42
|
+
falsey? ? True.instance : False.instance
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the first falsey argument (if any),
|
46
|
+
# otherwise returns the last truthy argument.
|
47
|
+
# @param operand2 [Loxxy::Datatype::BuiltinDatatype, Proc]
|
48
|
+
def and(operand2)
|
49
|
+
falsey? ? self : logical_2nd_arg(operand2)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the first truthy argument (if any),
|
53
|
+
# otherwise returns the last falsey argument.
|
54
|
+
# @param operand2 [Loxxy::Datatype::BuiltinDatatype, Proc]
|
55
|
+
def or(operand2)
|
56
|
+
truthy? ? self : logical_2nd_arg(operand2)
|
57
|
+
end
|
58
|
+
|
17
59
|
# Method called from Lox to obtain the text representation of the boolean.
|
18
60
|
# @return [String]
|
19
61
|
def to_str
|
@@ -25,6 +67,19 @@ module Loxxy
|
|
25
67
|
def validated_value(aValue)
|
26
68
|
aValue
|
27
69
|
end
|
70
|
+
|
71
|
+
def logical_2nd_arg(operand2)
|
72
|
+
case operand2
|
73
|
+
when false
|
74
|
+
False.instance # Convert to Lox equivalent
|
75
|
+
when nil
|
76
|
+
Nil.instance # Convert to Lox equivalent
|
77
|
+
when true
|
78
|
+
True.instance # Convert to Lox equivalent
|
79
|
+
else
|
80
|
+
operand2
|
81
|
+
end
|
82
|
+
end
|
28
83
|
end # class
|
29
84
|
end # module
|
30
85
|
end # module
|
data/lib/loxxy/datatype/false.rb
CHANGED
@@ -19,6 +19,28 @@ module Loxxy
|
|
19
19
|
def false?
|
20
20
|
true
|
21
21
|
end
|
22
|
+
|
23
|
+
# Is the value considered falsey in Lox?
|
24
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
25
|
+
# This test used in conditional statements (i.e. if, while)
|
26
|
+
def falsey?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
# Is the value considered truthy in Lox?
|
31
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
32
|
+
# This test used in conditional statements (i.e. if, while)
|
33
|
+
def truthy?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check for equality of a Lox False with another Lox object
|
38
|
+
# @param other [Datatype::BuiltinDatatype, FalseClass, Object]
|
39
|
+
# @return [Datatype::Boolean]
|
40
|
+
def ==(other)
|
41
|
+
falsey = other.kind_of?(False) || other.kind_of?(FalseClass)
|
42
|
+
falsey ? True.instance : False.instance
|
43
|
+
end
|
22
44
|
end # class
|
23
45
|
|
24
46
|
False.instance.freeze # Make the sole instance immutable
|
@@ -7,8 +7,8 @@ module Loxxy
|
|
7
7
|
module Datatype
|
8
8
|
# Class for representing a Lox string of characters value.
|
9
9
|
class LXString < BuiltinDatatype
|
10
|
-
#
|
11
|
-
# @param other [Datatype::LxString, String]
|
10
|
+
# Check the equality of a Lox String with another Lox object.
|
11
|
+
# @param other [Datatype::LxString, String, Object]
|
12
12
|
# @return [Datatype::Boolean]
|
13
13
|
def ==(other)
|
14
14
|
case other
|
@@ -17,8 +17,7 @@ module Loxxy
|
|
17
17
|
when String
|
18
18
|
(value == other) ? True.instance : False.instance
|
19
19
|
else
|
20
|
-
|
21
|
-
raise StandardError, err_msg
|
20
|
+
False.instance
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
data/lib/loxxy/datatype/nil.rb
CHANGED
@@ -14,6 +14,28 @@ module Loxxy
|
|
14
14
|
super(nil)
|
15
15
|
end
|
16
16
|
|
17
|
+
# Check the equality with another object.
|
18
|
+
# @param other [Datatype::BuiltinDatatype, NilClass, Object]
|
19
|
+
# @return [Datatype::Boolean]
|
20
|
+
def ==(other)
|
21
|
+
is_nil = other.kind_of?(Nil) || other.kind_of?(NilClass)
|
22
|
+
is_nil ? True.instance : False.instance
|
23
|
+
end
|
24
|
+
|
25
|
+
# Is the value considered falsey in Lox?
|
26
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
27
|
+
# This test used in conditional statements (i.e. if, while)
|
28
|
+
def falsey?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Is the value considered truthy in Lox?
|
33
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
34
|
+
# This test used in conditional statements (i.e. if, while)
|
35
|
+
def truthy?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
17
39
|
# Method called from Lox to obtain the text representation of nil.
|
18
40
|
# @return [String]
|
19
41
|
def to_str
|
@@ -7,8 +7,78 @@ module Loxxy
|
|
7
7
|
module Datatype
|
8
8
|
# Class for representing a Lox numeric value.
|
9
9
|
class Number < BuiltinDatatype
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# Perform the addition of two Lox numbers or
|
11
|
+
# one Lox number and a Ruby Numeric
|
12
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
13
|
+
# @return [Loxxy::Datatype::Number]
|
14
|
+
def +(other)
|
15
|
+
case other
|
16
|
+
when Number
|
17
|
+
self.class.new(value + other.value)
|
18
|
+
when Numeric
|
19
|
+
self.class.new(value + other)
|
20
|
+
else
|
21
|
+
err_msg = "`+': #{other.class} can't be coerced into #{self.class} (TypeError)"
|
22
|
+
raise TypeError, err_msg
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Perform the subtraction of two Lox numbers or
|
27
|
+
# one Lox number and a Ruby Numeric
|
28
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
29
|
+
# @return [Loxxy::Datatype::Number]
|
30
|
+
def -(other)
|
31
|
+
case other
|
32
|
+
when Number
|
33
|
+
self.class.new(value - other.value)
|
34
|
+
when Numeric
|
35
|
+
self.class.new(value - other)
|
36
|
+
else
|
37
|
+
err_msg = "`-': #{other.class} can't be coerced into #{self.class} (TypeError)"
|
38
|
+
raise TypeError, err_msg
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Perform the multiplication of two Lox numbers or
|
43
|
+
# one Lox number and a Ruby Numeric
|
44
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
45
|
+
# @return [Loxxy::Datatype::Number]
|
46
|
+
def *(other)
|
47
|
+
case other
|
48
|
+
when Number
|
49
|
+
self.class.new(value * other.value)
|
50
|
+
when Numeric
|
51
|
+
self.class.new(value * other)
|
52
|
+
else
|
53
|
+
err_msg = "'*': Operands must be numbers."
|
54
|
+
raise TypeError, err_msg
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Perform the division of two Lox numbers or
|
59
|
+
# one Lox number and a Ruby Numeric
|
60
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
61
|
+
# @return [Loxxy::Datatype::Number]
|
62
|
+
def /(other)
|
63
|
+
case other
|
64
|
+
when Number
|
65
|
+
self.class.new(value / other.value)
|
66
|
+
when Numeric
|
67
|
+
self.class.new(value / other)
|
68
|
+
else
|
69
|
+
err_msg = "'/': Operands must be numbers."
|
70
|
+
raise TypeError, err_msg
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Unary minus (return value with changed sign)
|
75
|
+
# @return [Loxxy::Datatype::Number]
|
76
|
+
def -@
|
77
|
+
self.class.new(-value)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Check the equality of a Lox number object with another object
|
81
|
+
# @param other [Datatype::BuiltinDatatype, Numeric, Object]
|
12
82
|
# @return [Datatype::Boolean]
|
13
83
|
def ==(other)
|
14
84
|
case other
|
@@ -17,27 +87,56 @@ module Loxxy
|
|
17
87
|
when Numeric
|
18
88
|
(value == other) ? True.instance : False.instance
|
19
89
|
else
|
20
|
-
|
21
|
-
raise StandardError, err_msg
|
90
|
+
False.instance
|
22
91
|
end
|
23
92
|
end
|
24
93
|
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# @
|
28
|
-
|
29
|
-
|
30
|
-
case numeric
|
94
|
+
# Check whether this Lox number has a greater value than given argument.
|
95
|
+
# @param other [Datatype::Number, Numeric]
|
96
|
+
# @return [Datatype::Boolean]
|
97
|
+
def >(other)
|
98
|
+
case other
|
31
99
|
when Number
|
32
|
-
|
100
|
+
(value > other.value) ? True.instance : False.instance
|
33
101
|
when Numeric
|
34
|
-
|
102
|
+
(value > other) ? True.instance : False.instance
|
35
103
|
else
|
36
|
-
|
37
|
-
raise
|
104
|
+
msg = "'>': Operands must be numbers."
|
105
|
+
raise StandardError, msg
|
38
106
|
end
|
39
107
|
end
|
40
108
|
|
109
|
+
# Check whether this Lox number has a greater or equal value
|
110
|
+
# than given argument.
|
111
|
+
# @param other [Datatype::Number, Numeric]
|
112
|
+
# @return [Datatype::Boolean]
|
113
|
+
def >=(other)
|
114
|
+
case other
|
115
|
+
when Number
|
116
|
+
(value >= other.value) ? True.instance : False.instance
|
117
|
+
when Numeric
|
118
|
+
(value >= other) ? True.instance : False.instance
|
119
|
+
else
|
120
|
+
msg = "'>': Operands must be numbers."
|
121
|
+
raise StandardError, msg
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Check whether this Lox number has a lesser value than given argument.
|
126
|
+
# @param other [Datatype::Number, Numeric]
|
127
|
+
# @return [Datatype::Boolean]
|
128
|
+
def <(other)
|
129
|
+
!(self >= other)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Check whether this Lox number has a lesser or equal value
|
133
|
+
# than given argument.
|
134
|
+
# @param other [Datatype::Number, Numeric]
|
135
|
+
# @return [Datatype::Boolean]
|
136
|
+
def <=(other)
|
137
|
+
!(self > other)
|
138
|
+
end
|
139
|
+
|
41
140
|
protected
|
42
141
|
|
43
142
|
def validated_value(aValue)
|
data/lib/loxxy/datatype/true.rb
CHANGED
@@ -19,6 +19,14 @@ module Loxxy
|
|
19
19
|
def true?
|
20
20
|
true
|
21
21
|
end
|
22
|
+
|
23
|
+
# Check for equality of a Lox True with another Lox object / Ruby true
|
24
|
+
# @param other [Datatype::True, TrueClass, Object]
|
25
|
+
# @return [Datatype::Boolean]
|
26
|
+
def ==(other)
|
27
|
+
thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
|
28
|
+
thruthy ? True.instance : False.instance
|
29
|
+
end
|
22
30
|
end # class
|
23
31
|
|
24
32
|
True.instance.freeze # Make the sole instance immutable
|
@@ -121,7 +121,7 @@ module Loxxy
|
|
121
121
|
rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
|
122
122
|
rule('multOp' => 'SLASH')
|
123
123
|
rule('multOp' => 'STAR')
|
124
|
-
rule('unary' => 'unaryOp unary')
|
124
|
+
rule('unary' => 'unaryOp unary').as 'unary_expr'
|
125
125
|
rule('unary' => 'call')
|
126
126
|
rule('unaryOp' => 'BANG')
|
127
127
|
rule('unaryOp' => 'MINUS')
|
@@ -138,7 +138,7 @@ module Loxxy
|
|
138
138
|
rule('primary' => 'NUMBER').as 'literal_expr'
|
139
139
|
rule('primary' => 'STRING').as 'literal_expr'
|
140
140
|
rule('primary' => 'IDENTIFIER')
|
141
|
-
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
141
|
+
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
|
142
142
|
rule('primary' => 'SUPER DOT IDENTIFIER')
|
143
143
|
|
144
144
|
# Utility rules
|
data/lib/loxxy/version.rb
CHANGED
@@ -51,6 +51,11 @@ module Loxxy
|
|
51
51
|
result = subject == ''
|
52
52
|
expect(result).to be_false
|
53
53
|
end
|
54
|
+
|
55
|
+
it 'performs the concatenation with another string' do
|
56
|
+
concatenation = LXString.new('str') + LXString.new('ing')
|
57
|
+
expect(concatenation == 'string').to be_true
|
58
|
+
end
|
54
59
|
end
|
55
60
|
end # describe
|
56
61
|
end # module
|
@@ -41,6 +41,16 @@ module Loxxy
|
|
41
41
|
it 'should give its display representation' do
|
42
42
|
expect(subject.to_str).to eq(sample_value.to_s)
|
43
43
|
end
|
44
|
+
|
45
|
+
it 'should compute the addition with another number' do
|
46
|
+
addition = subject + Number.new(10)
|
47
|
+
expect(addition == -2.34).to be_true
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should compute the subtraction with another number' do
|
51
|
+
subtraction = subject - Number.new(10)
|
52
|
+
expect(subtraction == -22.34).to be_true
|
53
|
+
end
|
44
54
|
end
|
45
55
|
end # describe
|
46
56
|
end # module
|
@@ -262,12 +262,12 @@ LOX_END
|
|
262
262
|
end # context
|
263
263
|
|
264
264
|
context 'Parsing logical expressions' do
|
265
|
-
it 'should parse the logical operations
|
265
|
+
it 'should parse the logical operations between two sub-expression' do
|
266
266
|
%w[or and].each do |connector|
|
267
267
|
input = "5 > 2 #{connector} 3 <= 4;"
|
268
268
|
ptree = subject.parse(input)
|
269
269
|
expr = ptree.root
|
270
|
-
expect(expr).to be_kind_of(Ast::
|
270
|
+
expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
|
271
271
|
expect(expr.operator).to eq(connector.to_sym)
|
272
272
|
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
273
273
|
expect(expr.operands[0].operator).to eq(:>)
|
@@ -284,9 +284,9 @@ LOX_END
|
|
284
284
|
input = '4 > 3 and 1 < 2 or 4 >= 5;'
|
285
285
|
ptree = subject.parse(input)
|
286
286
|
expr = ptree.root
|
287
|
-
expect(expr).to be_kind_of(Ast::
|
287
|
+
expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
|
288
288
|
expect(expr.operator).to eq(:or) # or has lower precedence than and
|
289
|
-
expect(expr.operands[0]).to be_kind_of(Ast::
|
289
|
+
expect(expr.operands[0]).to be_kind_of(Ast::LoxLogicalExpr)
|
290
290
|
expect(expr.operands[0].operator).to eq(:and)
|
291
291
|
conjuncts = expr.operands[0].operands
|
292
292
|
expect(conjuncts[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
data/spec/interpreter_spec.rb
CHANGED
@@ -25,6 +25,32 @@ module Loxxy
|
|
25
25
|
end
|
26
26
|
end # context
|
27
27
|
|
28
|
+
context 'Evaluating arithmetic operations code:' do
|
29
|
+
it 'should evaluate an addition of two numbers' do
|
30
|
+
result = subject.evaluate('123 + 456; // => 579')
|
31
|
+
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
32
|
+
expect(result == 579).to be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should evaluate a subtraction of two numbers' do
|
36
|
+
result = subject.evaluate('4 - 3; // => 1')
|
37
|
+
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
38
|
+
expect(result == 1).to be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should evaluate a multiplication of two numbers' do
|
42
|
+
result = subject.evaluate('5 * 3; // => 15')
|
43
|
+
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
44
|
+
expect(result == 15).to be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should evaluate a division of two numbers' do
|
48
|
+
result = subject.evaluate('8 / 2; // => 4')
|
49
|
+
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
50
|
+
expect(result == 4).to be_true
|
51
|
+
end
|
52
|
+
end # context
|
53
|
+
|
28
54
|
context 'Evaluating Lox code:' do
|
29
55
|
let(:hello_world) { 'print "Hello, world!";' }
|
30
56
|
|
@@ -33,18 +59,170 @@ module Loxxy
|
|
33
59
|
expect(result).to be_kind_of(Loxxy::Datatype::True)
|
34
60
|
end
|
35
61
|
|
36
|
-
it 'should evaluate arithmetic operation' do
|
37
|
-
result = subject.evaluate('123 + 456; // => 579')
|
38
|
-
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
39
|
-
expect(result == 579).to be_true
|
40
|
-
end
|
41
|
-
|
42
62
|
it 'should evaluate string concatenation' do
|
43
63
|
result = subject.evaluate('"str" + "ing"; // => "string"')
|
44
64
|
expect(result).to be_kind_of(Loxxy::Datatype::LXString)
|
45
65
|
expect(result == 'string').to be_true
|
46
66
|
end
|
47
67
|
|
68
|
+
it 'should perform the equality tests of two values' do
|
69
|
+
[
|
70
|
+
['nil == nil;', true],
|
71
|
+
['true == true;', true],
|
72
|
+
['true == false;', false],
|
73
|
+
['1 == 1;', true],
|
74
|
+
['1 == 2;', false],
|
75
|
+
['0 == 0;', true],
|
76
|
+
['"str" == "str";', true],
|
77
|
+
['"str" == "ing";', false],
|
78
|
+
['"" == "";', true],
|
79
|
+
['nil == false;', false],
|
80
|
+
['false == 0;', false],
|
81
|
+
['0 == "0";', false]
|
82
|
+
].each do |(source, predicted)|
|
83
|
+
lox = Loxxy::Interpreter.new
|
84
|
+
result = lox.evaluate(source)
|
85
|
+
expect(result.value == predicted).to be_truthy
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should perform the inequality test of two values' do
|
90
|
+
[
|
91
|
+
['nil != nil;', false],
|
92
|
+
['true != true;', false],
|
93
|
+
['true != false;', true],
|
94
|
+
['1 != 1;', false],
|
95
|
+
['1 != 2;', true],
|
96
|
+
['0 != 0;', false],
|
97
|
+
['"str" != "str";', false],
|
98
|
+
['"str" != "ing";', true],
|
99
|
+
['"" != "";', false],
|
100
|
+
['nil != false;', true],
|
101
|
+
['false != 0;', true],
|
102
|
+
['0 != "0";', true]
|
103
|
+
].each do |(source, predicted)|
|
104
|
+
lox = Loxxy::Interpreter.new
|
105
|
+
result = lox.evaluate(source)
|
106
|
+
expect(result.value == predicted).to be_truthy
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should evaluate a comparison of two numbers' do
|
111
|
+
[
|
112
|
+
['1 < 2;', true],
|
113
|
+
['2 < 2;', false],
|
114
|
+
['2 < 1;', false],
|
115
|
+
['1 <= 2;', true],
|
116
|
+
['2 <= 2;', true],
|
117
|
+
['2 <= 1;', false],
|
118
|
+
['1 > 2;', false],
|
119
|
+
['2 > 2;', false],
|
120
|
+
['2 > 1;', true],
|
121
|
+
['1 >= 2;', false],
|
122
|
+
['2 >= 2;', true],
|
123
|
+
['2 >= 1;', true],
|
124
|
+
['0 < -0;', false],
|
125
|
+
['-0 < 0;', false],
|
126
|
+
['0 > -0;', false],
|
127
|
+
['-0 > 0;', false],
|
128
|
+
['0 <= -0;', true],
|
129
|
+
['-0 <= 0;', true],
|
130
|
+
['0 >= -0;', true],
|
131
|
+
['-0 >= 0;', true]
|
132
|
+
].each do |(source, predicted)|
|
133
|
+
lox = Loxxy::Interpreter.new
|
134
|
+
result = lox.evaluate(source)
|
135
|
+
expect(result.value == predicted).to be_truthy
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should evaluate the change sign of a number' do
|
140
|
+
[
|
141
|
+
['- 3;', -3],
|
142
|
+
['- - 3;', 3],
|
143
|
+
['- - - 3;', -3]
|
144
|
+
].each do |(source, predicted)|
|
145
|
+
lox = Loxxy::Interpreter.new
|
146
|
+
result = lox.evaluate(source)
|
147
|
+
expect(result.value == predicted).to be_truthy
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should evaluate the negation of an object' do
|
152
|
+
[
|
153
|
+
['!true;', false],
|
154
|
+
['!false;', true],
|
155
|
+
['!!true;', true],
|
156
|
+
['!123;', false],
|
157
|
+
['!0;', false],
|
158
|
+
['!nil;', true],
|
159
|
+
['!"";', false]
|
160
|
+
].each do |(source, predicted)|
|
161
|
+
lox = Loxxy::Interpreter.new
|
162
|
+
result = lox.evaluate(source)
|
163
|
+
expect(result.value == predicted).to be_truthy
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should evaluate the "conjunction" of two values' do
|
168
|
+
[
|
169
|
+
# Return the first falsey argument
|
170
|
+
['false and 1;', false],
|
171
|
+
['nil and 1;', nil],
|
172
|
+
['true and 1;', 1],
|
173
|
+
['1 and 2 and false;', false],
|
174
|
+
['1 and 2 and nil;', nil],
|
175
|
+
|
176
|
+
# Return the last argument if all are truthy
|
177
|
+
['1 and true;', true],
|
178
|
+
['0 and true;', true],
|
179
|
+
['"false" and 0;', 0],
|
180
|
+
['1 and 2 and 3;', 3]
|
181
|
+
|
182
|
+
# TODO test short-circuit at first false argument
|
183
|
+
].each do |(source, predicted)|
|
184
|
+
lox = Loxxy::Interpreter.new
|
185
|
+
result = lox.evaluate(source)
|
186
|
+
expect(result.value == predicted).to be_truthy
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should evaluate the "disjunction" of two values' do
|
191
|
+
[
|
192
|
+
# Return the first truthy argument
|
193
|
+
['1 or true;', 1],
|
194
|
+
['false or 1;', 1],
|
195
|
+
['nil or 1;', 1],
|
196
|
+
['false or false or true;', true],
|
197
|
+
['1 and 2 and nil;', nil],
|
198
|
+
|
199
|
+
# Return the last argument if all are falsey
|
200
|
+
['false or false;', false],
|
201
|
+
['nil or false;', false],
|
202
|
+
['false or nil;', nil],
|
203
|
+
['false or false or false;', false],
|
204
|
+
['false or false or nil;', nil]
|
205
|
+
|
206
|
+
# TODO test short-circuit at first false argument
|
207
|
+
].each do |(source, predicted)|
|
208
|
+
lox = Loxxy::Interpreter.new
|
209
|
+
result = lox.evaluate(source)
|
210
|
+
expect(result.value == predicted).to be_truthy
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should supprt expressions between parentheses' do
|
215
|
+
[
|
216
|
+
['3 + 4 * 5;', 23],
|
217
|
+
['(3 + 4) * 5;', 35],
|
218
|
+
['(5 - (3 - 1)) + -(1);', 2]
|
219
|
+
].each do |(source, predicted)|
|
220
|
+
lox = Loxxy::Interpreter.new
|
221
|
+
result = lox.evaluate(source)
|
222
|
+
expect(result.value == predicted).to be_truthy
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
48
226
|
it 'should print the hello world message' do
|
49
227
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
50
228
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
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.0.
|
4
|
+
version: 0.0.19
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01-
|
11
|
+
date: 2021-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -89,10 +89,13 @@ files:
|
|
89
89
|
- lib/loxxy/ast/ast_visitor.rb
|
90
90
|
- lib/loxxy/ast/lox_binary_expr.rb
|
91
91
|
- lib/loxxy/ast/lox_compound_expr.rb
|
92
|
+
- lib/loxxy/ast/lox_grouping_expr.rb
|
92
93
|
- lib/loxxy/ast/lox_literal_expr.rb
|
94
|
+
- lib/loxxy/ast/lox_logical_expr.rb
|
93
95
|
- lib/loxxy/ast/lox_node.rb
|
94
96
|
- lib/loxxy/ast/lox_noop_expr.rb
|
95
97
|
- lib/loxxy/ast/lox_print_stmt.rb
|
98
|
+
- lib/loxxy/ast/lox_unary_expr.rb
|
96
99
|
- lib/loxxy/back_end/engine.rb
|
97
100
|
- lib/loxxy/datatype/all_datatypes.rb
|
98
101
|
- lib/loxxy/datatype/boolean.rb
|