loxxy 0.0.19 → 0.0.20

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: eced55bddfd7918cbc7aea8eff5f7ecd37aa88baa6e5a6f7350f5bd7606e99af
4
- data.tar.gz: f9b8104a0f3a273466ff568a014e345437f4c568621828de8da44961fb52f0e5
3
+ metadata.gz: e6628d902c258d0aa2d589fd0960ab812addbfe8f0fbf162cfb627a90fbc1a40
4
+ data.tar.gz: c1fe1f5ce4b3dc7109c70783b78953fd72ffbbd837d58f8350d2a03615f80222
5
5
  SHA512:
6
- metadata.gz: d5b23defe380228bda4e32d9cb94909767fdc42242111f00be214bfbead6323a5e6acda0d78dcaf4e1c2e1fa7d13cd0363661af959a62b093bfcc50c34c290a0
7
- data.tar.gz: 96a6a77824cd97b248ad6a78d66d4757c81549fa199021f446883d690f2702dbf4d9879950505d9aa54f45376bd7e6204be8b054e27b43a5576aca744be40047
6
+ metadata.gz: 301160cc1a00e819cc13cfb3273985b6af1ddadd94b236e4e7be0b346744355327be69d3b2d338ece3b6148a905d10a91dc3f6ef828912b7750efcdb0f4e5238
7
+ data.tar.gz: cc5bb436b6e487d7752180d42d046d5a09a6817878428d757b65b26f2955133985598b55e7d63c13d249ebef110914a3a2b8292777a2857b15dcba885c74c106
@@ -1,4 +1,13 @@
1
- ## [0.0.18] - 2021-01-14
1
+ ## [0.0.20] - 2021-01-15
2
+ - The interpreter supports the `if` ... `else` statement.
3
+
4
+ ## Added
5
+ - Class `Ast::LoxItStmt`, AST node specific for `if` `else` statements
6
+ - Method `Ast::ASTBuilder#reduce_if_stmt` as semantic action for if ... else
7
+ - Method `Ast::ASTVisitor#visit_if_stmt` for visiting `LoxIfStmt` nodes
8
+ - Method `Engine::after_if_stmt` implementation of the control flow
9
+
10
+ ## [0.0.19] - 2021-01-14
2
11
  - The interpreter supports expressions between parentheses (grouping).
3
12
 
4
13
  ## Added
@@ -7,6 +16,10 @@
7
16
  - Method `Ast::ASTVisitor#visit_grouping_expr` for visiting grouping expressions
8
17
  - Method `Engine::after_grouping_expr`for the evaluation of grouping expressions
9
18
 
19
+ ## Changed
20
+ - File `grammar.rb` rules for if ... else were given names in order to activate semantic actions.
21
+ - File `README.md` updated with little `if ... else` documentation.
22
+
10
23
  ## [0.0.18] - 2021-01-13
11
24
  - The interpreter can evaluate `and`, `or`expressions.
12
25
 
