loxxy 0.0.17 → 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: 6eed6a7de54b89c276b0f098cd7d824f26c0ce9ba957d4fb9e3a4d12e6dfbc4f
4
- data.tar.gz: ba7c00f11c5b69322aec0d447c2fef9cf6d6d74b2ea7bb16096581508a786427
3
+ metadata.gz: 43f600b63e6a264cf14a760e82537693068118152aa41e2b6d87f99d993280ea
4
+ data.tar.gz: 5ea73cae221b5039ec8257ef29c49bd63d22bb2477cde37b60f4f3799fe06534
5
5
  SHA512:
6
- metadata.gz: 1b2c3826f085ce3f723cdbc70c3bdc3563817270985a321db05f97ca1ff81971bf872db49631d3fa20580932f7a0e2910bf3c6c84daf7873cdcd1f04c3392a99
7
- data.tar.gz: 4e3d075facaf9c370d78f3f3ac89b7226e375eb32064535e3401c5ef52b8ca43c9d4c6cfb8f027b4037c7baa7c5945b0fd0dbe1e8b1e580fc732c4a38f7c57c6
6
+ metadata.gz: 8d4944a0b164730ddf0839abc325fc0389d895abecb1cd37f22909dc5a17243c29965eb194c351044fb3087bebc1d3dfd319aa6188f05e7a31df09bb41326e18
7
+ data.tar.gz: e7f857a51b74337724fa4cfd16816d79f9a0dec2b51dcdc8c297a52773048ea2dd2349a110fbf9b0d5eea6f2c57c6397f8196d14520cff59edab1e99444d7a60
@@ -1,3 +1,8 @@
1
+ ## [0.0.18] - 2021-01-13
2
+ - The interpreter can evaluate `and`, `or`expressions.
3
+
4
+
5
+
1
6
  ## [0.0.17] - 2021-01-12
2
7
  - The interpreter can evaluate all arithmetic and comparison operations.
3
8
  - It implements `==`, `!=` and the unary operations `!`, `-`
