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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18b1d3e9f498ca8249775f55cc3950622d9f5a49cd67db912816cc60f15b3d16
4
- data.tar.gz: 5766130089ebbe6d85a90a9f59ac1a505202c0e2646d4d7da93af025a80b0651
3
+ metadata.gz: eced55bddfd7918cbc7aea8eff5f7ecd37aa88baa6e5a6f7350f5bd7606e99af
4
+ data.tar.gz: f9b8104a0f3a273466ff568a014e345437f4c568621828de8da44961fb52f0e5
5
5
  SHA512:
6
- metadata.gz: 621832b48a5625081a0b82a8f8992d78709a1d4a053d8e4719140510242ce14d4418ca23c82d9f52186453c42d5d432203e7af496d7f92726f75622c743119e2
7
- data.tar.gz: aa67901fae29e13d3154e274c7014fe2002fb7e054c62cf253e87e44ac421747db9f5f05ef1e108cd92c8c0124ceb93b31fa537de64f875607c096465f89aee4
6
+ metadata.gz: d5b23defe380228bda4e32d9cb94909767fdc42242111f00be214bfbead6323a5e6acda0d78dcaf4e1c2e1fa7d13cd0363661af959a62b093bfcc50c34c290a0
7
+ data.tar.gz: 96a6a77824cd97b248ad6a78d66d4757c81549fa199021f446883d690f2702dbf4d9879950505d9aa54f45376bd7e6204be8b054e27b43a5576aca744be40047
@@ -250,7 +250,7 @@ Style/IfUnlessModifier:
250
250
  Enabled: false
251
251
 
252
252
  Style/InverseMethods:
253
- Enabled: true
253
+ Enabled: false
254
254
 
255
255
  Style/MissingRespondToMissing:
256
256
  Enabled: false
@@ -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
  [![Gem Version](https://badge.fury.io/rb/loxxy.svg)](https://badge.fury.io/rb/loxxy)
3
3
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](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
- The __loxxy__ gem also a parser class `RawPaser` that can parse, in principle, any valid Lox input.
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 = '37 + 5; // THE answer'
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
- Although the interpreter should parse almost any valid Lox program,
132
- it currently can evaluate a tiny set of AST node (AST = Abstract Syntax Tree).
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
- - `print` statement.
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, class, else, false, fun, for, if, nil, or,
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
- Loxxy supports the following standard __Lox__ datatypes:
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
- All the Lox literals (booleans, numbers, strings and nil),
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
- ### Implemented statements
202
- Loxxy implements the following statements:
203
- - Expressions (see above sub-section)
204
- - Print statement
257
+ ##### Print Statement
205
258
 
206
- ```javascript
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
- reduce_binary_operator(production, range, tokens, theChildren)
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
- reduce_binary_operator(production, range, tokens, theChildren)
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 expression.
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 print statement expression.
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, luanch the visit
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
- implemented = [:+].include?(op)
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
@@ -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
- # Compare a Lox String with another Lox (or genuine Ruby) String
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
- err_msg = "Cannot compare a #{self.class} with #{other.class}"
21
- raise StandardError, err_msg
20
+ False.instance
22
21
  end
23
22
  end
24
23
 
@@ -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
- # Compare a Lox Number with another Lox (or genuine Ruby) Number
11
- # @param other [Datatype::Number, Numeric]
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
- err_msg = "Cannot compare a #{self.class} with #{other.class}"
21
- raise StandardError, err_msg
90
+ False.instance
22
91
  end
23
92
  end
24
93
 
25
- # Perform the addition of two Lox numbers or
26
- # one Lox number and a Ruby Numeric
27
- # @param numeric [Loxxy::Datatype::Number, Numeric]
28
- # @return [Loxxy::Datatype::Number]
29
- def +(numeric)
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
- self.class.new(value + numeric.value)
100
+ (value > other.value) ? True.instance : False.instance
33
101
  when Numeric
34
- self.class.new(value + numeric)
102
+ (value > other) ? True.instance : False.instance
35
103
  else
36
- err_msg = "`+': #{numeric.class} can't be coerced into #{self.class} (TypeError)"
37
- raise TypeError, err_msg
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)
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.14'
4
+ VERSION = '0.0.19'
5
5
  end
@@ -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 betweentwo sub-expression' do
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::LoxBinaryExpr)
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::LoxBinaryExpr)
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::LoxBinaryExpr)
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)
@@ -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.14
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-10 00:00:00.000000000 Z
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