loxxy 0.0.13 → 0.0.18
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 +69 -0
- data/README.md +128 -10
- data/lib/loxxy/ast/all_lox_nodes.rb +4 -1
- data/lib/loxxy/ast/ast_builder.rb +25 -3
- data/lib/loxxy/ast/ast_visitor.rb +45 -1
- data/lib/loxxy/ast/lox_binary_expr.rb +7 -1
- data/lib/loxxy/ast/lox_logical_expr.rb +28 -0
- data/lib/loxxy/ast/lox_print_stmt.rb +0 -1
- data/lib/loxxy/ast/lox_unary_expr.rb +27 -0
- data/lib/loxxy/back_end/engine.rb +56 -1
- data/lib/loxxy/datatype/boolean.rb +11 -0
- data/lib/loxxy/datatype/builtin_datatype.rb +55 -0
- data/lib/loxxy/datatype/false.rb +28 -0
- data/lib/loxxy/datatype/lx_string.rb +26 -10
- data/lib/loxxy/datatype/nil.rb +22 -0
- data/lib/loxxy/datatype/number.rb +132 -1
- data/lib/loxxy/datatype/true.rb +14 -0
- data/lib/loxxy/front_end/grammar.rb +1 -1
- data/lib/loxxy/version.rb +1 -1
- data/spec/datatype/lx_string_spec.rb +28 -5
- data/spec/datatype/number_spec.rb +26 -0
- data/spec/front_end/parser_spec.rb +4 -4
- data/spec/interpreter_spec.rb +178 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43f600b63e6a264cf14a760e82537693068118152aa41e2b6d87f99d993280ea
|
4
|
+
data.tar.gz: 5ea73cae221b5039ec8257ef29c49bd63d22bb2477cde37b60f4f3799fe06534
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d4944a0b164730ddf0839abc325fc0389d895abecb1cd37f22909dc5a17243c29965eb194c351044fb3087bebc1d3dfd319aa6188f05e7a31df09bb41326e18
|
7
|
+
data.tar.gz: e7f857a51b74337724fa4cfd16816d79f9a0dec2b51dcdc8c297a52773048ea2dd2349a110fbf9b0d5eea6f2c57c6397f8196d14520cff59edab1e99444d7a60
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,72 @@
|
|
1
|
+
## [0.0.18] - 2021-01-13
|
2
|
+
- The interpreter can evaluate `and`, `or`expressions.
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
## [0.0.17] - 2021-01-12
|
7
|
+
- The interpreter can evaluate all arithmetic and comparison operations.
|
8
|
+
- It implements `==`, `!=` and the unary operations `!`, `-`
|
9
|
+
|
10
|
+
## Added
|
11
|
+
- Class `AST::LoxUnaryExpr`
|
12
|
+
- Method `AST::ASTBuilder#reduce_unary_expr` to support the evaluation of `!` and ``-@`
|
13
|
+
- Method `Ast::ASTVisitor#visit_unnary_expr` for visiting unary expressions
|
14
|
+
- Method `Backend::Engine#after_unary_expr` evaluating an unary expression
|
15
|
+
- In class `Datatype::BuiltinDatatype` the methods `falsey?`, `truthy?`, `!`, `!=`
|
16
|
+
- In class `Datatype::Number`the methods `<`, `<=`, ´>´, `>=` and `-@`
|
17
|
+
|
18
|
+
## Changed
|
19
|
+
- File `README.md` updated.
|
20
|
+
|
21
|
+
## [0.0.16] - 2021-01-11
|
22
|
+
- The interpreter can evaluate product and division of two numbers.
|
23
|
+
- It also implements equality `==` and inequality `!=` operators
|
24
|
+
|
25
|
+
## Added
|
26
|
+
- Method `Datatype::False#==` for equality testing
|
27
|
+
- Method `Datatype::False#!=` for inequality testing
|
28
|
+
- Method `Datatype::LXString#==` for equality testing
|
29
|
+
- Method `Datatype::LXString#!=` for inequality testing
|
30
|
+
- Method `Datatype::Nil#==` for equality testing
|
31
|
+
- Method `Datatype::Nil#!=` for inequality testing
|
32
|
+
- Method `Datatype::Number#==` for equality testing
|
33
|
+
- Method `Datatype::Number#!=` for inequality testing
|
34
|
+
- Method `Datatype::Number#*` for multiply operator
|
35
|
+
- Method `Datatype::Number#/` for divide operator
|
36
|
+
- Method `Datatype::True#==` for equality testing
|
37
|
+
- Method `Datatype::True#!=` for inequality testing
|
38
|
+
|
39
|
+
## Changed
|
40
|
+
- Method `BackEnd::Engine#after_binary_expr` to allow `*`, `/`, `==`, `!=` operators
|
41
|
+
- File `README.md` updated for the newly implemented operators
|
42
|
+
|
43
|
+
## [0.0.15] - 2021-01-11
|
44
|
+
- The interpreter can evaluate substraction between two numbers.
|
45
|
+
|
46
|
+
## Added
|
47
|
+
- Method `Datatype::Number#-` implmenting the subtraction operation
|
48
|
+
|
49
|
+
## Changed
|
50
|
+
- File `README.md` minor editorial changes.
|
51
|
+
- File `lx_string_spec.rb` Added test for string concatentation
|
52
|
+
- File `number_spec.rb` Added tests for addition and subtraction operations
|
53
|
+
- File `interpreter_spec.rb` Added tests for subtraction operation
|
54
|
+
|
55
|
+
## [0.0.14] - 2021-01-10
|
56
|
+
- The interpreter can evaluate addition of numbers and string concatenation
|
57
|
+
|
58
|
+
## Added
|
59
|
+
- Method `Ast::ASTVisitor#visit_binary_expr` for visiting binary expressions
|
60
|
+
- Method `Ast::LoxBinaryExpr#accept` for visitor pattern
|
61
|
+
- Method `BackEnd::Engine#after_binary_expr` to trigger execution of binary operator
|
62
|
+
- `Boolean` class hierarchy: added methos `true?` and `false?` to ease spec test writing
|
63
|
+
- Method `Datatype::LXString#+` implementation of the string concatenation
|
64
|
+
- Method `Datatype::Number#+` implementation of the addition of numbers
|
65
|
+
|
66
|
+
## Changed
|
67
|
+
- File `interpreter_spec.rb` Added tests for addition operation and string concatenation
|
68
|
+
|
69
|
+
|
1
70
|
## [0.0.13] - 2021-01-10
|
2
71
|
- The interpreter can evaluate directly simple literals.
|
3
72
|
|
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,10 +74,11 @@ 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...
|
81
|
+
puts result.value # Output: 42
|
81
82
|
```
|
82
83
|
|
83
84
|
## Example using RawParser class
|
@@ -127,14 +128,131 @@ program
|
|
127
128
|
```
|
128
129
|
|
129
130
|
## Suppported Lox language features
|
130
|
-
|
131
|
-
|
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...
|
136
|
+
|
137
|
+
Here are the language features currently supported by the interpreter:
|
132
138
|
|
133
|
-
|
134
|
-
-
|
135
|
-
-
|
136
|
-
-
|
139
|
+
- [Comments](#comments)
|
140
|
+
- [Keywords](#keywords)
|
141
|
+
- [Datatypes](#datatypes)
|
142
|
+
- [Statements](#statements)
|
143
|
+
-[Expressions](#expressions)
|
144
|
+
-[Print Statement](#print-statement)
|
137
145
|
|
146
|
+
### Comments
|
147
|
+
|
148
|
+
Loxxy supports single line C-style comments.
|
149
|
+
|
150
|
+
```javascript
|
151
|
+
// single line comment
|
152
|
+
```
|
153
|
+
|
154
|
+
### Keywords
|
155
|
+
Loxxy implements the following __Lox__ reserved keywords:
|
156
|
+
```lang-none
|
157
|
+
and, false, nil, or, print, true
|
158
|
+
```
|
159
|
+
|
160
|
+
### Datatypes
|
161
|
+
|
162
|
+
loxxy supports all the standard __Lox__ datatypes:
|
163
|
+
- `Boolean`: Can be `true` or `false`
|
164
|
+
- `Number`: Can be an integer or a floating-point numbers. For example: `123, 12.34, -45.67`
|
165
|
+
- `String`: Sequence of characters surrounded by `"`. For example: `"Hello!"`
|
166
|
+
- `Nil`: Used to define a null value, denoted by the `nil` keyword
|
167
|
+
|
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
|
+
##### Print Statement
|
249
|
+
|
250
|
+
The statement print + expression + ; prints the result of the expression to stdout.
|
251
|
+
|
252
|
+
``` javascript
|
253
|
+
print "Hello, world!"; // Output: Hello, world!
|
254
|
+
|
255
|
+
```
|
138
256
|
|
139
257
|
## Installation
|
140
258
|
|
@@ -1,5 +1,8 @@
|
|
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_unary_expr'
|
6
|
+
require_relative 'lox_binary_expr'
|
7
|
+
require_relative 'lox_logical_expr'
|
8
|
+
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]
|
@@ -145,7 +160,7 @@ module Loxxy
|
|
145
160
|
|
146
161
|
# rule('logic_or' => 'logic_and disjunct_plus')
|
147
162
|
def reduce_logic_or_plus(production, range, tokens, theChildren)
|
148
|
-
|
163
|
+
reduce_logical_expr(production, range, tokens, theChildren)
|
149
164
|
end
|
150
165
|
|
151
166
|
# rule('disjunct_plus' => 'disjunct_plus OR logic_and')
|
@@ -160,7 +175,7 @@ module Loxxy
|
|
160
175
|
|
161
176
|
# rule('logic_and' => 'equality conjunct_plus')
|
162
177
|
def reduce_logic_and_plus(production, range, tokens, theChildren)
|
163
|
-
|
178
|
+
reduce_logical_expr(production, range, tokens, theChildren)
|
164
179
|
end
|
165
180
|
|
166
181
|
# rule('conjunct_plus' => 'conjunct_plus AND equality')
|
@@ -231,6 +246,13 @@ module Loxxy
|
|
231
246
|
reduce_binary_plus_end(production, range, tokens, theChildren)
|
232
247
|
end
|
233
248
|
|
249
|
+
# rule('unary' => 'unaryOp unary')
|
250
|
+
def reduce_unary_expr(_production, _range, tokens, theChildren)
|
251
|
+
operator = Name2unary[theChildren[0].symbol.name].to_sym
|
252
|
+
operand = theChildren[1]
|
253
|
+
LoxUnaryExpr.new(tokens[0].position, operator, operand)
|
254
|
+
end
|
255
|
+
|
234
256
|
# rule('primary' => 'FALSE' | TRUE').as 'literal_expr'
|
235
257
|
def reduce_literal_expr(_production, _range, _tokens, theChildren)
|
236
258
|
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,6 +59,36 @@ module Loxxy
|
|
59
59
|
broadcast(:after_print_stmt, aPrintStmt)
|
60
60
|
end
|
61
61
|
|
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.
|
77
|
+
# @param aBinaryExpr [AST::LOXBinaryExpr] the binary expression node to visit
|
78
|
+
def visit_binary_expr(aBinaryExpr)
|
79
|
+
broadcast(:before_binary_expr, aBinaryExpr)
|
80
|
+
traverse_subnodes(aBinaryExpr)
|
81
|
+
broadcast(:after_binary_expr, aBinaryExpr)
|
82
|
+
end
|
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
|
+
|
62
92
|
# Visit event. The visitor is visiting the
|
63
93
|
# given terminal node containing a datatype object.
|
64
94
|
# @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
|
@@ -90,6 +120,20 @@ module Loxxy
|
|
90
120
|
broadcast(:after_subnodes, aParentNode, subnodes)
|
91
121
|
end
|
92
122
|
|
123
|
+
# Visit event. The visitor is about to visit one given subnode of a non
|
124
|
+
# terminal node.
|
125
|
+
# @param aParentNode [Ast::LocCompoundExpr] the parent node.
|
126
|
+
# @param index [integer] index of child subnode
|
127
|
+
def traverse_given_subnode(aParentNode, index)
|
128
|
+
subnode = aParentNode.subnodes[index]
|
129
|
+
broadcast(:before_given_subnode, aParentNode, subnode)
|
130
|
+
|
131
|
+
# Now, let's proceed with the visit of that subnode
|
132
|
+
subnode.accept(self)
|
133
|
+
|
134
|
+
broadcast(:after_given_subnode, aParentNode, subnode)
|
135
|
+
end
|
136
|
+
|
93
137
|
# Send a notification to all subscribers.
|
94
138
|
# @param msg [Symbol] event to notify
|
95
139
|
# @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.
|
@@ -16,6 +16,12 @@ module Loxxy
|
|
16
16
|
@operator = anOperator
|
17
17
|
end
|
18
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_binary_expr(self)
|
23
|
+
end
|
24
|
+
|
19
25
|
alias operands subnodes
|
20
26
|
end # class
|
21
27
|
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,6 +40,61 @@ 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
|
+
|
75
|
+
def after_binary_expr(aBinaryExpr)
|
76
|
+
op = aBinaryExpr.operator
|
77
|
+
operand2 = stack.pop
|
78
|
+
operand1 = stack.pop
|
79
|
+
if operand1.respond_to?(op)
|
80
|
+
stack.push operand1.send(op, operand2)
|
81
|
+
else
|
82
|
+
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
83
|
+
raise StandardError, msg1
|
84
|
+
end
|
85
|
+
end
|
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
|
+
|
43
98
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
44
99
|
def before_literal_expr(literalExpr)
|
45
100
|
stack.push(literalExpr.literal)
|
@@ -8,6 +8,17 @@ module Loxxy
|
|
8
8
|
# An instance acts merely as a wrapper around a Ruby representation
|
9
9
|
# of the value.
|
10
10
|
class Boolean < BuiltinDatatype
|
11
|
+
# Is this object representing a false value in Lox?
|
12
|
+
# @return [FalseClass, TrueClass]
|
13
|
+
def false?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
# Is this object representing the true value in Lox?
|
18
|
+
# @return [FalseClass, TrueClass]
|
19
|
+
def true?
|
20
|
+
false
|
21
|
+
end
|
11
22
|
end # class
|
12
23
|
end # module
|
13
24
|
end # module
|
@@ -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
@@ -13,6 +13,34 @@ module Loxxy
|
|
13
13
|
def initialize
|
14
14
|
super(false)
|
15
15
|
end
|
16
|
+
|
17
|
+
# Is this object representing a false value in Lox?
|
18
|
+
# @return [TrueClass]
|
19
|
+
def false?
|
20
|
+
true
|
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
|
16
44
|
end # class
|
17
45
|
|
18
46
|
False.instance.freeze # Make the sole instance immutable
|
@@ -1,23 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'false'
|
4
|
+
require_relative 'true'
|
4
5
|
|
5
6
|
module Loxxy
|
6
7
|
module Datatype
|
7
8
|
# Class for representing a Lox string of characters value.
|
8
9
|
class LXString < BuiltinDatatype
|
9
|
-
#
|
10
|
-
# @param other [Datatype::LxString, String]
|
11
|
-
# @return [Boolean]
|
10
|
+
# Check the equality of a Lox String with another Lox object.
|
11
|
+
# @param other [Datatype::LxString, String, Object]
|
12
|
+
# @return [Datatype::Boolean]
|
12
13
|
def ==(other)
|
13
14
|
case other
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
when LXString
|
16
|
+
(value == other.value) ? True.instance : False.instance
|
17
|
+
when String
|
18
|
+
(value == other) ? True.instance : False.instance
|
18
19
|
else
|
19
|
-
|
20
|
-
|
20
|
+
False.instance
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Perform the concatenation of two Lox stings or
|
25
|
+
# one Lox string and a Ruby String
|
26
|
+
# @param other [Loxxy::Datatype::LXString, String]
|
27
|
+
# @return [Loxxy::Datatype::LXString]
|
28
|
+
def +(other)
|
29
|
+
case other
|
30
|
+
when LXString
|
31
|
+
self.class.new(value + other.value)
|
32
|
+
when Numeric
|
33
|
+
self.class.new(value + other)
|
34
|
+
else
|
35
|
+
err_msg = "`+': #{other.class} can't be coerced into #{self.class} (TypeError)"
|
36
|
+
raise TypeError, err_msg
|
21
37
|
end
|
22
38
|
end
|
23
39
|
|
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
|
@@ -1,11 +1,142 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'false'
|
4
|
+
require_relative 'true'
|
4
5
|
|
5
6
|
module Loxxy
|
6
7
|
module Datatype
|
7
8
|
# Class for representing a Lox numeric value.
|
8
9
|
class Number < BuiltinDatatype
|
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]
|
82
|
+
# @return [Datatype::Boolean]
|
83
|
+
def ==(other)
|
84
|
+
case other
|
85
|
+
when Number
|
86
|
+
(value == other.value) ? True.instance : False.instance
|
87
|
+
when Numeric
|
88
|
+
(value == other) ? True.instance : False.instance
|
89
|
+
else
|
90
|
+
False.instance
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
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
|
99
|
+
when Number
|
100
|
+
(value > other.value) ? True.instance : False.instance
|
101
|
+
when Numeric
|
102
|
+
(value > other) ? True.instance : False.instance
|
103
|
+
else
|
104
|
+
msg = "'>': Operands must be numbers."
|
105
|
+
raise StandardError, msg
|
106
|
+
end
|
107
|
+
end
|
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
|
+
|
9
140
|
protected
|
10
141
|
|
11
142
|
def validated_value(aValue)
|
data/lib/loxxy/datatype/true.rb
CHANGED
@@ -13,6 +13,20 @@ module Loxxy
|
|
13
13
|
def initialize
|
14
14
|
super(true)
|
15
15
|
end
|
16
|
+
|
17
|
+
# Is this object representing the true value in Lox?
|
18
|
+
# @return [TrueClass]
|
19
|
+
def true?
|
20
|
+
true
|
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
|
16
30
|
end # class
|
17
31
|
|
18
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')
|
data/lib/loxxy/version.rb
CHANGED
@@ -26,12 +26,35 @@ module Loxxy
|
|
26
26
|
expect(subject.to_str).to eq(sample_text)
|
27
27
|
end
|
28
28
|
|
29
|
-
it 'compares with another string' do
|
30
|
-
|
31
|
-
expect(
|
29
|
+
it 'compares with another Lox string' do
|
30
|
+
result = subject == LXString.new(sample_text.dup)
|
31
|
+
expect(result).to be_true
|
32
32
|
|
33
|
-
|
34
|
-
expect(
|
33
|
+
result = subject == LXString.new('other-text')
|
34
|
+
expect(result).to be_false
|
35
|
+
|
36
|
+
result = subject == LXString.new('')
|
37
|
+
expect(result).to be_false
|
38
|
+
|
39
|
+
# Two empty strings are equal
|
40
|
+
result = LXString.new('') == LXString.new('')
|
41
|
+
expect(result).to be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'compares with a Ruby string' do
|
45
|
+
result = subject == sample_text.dup
|
46
|
+
expect(result).to be_true
|
47
|
+
|
48
|
+
result = subject == 'other-text'
|
49
|
+
expect(result).to be_false
|
50
|
+
|
51
|
+
result = subject == ''
|
52
|
+
expect(result).to be_false
|
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
|
35
58
|
end
|
36
59
|
end
|
37
60
|
end # describe
|
@@ -22,9 +22,35 @@ module Loxxy
|
|
22
22
|
end
|
23
23
|
|
24
24
|
context 'Provided services:' do
|
25
|
+
it 'should compare with other Lox numbers' do
|
26
|
+
result = subject == Number.new(sample_value)
|
27
|
+
expect(result).to be_true
|
28
|
+
|
29
|
+
result = subject == Number.new(5)
|
30
|
+
expect(result).to be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should compare with Ruby numbers' do
|
34
|
+
result = subject == sample_value
|
35
|
+
expect(result).to be_true
|
36
|
+
|
37
|
+
result = subject == 5
|
38
|
+
expect(result).to be_false
|
39
|
+
end
|
40
|
+
|
25
41
|
it 'should give its display representation' do
|
26
42
|
expect(subject.to_str).to eq(sample_value.to_s)
|
27
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
|
28
54
|
end
|
29
55
|
end # describe
|
30
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,6 +59,158 @@ module Loxxy
|
|
33
59
|
expect(result).to be_kind_of(Loxxy::Datatype::True)
|
34
60
|
end
|
35
61
|
|
62
|
+
it 'should evaluate string concatenation' do
|
63
|
+
result = subject.evaluate('"str" + "ing"; // => "string"')
|
64
|
+
expect(result).to be_kind_of(Loxxy::Datatype::LXString)
|
65
|
+
expect(result == 'string').to be_true
|
66
|
+
end
|
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
|
+
|
36
214
|
it 'should print the hello world message' do
|
37
215
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
38
216
|
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.18
|
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-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -90,9 +90,11 @@ files:
|
|
90
90
|
- lib/loxxy/ast/lox_binary_expr.rb
|
91
91
|
- lib/loxxy/ast/lox_compound_expr.rb
|
92
92
|
- lib/loxxy/ast/lox_literal_expr.rb
|
93
|
+
- lib/loxxy/ast/lox_logical_expr.rb
|
93
94
|
- lib/loxxy/ast/lox_node.rb
|
94
95
|
- lib/loxxy/ast/lox_noop_expr.rb
|
95
96
|
- lib/loxxy/ast/lox_print_stmt.rb
|
97
|
+
- lib/loxxy/ast/lox_unary_expr.rb
|
96
98
|
- lib/loxxy/back_end/engine.rb
|
97
99
|
- lib/loxxy/datatype/all_datatypes.rb
|
98
100
|
- lib/loxxy/datatype/boolean.rb
|