data/README.md CHANGED
@@ -174,6 +174,7 @@ Loxxy supports the following statements:
174
174
  -[Comparison expressions](#comparison-expressions)
175
175
  -[Logical expressions](#logical-expressions)
176
176
  -[Grouping expressions](#grouping-expressions)
177
+ -[If Statement](#if-statement)
177
178
  -[Print Statement](#print-statement)
178
179
 
179
180
  #### Expressions
@@ -245,7 +246,7 @@ REMINDER: In __Lox__, `false` and `nil` are considered falsey, everything else i
245
246
  `!!true; // => true`
246
247
  `!0; // => false`
247
248
 
248
- ##### Grouping expressions
249
+ #### Grouping expressions
249
250
  Use parentheses `(` `)` for a better control in expression/operator precedence.
250
251
 
251
252
  ``` javascript
@@ -253,8 +254,43 @@ print 3 + 4 * 5; // => 23
253
254
  print (3 + 4) * 5; // => 35
254
255
  ```
255
256
 
257
+ #### If statement
256
258
 
257
- ##### Print Statement
259
+ Based on a given condition, an if statement executes one of two statements:
260
+ ``` javascript
261
+ if (condition) {
262
+ print "then-branch";
263
+ } else {
264
+ print "else-branch";
265
+ }
266
+ ```
267
+
268
+ As for other languages, the `else` part is optional.
269
+ ##### Warning: nested `if`...`else`
270
+ Call it a bug ... Nested `if` `else` control flow structure aren't yet supported by __Loxxy__.
271
+ The culprit has a name: [the dangling else](https://en.wikipedia.org/wiki/Dangling_else).
272
+
273
+ The problem in a nutshell: in a nested if ... else ... statement like this:
274
+ ``` javascript
275
+ 'if (true) if (false) print "bad"; else print "good";
276
+ ```
277
+ ... there is an ambiguity. Indeed, according to the __Lox__ grammar, the `else` could be bound
278
+ either to the first `if` or to the second one.
279
+ This ambiguity is usually lifted by applying an ad-hoc rule: an `else` is aways bound to the most
280
+ recent (rightmost) `if`.
281
+ Being a generic parsing library, `Rley` doesn't apply any of these supplemental rules.
282
+ As a consequence,it complains about the found ambiguity and stops the parsing...
283
+ Although `Rley` can cope with ambiguities, this requires the use of an advanced data structure
284
+ called `Shared Packed Parse Forest (SPPF)`.
285
+ SPPF are much more complex to handle than the `common` parse trees present in most compiler or interpreter books.
286
+ Therefore, a future version of `Rley` will incorporate the capability to define disambuiguation rules.
287
+
288
+ In the meantime, the `Loxxy` will progress on other __Lox__ features like:
289
+ - Variables,
290
+ - Block structures...
291
+
292
+
293
+ #### Print Statement
258
294
 
259
295
  The statement print + expression + ; prints the result of the expression to stdout.
260
296
 
@@ -7,3 +7,4 @@ require_relative 'lox_unary_expr'
7
7
  require_relative 'lox_binary_expr'
8
8
  require_relative 'lox_logical_expr'
9
9
  require_relative 'lox_print_stmt'
10
+ require_relative 'lox_if_stmt'
@@ -158,6 +158,14 @@ module Loxxy
158
158
  return_first_child(range, tokens, theChildren) # Discard the semicolon
159
159
  end
160
160
 
161
+ # rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
162
+ def reduce_if_stmt(_production, _range, tokens, theChildren)
163
+ condition = theChildren[1]
164
+ then_stmt = theChildren[2]
165
+ else_stmt = theChildren[3]
166
+ LoxIfStmt.new(tokens[0].position, condition, then_stmt, else_stmt)
167
+ end
168
+
161
169
  # rule('printStmt' => 'PRINT expression SEMICOLON')
162
170
  def reduce_print_stmt(_production, _range, tokens, theChildren)
163
171
  Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
@@ -51,6 +51,14 @@ module Loxxy
51
51
  broadcast(:after_ptree, aParseTree)
52
52
  end
53
53
 
54
+ # Visit event. The visitor is about to visit a if statement.
55
+ # @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
56
+ def visit_if_stmt(anIfStmt)
57
+ broadcast(:before_if_stmt, anIfStmt)
58
+ traverse_subnodes(anIfStmt) # The condition is visited/evaluated here...
59
+ broadcast(:after_if_stmt, anIfStmt, self)
60
+ end
61
+
54
62
  # Visit event. The visitor is about to visit a print statement.
55
63
  # @param aPrintStmt [AST::LOXPrintStmt] the print statement node to visit
56
64
  def visit_print_stmt(aPrintStmt)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxIfStmt < LoxCompoundExpr
8
+ # @return [LoxNode] code of then branch
9
+ attr_reader :then_stmt
10
+
11
+ # @return [LoxNode, NilClass] code of else branch
12
+ attr_reader :else_stmt
13
+
14
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
+ # @param subExpr [Loxxy::Ast::LoxNode]
16
+ def initialize(aPosition, condExpr, thenStmt, elseStmt)
17
+ super(aPosition, [condExpr])
18
+ @then_stmt = thenStmt
19
+ @else_stmt = elseStmt
20
+ end
21
+
22
+ # Part of the 'visitee' role in Visitor design pattern.
23
+ # @param visitor [Ast::ASTVisitor] the visitor
24
+ def accept(visitor)
25
+ visitor.visit_if_stmt(self)
26
+ end
27
+
28
+ # Accessor to the condition expression
29
+ # @return [LoxNode]
30
+ def condition
31
+ subnodes[0]
32
+ end
33
+ end # class
34
+ end # module
35
+ end # module
@@ -33,7 +33,18 @@ module Loxxy
33
33
  stack.empty? ? Datatype::Nil.instance : stack.pop
34
34
  end
35
35
 
36
+ ##########################################################################
36
37
  # Visit event handling
38
+ ##########################################################################
39
+ def after_if_stmt(anIfStmt, aVisitor)
40
+ # Retrieve the result of the condition evaluation
41
+ condition = stack.pop
42
+ if condition.truthy?
43
+ result = anIfStmt.then_stmt.accept(aVisitor)
44
+ elsif anIfStmt.else_stmt
45
+ anIfStmt.else_stmt.accept(aVisitor)
46
+ end
47
+ end
37
48
 
38
49
  def after_print_stmt(_printStmt)
39
50
  tos = stack.pop
@@ -68,9 +68,9 @@ module Loxxy
68
68
  rule('forTest' => 'expression_opt SEMICOLON')
69
69
  rule('forUpdate' => 'expression_opt')
70
70
 
71
- rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
72
- rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN')
73
- rule('elsePart_opt' => 'ELSE statement')
71
+ rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
72
+ rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
73
+ rule('elsePart_opt' => 'ELSE statement').as 'keep_symbol2'
74
74
  rule('elsePart_opt' => [])
75
75
 
76
76
  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.19'
4
+ VERSION = '0.0.20'
5
5
  end
@@ -223,6 +223,33 @@ module Loxxy
223
223
  end
224
224
  end
225
225
 
226
+ it 'should evaluate an if statement' do
227
+ [
228
+ # Evaluate the 'then' expression if the condition is true.
229
+ ['if (true) print "then-branch";', 'then-branch'],
230
+ ['if (false) print "ignored";', ''],
231
+ # TODO: test with then block body
232
+ # TODO: test with assignment in if condition
233
+
234
+ # Evaluate the 'else' expression if the condition is false.
235
+ ['if (true) print "then-branch"; else print "else-branch";', 'then-branch'],
236
+ ['if (false) print "then-branch"; else print "else-branch";', 'else-branch'],
237
+ ['if (0) print "then-branch"; else print "else-branch";', 'then-branch'],
238
+ ['if (nil) print "then-branch"; else print "else-branch";', 'else-branch'],
239
+ # TODO: test with else block body
240
+
241
+ # TODO: A dangling else binds to the right-most if.
242
+ # ['if (true) if (false) print "bad"; else print "good";', 'good'],
243
+ # ['if (false) if (true) print "bad"; else print "worse";', 'bad']
244
+ ].each do |(source, predicted)|
245
+ io = StringIO.new
246
+ cfg = { ostream: io }
247
+ lox = Loxxy::Interpreter.new(cfg)
248
+ result = lox.evaluate(source)
249
+ expect(io.string).to eq(predicted)
250
+ end
251
+ end
252
+
226
253
  it 'should print the hello world message' do
227
254
  expect { subject.evaluate(hello_world) }.not_to raise_error
228
255
  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.19
4
+ version: 0.0.20
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-14 00:00:00.000000000 Z
11
+ date: 2021-01-15 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_grouping_expr.rb
93
+ - lib/loxxy/ast/lox_if_stmt.rb
93
94
  - lib/loxxy/ast/lox_literal_expr.rb
94
95
  - lib/loxxy/ast/lox_logical_expr.rb
95
96
  - lib/loxxy/ast/lox_node.rb