loxxy 0.2.02 → 0.3.00

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/README.md +19 -2
  4. data/lib/loxxy/ast/all_lox_nodes.rb +0 -1
  5. data/lib/loxxy/ast/ast_builder.rb +27 -4
  6. data/lib/loxxy/ast/ast_visitee.rb +53 -0
  7. data/lib/loxxy/ast/ast_visitor.rb +2 -10
  8. data/lib/loxxy/ast/lox_assign_expr.rb +1 -5
  9. data/lib/loxxy/ast/lox_binary_expr.rb +1 -6
  10. data/lib/loxxy/ast/lox_block_stmt.rb +1 -5
  11. data/lib/loxxy/ast/lox_call_expr.rb +1 -5
  12. data/lib/loxxy/ast/lox_class_stmt.rb +1 -5
  13. data/lib/loxxy/ast/lox_fun_stmt.rb +1 -5
  14. data/lib/loxxy/ast/lox_get_expr.rb +1 -6
  15. data/lib/loxxy/ast/lox_grouping_expr.rb +1 -5
  16. data/lib/loxxy/ast/lox_if_stmt.rb +2 -6
  17. data/lib/loxxy/ast/lox_literal_expr.rb +1 -5
  18. data/lib/loxxy/ast/lox_logical_expr.rb +1 -6
  19. data/lib/loxxy/ast/lox_node.rb +9 -1
  20. data/lib/loxxy/ast/lox_print_stmt.rb +1 -5
  21. data/lib/loxxy/ast/lox_return_stmt.rb +1 -5
  22. data/lib/loxxy/ast/lox_seq_decl.rb +1 -5
  23. data/lib/loxxy/ast/lox_set_expr.rb +1 -5
  24. data/lib/loxxy/ast/lox_super_expr.rb +2 -6
  25. data/lib/loxxy/ast/lox_this_expr.rb +1 -5
  26. data/lib/loxxy/ast/lox_unary_expr.rb +1 -6
  27. data/lib/loxxy/ast/lox_var_stmt.rb +1 -5
  28. data/lib/loxxy/ast/lox_variable_expr.rb +1 -5
  29. data/lib/loxxy/ast/lox_while_stmt.rb +2 -6
  30. data/lib/loxxy/back_end/engine.rb +28 -26
  31. data/lib/loxxy/back_end/lox_instance.rb +1 -1
  32. data/lib/loxxy/back_end/resolver.rb +17 -21
  33. data/lib/loxxy/datatype/number.rb +19 -4
  34. data/lib/loxxy/front_end/parser.rb +1 -1
  35. data/lib/loxxy/front_end/scanner.rb +11 -10
  36. data/lib/loxxy/version.rb +1 -1
  37. data/spec/back_end/environment_spec.rb +0 -14
  38. data/spec/back_end/symbol_table_spec.rb +0 -19
  39. data/spec/back_end/variable_spec.rb +0 -35
  40. data/spec/front_end/scanner_spec.rb +35 -7
  41. data/spec/interpreter_spec.rb +36 -0
  42. metadata +3 -3
  43. data/lib/loxxy/ast/lox_for_stmt.rb +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afc822710c5023cdffd8eeb9e9316736f2b2d9ddb2d05e4c4e835dee9b9b4d8b
4
- data.tar.gz: 5debd7264833555cc34c64586de96d4e811eef4ebebe25f6c42a0832dee8e433
3
+ metadata.gz: 231fc227cc66ebd0b4f47a00f9fe7260146d70e64cea3581df6cfc5c71937bef
4
+ data.tar.gz: cd8117dc8ae9b631ad0d7bef34ee972bdcaa8c758045637bae2f3ea0ed25bb79
5
5
  SHA512:
6
- metadata.gz: d988772978387faa28692101e6b2dab580a93b6f5738ad9094faf22d8aed78aca68123982ff0a25d9b9029d4d27ef4f82788009b10942a14ce5dd9bd9f1e37a7
7
- data.tar.gz: 711b3188b0dd14a9821eee5227ee0d574c60144b7be88a3f722d10b4506556fa864685659c7493acfd6d63c2cf82f8c0c6c36cc0431a57a2d476efe3b4bf7efd
6
+ metadata.gz: ca714bbd5fa4fcbae67548cdae0d8c43236b02f30adc45e939c0e1dcd3e6694471ea429f902d5e1a7335d4a4cf9779d167a9387b0f4f0732325bd31237630e8d
7
+ data.tar.gz: '008526b6d9a2e9f5cf27a68f796e53aa5312d67523a53d8c8682cf944ee6cd990adf3928a3af7fb66e056bca4fc6e45fc11119ffcab1dda4d41fb2ca1c1207be'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,62 @@
1
+ ## [0.3.00] - 2021-05-07
2
+ - Milestone: `Loxxy` passes all reference test suite.
3
+
4
+ ### Fixed
5
+ - Method `BackEdn::Resolver#before_variable_expr`: Standard `Lox` allows re-declaration of a variable at top-level scope
6
+
7
+
8
+ ## [0.2.06] - 2021-05-04
9
+ - Nearly passing the 'official' test suite, fixing non-compliant behavior, specialized exceptions for errors
10
+
11
+ ### New
12
+ - Module `LoxFileTester` module that hosts methods that simplify the tests of `Lox` source file.
13
+
14
+ ### Changed
15
+ - Folder `test_suite` vastly reorganized. Sub-folder `baseline` contains spec files testing the `Lox` files from official implementation
16
+ - Class `BackEnd::Engine` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
17
+ - Class `BackEnd::Resolver` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
18
+ - Method `Datatype::Number#/` now handles correctly expression like `0/0` (integer divide)
19
+
20
+ ### Fixed
21
+ - `0/0` expression results in a ZeroDivisionError exception, in Lox this result to a NaN (Not a Number). Now, `Loxxy` is aligned to standard `Lox`
22
+ - `FrontEnd::Scanner` now always treats expression like `-123` as the unary or binary minus operator applied to a positive number.
23
+
24
+ ## [0.2.05] - 2021-04-26
25
+ - `Loxxy` now transforms for loops into while loops (desugaring), fix in Scanner class
26
+
27
+ ### Changed
28
+ - Method `Ast::ASTBuilder#reduce_for_stmt` converts 'for' loops into 'while' loops
29
+ - Method `Ast::ASTBuilder#reduce_for_control takes care of case for(expr1;;expr2) now test expression is set to true
30
+
31
+ ### Fixed
32
+ - Method `FrontEnd::Scanner#next_token` keyword recognition was case insensitive
33
+
34
+ ### Removed
35
+ - Method `Ast::Visitor#visitor_for_stmt`
36
+ - Method `BackEnd::Engine#after_for_stmt`
37
+ - Method `BackEnd::Resolver#before_for_stmt`
38
+ - Method `BackEnd::Resolver#after_for_stmt`
39
+
40
+
41
+ ## [0.2.04] - 2021-04-25
42
+ - `Loxxy` passes the test suite for `for` statements
43
+
44
+ ### Fixed
45
+ - Method `BackEnd::Engine#after_for_stmt` now a for(;;) executes the body once; a for without test will execute forever
46
+ - Method `BackEnd::Resolver#after_for_stmt now accepts nil test expression
47
+
48
+ ## [0.2.03] - 2021-04-24
49
+ - Fixes for the set (field) expressions, `accept` methods for AST nodes are meta-programmed
50
+
51
+ ### New
52
+ - Module `Ast::Visitee` provides the `define_accept` method that generate `accept` method with meta-programming
53
+
54
+ ### Fixed
55
+ - Method `BackEnd::Engine#before_set_expr` methos method that ensure that the receiver is evaluated first, then the assigned value
56
+ - Method `BackEnd::Engine#after_set_expr` now pushes the value assigned to the field also onto the stack
57
+ - Class `BackEnd::Engine` a number of StnadardError exceptions are replaced by Loxxy::RuntimeError
58
+
59
+
1
60
  ## [0.2.02] - 2021-04-21
2
61
  - Improvements in the scanner class (escape sequence for quotes and newlines), error messages closer to jlox.
3
62
 
data/README.md CHANGED
@@ -19,20 +19,37 @@ Although __Lox__ is fairly simple, it is far from being a toy language:
19
19
  ### Loxxy gem features
20
20
  - Complete tree-walking interpreter including lexer, parser and resolver
21
21
  - 100% pure Ruby with clean design (not a port from some other language)
