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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e689f37b8b4893aff00f320707982cc1b09c17258abee50d0ae82f2d9d9d04db
4
- data.tar.gz: 57aa5e505c245cabd8f4e583a6300dbd5071b9ba580228103c4f9972c7679174
3
+ metadata.gz: 43f600b63e6a264cf14a760e82537693068118152aa41e2b6d87f99d993280ea
4
+ data.tar.gz: 5ea73cae221b5039ec8257ef29c49bd63d22bb2477cde37b60f4f3799fe06534
5
5
  SHA512:
6
- metadata.gz: 9336292545bd83f18d8f1be0a820717dcbe33cbb3b912697ea3e5245b726a5d79810ca6613c07017aef9d766670008bf49f59f6948922f18433db4a0df94f41e
7
- data.tar.gz: 149d09683e80f1d36a1939cfa13faefb1fc2bdae3b096bbde34d8824f67a305627018229e453b7b9655c78ae45b815726535a307125d238f6399df2c3d0f1aec
6
+ metadata.gz: 8d4944a0b164730ddf0839abc325fc0389d895abecb1cd37f22909dc5a17243c29965eb194c351044fb3087bebc1d3dfd319aa6188f05e7a31df09bb41326e18
7
+ data.tar.gz: e7f857a51b74337724fa4cfd16816d79f9a0dec2b51dcdc8c297a52773048ea2dd2349a110fbf9b0d5eea6f2c57c6397f8196d14520cff59edab1e99444d7a60
@@ -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,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
  [![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,10 +74,11 @@ require 'loxxy'
74
74
 
75
75
  lox = Loxxy::Interpreter.new
76
76
 
77
- lox_program = '12.34; // A fractional number'
77
+ lox_program = '47 - 5; // THE answer'
78
78
  result = lox.evaluate(lox_program) # => Loxxy::Datatype::Number
79
79
 
80
- puts result.value # Output: 12.34
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
- Although the interpreter should parse almost any valid Lox program,
131
- 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...
136
+
137
+ Here are the language features currently supported by the interpreter:
132
138
 
133
- Here are the language features supported by the interpreter:
134
- - Line comments,
135
- - All the Lox literals (booleans, numbers, strings and nil),
136
- - `print` statement.
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
- reduce_binary_operator(production, range, tokens, theChildren)
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
- reduce_binary_operator(production, range, tokens, theChildren)
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 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,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
@@ -11,7 +11,6 @@ module Loxxy
11
11
  super(aPosition, [anExpression])
12
12
  end
13
13
 
14
- # Abstract method.
15
14
  # Part of the 'visitee' role in Visitor design pattern.
16
15
  # @param visitor [Ast::ASTVisitor] the visitor
17
16
  def accept(visitor)
@@ -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,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
@@ -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 'builtin_datatype'
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
- # Compare a Lox String with another Lox (or genuine Ruby) String
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
- when LXString
15
- value == other.value
16
- when String
17
- value == other
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
- err_msg = "Cannot compare a #{self.class} with #{other.class}"
20
- raise StandardError, err_msg
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
 
@@ -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 'builtin_datatype'
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)
@@ -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')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.13'
4
+ VERSION = '0.0.18'
5
5
  end
@@ -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
- expect(subject).to eq(sample_text)
31
- expect(subject).to eq(LXString.new(sample_text.dup))
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
- expect(subject).not_to eq('')
34
- expect(subject).not_to eq('other-text')
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 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,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.13
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-10 00:00:00.000000000 Z
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