data/README.md CHANGED
@@ -128,16 +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)
142
+ - [Statements](#statements)
143
+ -[Expressions](#expressions)
144
+ -[Print Statement](#print-statement)
141
145
 
142
146
  ### Comments
143
147
 
@@ -148,36 +152,10 @@ Loxxy supports single line C-style comments.
148
152
  ```
149
153
 
150
154
  ### Keywords
151
-
152
- The parser knows all the __Lox__ reserved keywords:
155
+ Loxxy implements the following __Lox__ reserved keywords:
153
156
  ```lang-none
154
- and, class, else, false, fun, for, if, nil, or,
155
- print, return, super, this, true, var, while
157
+ and, false, nil, or, print, true
156
158
  ```
157
- Of these, the interpreter implements: `false`, `nil`, `print`, `true`
158
-
159
- ### Operators and Special Chars
160
- #### Operators
161
- The __loxxy__ interpreter supports all the __Lox__ unary and binary operators:
162
- - Arithmetic operators: `+`, `-`, `*`, `/`
163
- - Comparison operators: `>`, `>=`, `<`, `<=`
164
- - Equality operators: `==`, `!=`
165
- - Unary negate (change sign): `-`
166
- - Unary not: `!`
167
-
168
- #### Delimiters
169
- The parser knows all the __Lox__ grouping delimiters:
170
- (`, ), `{`, `}`
171
-
172
- These aren't yet implemented in the interpreter.
173
-
174
- The other characters that have a special meaning in __Lox__ are:
175
- - `,` Used in parameter list
176
- - `.` For the dot notation (i.e. calling a method)
177
- - `;` The semicolon is used to terminates expressions
178
- - `=` Assignment
179
-
180
- The parser recognizes them all but the interpreter accepts the semicolons only.
181
159
 
182
160
  ### Datatypes
183
161
 
@@ -187,24 +165,93 @@ loxxy supports all the standard __Lox__ datatypes:
187
165
  - `String`: Sequence of characters surrounded by `"`. For example: `"Hello!"`
188
166
  - `Nil`: Used to define a null value, denoted by the `nil` keyword
189
167
 
190
- ## Statements
191
- ### Implemented expressions
192
- Loxxy implements expressions:
193
- - Plain literals only; or,
194
- - (In)equality testing between two values; or,
195
- - Basic arithmetic operations (`+`, `-`, `*`, `/`, `unary -`); or,
196
- - Comparison between two numbers; or,
197
- - Concatenation of two strings,
198
- - Negation `!`
199
-
200
- ### Implemented statements
201
- Loxxy implements the following statements:
202
- - Expressions (see above sub-section)
203
- - Print statement
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!
204
254
 
205
- ```javascript
206
- // Print statement with nested string concatenation
207
- print "Hello" + ", " + "world!";
208
255
  ```
209
256
 
210
257
  ## Installation
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'lox_literal_expr'
4
4
  require_relative 'lox_noop_expr'
5
- require_relative 'lox_binary_expr'
6
5
  require_relative 'lox_unary_expr'
6
+ require_relative 'lox_binary_expr'
7
+ require_relative 'lox_logical_expr'
7
8
  require_relative 'lox_print_stmt'
@@ -96,6 +96,17 @@ module Loxxy
96
96
  node
97
97
  end
98
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
+
99
110
  # rule('lhs' => 'nonterm_i nonterm_k_plus')
100
111
  def reduce_binary_operator(_production, _range, tokens, theChildren)
101
112
  operand1 = theChildren[0]
@@ -149,7 +160,7 @@ module Loxxy
149
160
 
150
161
  # rule('logic_or' => 'logic_and disjunct_plus')
151
162
  def reduce_logic_or_plus(production, range, tokens, theChildren)
152
- reduce_binary_operator(production, range, tokens, theChildren)
163
+ reduce_logical_expr(production, range, tokens, theChildren)
153
164
  end
154
165
 
155
166
  # rule('disjunct_plus' => 'disjunct_plus OR logic_and')
@@ -164,7 +175,7 @@ module Loxxy
164
175
 
165
176
  # rule('logic_and' => 'equality conjunct_plus')
166
177
  def reduce_logic_and_plus(production, range, tokens, theChildren)
167
- reduce_binary_operator(production, range, tokens, theChildren)
178
+ reduce_logical_expr(production, range, tokens, theChildren)
168
179
  end
169
180
 
170
181
  # rule('conjunct_plus' => 'conjunct_plus AND equality')
@@ -59,6 +59,20 @@ 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
+
62
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)
@@ -106,6 +120,20 @@ module Loxxy
106
120
  broadcast(:after_subnodes, aParentNode, subnodes)
107
121
  end
108
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
+
109
137
  # Send a notification to all subscribers.
110
138
  # @param msg [Symbol] event to notify
111
139
  # @param args [Array] arguments of the notification.
@@ -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
@@ -40,6 +40,38 @@ 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
@@ -28,6 +28,13 @@ module Loxxy
28
28
  true # Default implementation
29
29
  end
30
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
+
31
38
  # Negation ('not')
32
39
  # Returns a boolean with opposite truthiness value.
33
40
  # @return [Datatype::Boolean]
@@ -35,11 +42,18 @@ module Loxxy
35
42
  falsey? ? True.instance : False.instance
36
43
  end
37
44
 
38
- # Check for inequality of this object with another Lox object
39
- # @param other [Datatype::BuiltinDatatype, Object]
40
- # @return [Datatype::Boolean]
41
- def !=(other)
42
- !(self == other)
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)
43
57
  end
44
58
 
45
59
  # Method called from Lox to obtain the text representation of the boolean.
@@ -53,6 +67,19 @@ module Loxxy
53
67
  def validated_value(aValue)
54
68
  aValue
55
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
56
83
  end # class
57
84
  end # module
58
85
  end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.17'
4
+ VERSION = '0.0.18'
5
5
  end
@@ -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)
@@ -164,6 +164,53 @@ module Loxxy
164
164
  end
165
165
  end
166
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
+
167
214
  it 'should print the hello world message' do
168
215
  expect { subject.evaluate(hello_world) }.not_to raise_error
169
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.17
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-12 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,6 +90,7 @@ 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