22
+ - Passes the `jox` (THE reference `Lox` implementation) test suite
22
23
  - Minimal runtime dependency (Rley gem). Won't drag a bunch of gems...
23
24
  - Ruby API for integrating a Lox interpreter with your code.
24
25
  - A command-line interpreter `loxxy`
25
- - Open for your language extensions
26
+ - Open for your language extensions...
27
+
28
+ ### Why `Loxxy` ?
29
+ - If programming languages are one of your subject interest...
30
+ - ... and you wanted learn how to implement one in Ruby...
31
+ - ... then `Loxxy` can help to understand and experiment in this rewarding craft.
26
32
 
27
33
  ## How to start in 1, 2, 3...?
28
34
  ... in less than 3 minutes...
29
35
 
30
36
  ### 1. Installing
31
- Install __Loxxy__ as a gem:
37
+ __Loxxy__'s installation is pretty standard:
32
38
 
33
39
 
34
40
  $ gem install loxxy
35
41
 
42
+ Alternatively, you can install `loxxy` with Bundler.
43
+ Add this line to your application's Gemfile:
44
+
45
+ gem 'loxxy'
46
+
47
+ And then execute:
48
+
49
+ $ bundle
50
+
51
+
52
+
36
53
  ### 2. Your first `Lox` program
37
54
  Create a text file and enter the following lines:
38
55
  ```javascript
@@ -19,7 +19,6 @@ require_relative 'lox_while_stmt'
19
19
  require_relative 'lox_return_stmt'
20
20
  require_relative 'lox_print_stmt'
21
21
  require_relative 'lox_if_stmt'
22
- require_relative 'lox_for_stmt'
23
22
  require_relative 'lox_var_stmt'
24
23
  require_relative 'lox_class_stmt'
25
24
  require_relative 'lox_seq_decl'
@@ -225,16 +225,39 @@ module Loxxy
225
225
  end
226
226
 
227
227
  # rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
228
- def reduce_for_stmt(_production, _range, _tokens, theChildren)
229
- for_stmt = theChildren[2]
230
- for_stmt.body_stmt = theChildren[4]
228
+ def reduce_for_stmt(_production, _range, tokens, theChildren)
229
+ # Following 'Crafting Interpreters', we replace the for statement by a while loop
230
+ return theChildren[4] if theChildren[2].compact.empty? # for(;;) => execute body once
231
+
232
+ (init, test, update) = theChildren[2]
233
+ if update
234
+ new_body = LoxSeqDecl.new(tokens[0].position, [theChildren[4], update])
235
+ stmt = Ast::LoxBlockStmt.new(tokens[1].position, new_body)
236
+ else
237
+ stmt = theChildren[4]
238
+ end
239
+ while_stmt = Ast::LoxWhileStmt.new(tokens[0].position, test, stmt)
240
+
241
+ if init
242
+ block_body = LoxSeqDecl.new(tokens[0].position, [init, while_stmt])
243
+ for_stmt = Ast::LoxBlockStmt.new(tokens[1].position, block_body)
244
+ else
245
+ for_stmt = while_stmt
246
+ end
247
+
231
248
  for_stmt
232
249
  end
233
250
 
234
251
  # rule('forControl' => 'forInitialization forTest forUpdate')
235
252
  def reduce_for_control(_production, _range, tokens, theChildren)
236
253
  (init, test, update) = theChildren
237
- Ast::LoxForStmt.new(tokens[0].position, init, test, update)
254
+ if test.nil? && update
255
+ # when test expr is nil but update expr is not, then force test to be true
256
+ test = LoxLiteralExpr.new(tokens[0].position, Datatype::True.instance)
257
+ [init, test, update]
258
+ else
259
+ theChildren
260
+ end
238
261
  end
239
262
 
240
263
  # rule('forInitialization' => 'SEMICOLON')
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loxxy
4
+ module Ast
5
+ # Mix-in module that relies on meta-programming to add a method
6
+ # called `accept` to the host class (an AST node).
7
+ # That method fulfills the expected behavior of the `visitee` in
8
+ # the Visitor design pattern.
9
+ module ASTVisitee
10
+ # Convert the input string into a snake case string.
11
+ # Example: ClassExpr => class_expr
12
+ # @param aName [String] input name to convert
13
+ # @return [String] the name converted into snake case
14
+ def snake_case(aName)
15
+ converted = +''
16
+ down = false
17
+ aName.each_char do |ch|
18
+ lower = ch.downcase
19
+ if lower == ch
20
+ converted << ch
21
+ down = true
22
+ else
23
+ converted << '_' if down && converted[-1] != '_'
24
+ converted << lower
25
+ down = false
26
+ end
27
+ end
28
+
29
+ converted
30
+ end
31
+
32
+ # This method adds a method named `accept` that takes a visitor
33
+ # The visitor is expected to implement a method named:
34
+ # visit + class name (without the Lox prefix) in snake case
35
+ # Example: class name = LoxClassStmt => visit method = visit_class_stmt
36
+ def define_accept
37
+ return if instance_methods(false).include?(:accept)
38
+
39
+ base_name = name.split('::').last
40
+ name_suffix = snake_case(base_name).sub(/^lox/, '')
41
+ accept_body = <<-MTH_END
42
+ # Part of the 'visitee' role in Visitor design pattern.
43
+ # @param visitor [Ast::ASTVisitor] the visitor
44
+ def accept(aVisitor)
45
+ aVisitor.visit#{name_suffix}(self)
46
+ end
47
+ MTH_END
48
+
49
+ class_eval(accept_body)
50
+ end
51
+ end # module
52
+ end # module
53
+ end # module
@@ -75,14 +75,6 @@ module Loxxy
75
75
  broadcast(:after_class_stmt, aClassStmt, self)
76
76
  end
77
77
 
78
- # Visit event. The visitor is about to visit a for statement.
79
- # @param aForStmt [AST::LOXForStmt] the for statement node to visit
80
- def visit_for_stmt(aForStmt)
81
- broadcast(:before_for_stmt, aForStmt)
82
- traverse_subnodes(aForStmt) # The condition is visited/evaluated here...
83
- broadcast(:after_for_stmt, aForStmt, self)
84
- end
85
-
86
78
  # Visit event. The visitor is about to visit a if statement.
87
79
  # @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
88
80
  def visit_if_stmt(anIfStmt)
@@ -111,7 +103,7 @@ module Loxxy
111
103
  # @param aWhileStmt [AST::LOXWhileStmt] the while statement node to visit
112
104
  def visit_while_stmt(aWhileStmt)
113
105
  broadcast(:before_while_stmt, aWhileStmt)
114
- traverse_subnodes(aWhileStmt) # The condition is visited/evaluated here...
106
+ traverse_subnodes(aWhileStmt) if aWhileStmt.condition # The condition is visited/evaluated here...
115
107
  broadcast(:after_while_stmt, aWhileStmt, self)
116
108
  end
117
109
 
@@ -133,7 +125,7 @@ module Loxxy
133
125
 
134
126
  # @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
135
127
  def visit_set_expr(aSetExpr)
136
- broadcast(:before_set_expr, aSetExpr)
128
+ broadcast(:before_set_expr, aSetExpr, self)
137
129
  traverse_subnodes(aSetExpr)
138
130
  broadcast(:after_set_expr, aSetExpr, self)
139
131
  end
@@ -17,11 +17,7 @@ module Loxxy
17
17
  @name = aName
18
18
  end
19
19
 
20
- # Part of the 'visitee' role in Visitor design pattern.
21
- # @param visitor [Ast::ASTVisitor] the visitor
22
- def accept(visitor)
23
- visitor.visit_assign_expr(self)
24
- end
20
+ define_accept # Add `accept` method as found in Visitor design pattern
25
21
  end # class
26
22
  end # module
27
23
  end # module
@@ -16,12 +16,7 @@ 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
+ define_accept # Add `accept` method as found in Visitor design pattern
25
20
  alias operands subnodes
26
21
  end # class
27
22
  end # module
@@ -15,11 +15,7 @@ module Loxxy
15
15
  subnodes.size == 1 && subnodes[0].nil?
16
16
  end
17
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_block_stmt(self)
22
- end
18
+ define_accept # Add `accept` method as found in Visitor design pattern
23
19
  end # class
24
20
  end # module
25
21
  end # module
@@ -15,11 +15,7 @@ module Loxxy
15
15
  @arguments = argList
16
16
  end
17
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_call_expr(self)
22
- end
18
+ define_accept # Add `accept` method as found in Visitor design pattern
23
19
  end # class
24
20
  end # module
25
21
  end # module
@@ -24,11 +24,7 @@ module Loxxy
24
24
  @body = theMethods
25
25
  end
26
26
 
27
- # Part of the 'visitee' role in Visitor design pattern.
28
- # @param visitor [Ast::ASTVisitor] the visitor
29
- def accept(visitor)
30
- visitor.visit_class_stmt(self)
31
- end
27
+ define_accept # Add `accept` method as found in Visitor design pattern
32
28
  end # class
33
29
  end # module
34
30
  end # module
@@ -23,11 +23,7 @@ module Loxxy
23
23
  @is_method = false
24
24
  end
25
25
 
26
- # Part of the 'visitee' role in Visitor design pattern.
27
- # @param visitor [Ast::ASTVisitor] the visitor
28
- def accept(visitor)
29
- visitor.visit_fun_stmt(self)
30
- end
26
+ define_accept # Add `accept` method as found in Visitor design pattern
31
27
  end # class
32
28
  # rubocop: enable Style/AccessorGrouping
33
29
  end # module
@@ -18,12 +18,7 @@ module Loxxy
18
18
  @property = aPropertyName
19
19
  end
20
20
 
21
- # Part of the 'visitee' role in Visitor design pattern.
22
- # @param visitor [ASTVisitor] the visitor
23
- def accept(visitor)
24
- visitor.visit_get_expr(self)
25
- end
26
-
21
+ define_accept # Add `accept` method as found in Visitor design pattern
27
22
  alias callee= object=
28
23
  end # class
29
24
  end # module
@@ -11,11 +11,7 @@ module Loxxy
11
11
  super(aPosition, [subExpr])
12
12
  end
13
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
14
+ define_accept # Add `accept` method as found in Visitor design pattern
19
15
  end # class
20
16
  end # module
21
17
  end # module
@@ -21,17 +21,13 @@ module Loxxy
21
21
  @else_stmt = elseStmt
22
22
  end
23
23
 
24
- # Part of the 'visitee' role in Visitor design pattern.
25
- # @param visitor [Ast::ASTVisitor] the visitor
26
- def accept(visitor)
27
- visitor.visit_if_stmt(self)
28
- end
29
-
30
24
  # Accessor to the condition expression
31
25
  # @return [LoxNode]
32
26
  def condition
33
27
  subnodes[0]
34
28
  end
29
+
30
+ define_accept # Add `accept` method as found in Visitor design pattern
35
31
  end # class
36
32
  end # module
37
33
  end # module
@@ -15,11 +15,7 @@ module Loxxy
15
15
  @literal = aLiteral
16
16
  end
17
17
 
18
- # Part of the 'visitee' role in Visitor design pattern.
19
- # @param visitor [ASTVisitor] the visitor
20
- def accept(visitor)
21
- visitor.visit_literal_expr(self)
22
- end
18
+ define_accept # Add `accept` method as found in Visitor design pattern
23
19
  end # class
24
20
  end # module
25
21
  end # module
@@ -16,12 +16,7 @@ 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_logical_expr(self)
23
- end
24
-
19
+ define_accept # Add `accept` method as found in Visitor design pattern
25
20
  alias operands subnodes
26
21
  end # class
27
22
  end # module
@@ -1,8 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'ast_visitee'
4
+
3
5
  module Loxxy
4
6
  module Ast
7
+ # Abstract class.
8
+ # Instances of its subclasses represent nodes of an abstract syntax tree
9
+ # that is the product of the parse of an input text.
5
10
  class LoxNode
11
+ # Let nodes take `visitee` role as defined in the Visitor design pattern
12
+ extend ASTVisitee
13
+
6
14
  # return [Rley::Lexical::Position] Position of the entry in the input stream.
7
15
  attr_reader :position
8
16
 
@@ -16,7 +24,7 @@ module Loxxy
16
24
  # Default: do nothing ...
17
25
  end
18
26
 
19
- # Abstract method.
27
+ # Abstract method (must be overriden in subclasses).
20
28
  # Part of the 'visitee' role in Visitor design pattern.
21
29
  # @param _visitor [LoxxyTreeVisitor] the visitor
22
30
  def accept(_visitor)