loxxy 0.2.01 → 0.2.06

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -0
  3. data/README.md +25 -11
  4. data/bin/loxxy +9 -5
  5. data/lib/loxxy/ast/all_lox_nodes.rb +0 -1
  6. data/lib/loxxy/ast/ast_builder.rb +27 -4
  7. data/lib/loxxy/ast/ast_visitee.rb +53 -0
  8. data/lib/loxxy/ast/ast_visitor.rb +2 -10
  9. data/lib/loxxy/ast/lox_assign_expr.rb +1 -5
  10. data/lib/loxxy/ast/lox_binary_expr.rb +1 -6
  11. data/lib/loxxy/ast/lox_block_stmt.rb +1 -5
  12. data/lib/loxxy/ast/lox_call_expr.rb +1 -5
  13. data/lib/loxxy/ast/lox_class_stmt.rb +1 -5
  14. data/lib/loxxy/ast/lox_fun_stmt.rb +1 -5
  15. data/lib/loxxy/ast/lox_get_expr.rb +1 -6
  16. data/lib/loxxy/ast/lox_grouping_expr.rb +1 -5
  17. data/lib/loxxy/ast/lox_if_stmt.rb +2 -6
  18. data/lib/loxxy/ast/lox_literal_expr.rb +1 -5
  19. data/lib/loxxy/ast/lox_logical_expr.rb +1 -6
  20. data/lib/loxxy/ast/lox_node.rb +9 -1
  21. data/lib/loxxy/ast/lox_print_stmt.rb +1 -5
  22. data/lib/loxxy/ast/lox_return_stmt.rb +1 -5
  23. data/lib/loxxy/ast/lox_seq_decl.rb +1 -5
  24. data/lib/loxxy/ast/lox_set_expr.rb +1 -5
  25. data/lib/loxxy/ast/lox_super_expr.rb +2 -6
  26. data/lib/loxxy/ast/lox_this_expr.rb +1 -5
  27. data/lib/loxxy/ast/lox_unary_expr.rb +1 -6
  28. data/lib/loxxy/ast/lox_var_stmt.rb +1 -5
  29. data/lib/loxxy/ast/lox_variable_expr.rb +1 -5
  30. data/lib/loxxy/ast/lox_while_stmt.rb +2 -6
  31. data/lib/loxxy/back_end/engine.rb +28 -26
  32. data/lib/loxxy/back_end/lox_instance.rb +1 -1
  33. data/lib/loxxy/back_end/resolver.rb +16 -22
  34. data/lib/loxxy/datatype/number.rb +19 -4
  35. data/lib/loxxy/error.rb +3 -0
  36. data/lib/loxxy/front_end/parser.rb +1 -1
  37. data/lib/loxxy/front_end/scanner.rb +43 -17
  38. data/lib/loxxy/version.rb +1 -1
  39. data/spec/front_end/scanner_spec.rb +69 -7
  40. data/spec/interpreter_spec.rb +36 -0
  41. metadata +3 -3
  42. 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: 3137f0d16bb2c3dbbc4e2036abbf45888ba06b863fef69a8682898ffdb2a146b
4
- data.tar.gz: 344ff5f99e7d64dd45074aaacde5622ecb676d8d3112773555b0fbcffbd5aa89
3
+ metadata.gz: cbb2f24e245891baabf20458e5b663784ba7f82ff902ab7892007d9b53d99347
4
+ data.tar.gz: eab4ede6f48470449f731e3074b2ac2fe72b53449c9471231ea64d0007a873ae
5
5
  SHA512:
6
- metadata.gz: f3cd447081d53619afeb040d9eade7fab0c0d5db79b8407fba412df727fb72849e5671679d7fd13fded703b9009399febfac6506ef91ac0bdd3220ef26472408
7
- data.tar.gz: 4a83b84a9853787369d51f48d7c2eacfe8a7e58b4ab8cb5d5322b02e0a5de68c4481effa61587db70f066902d21c7b2a310509de74d13f2cf1476a490e2728a7
6
+ metadata.gz: 8037d3c239e39d47554c4e7f7698e5908cdaa5e460c8972fe8dc5b58ee5fd59647c413db95250ff739c08f9ca891c8f576eb8d78aa63cf40039e8d1b3328e9a2
7
+ data.tar.gz: 795face50a76140ba2f6be2e6e6c44e671992baaea2ecade980acf1baed173dc59cbe7cd2ce419b509e8593068ffc3e637e0678dab2db72a96250d4e53ef0338
data/CHANGELOG.md CHANGED
@@ -1,3 +1,66 @@
1
+ ## [0.2.06] - 2021-05-04
2
+ - Nearly passing the 'official' test suite, fixing non-compliant behavior, specialized exceptions for errors
3
+
4
+ ### New
5
+ - Module `LoxFileTester` module that hosts methods that simplify the tests of `Lox` source file.
6
+
7
+ ### Changed
8
+ - Folder `test_suite` vastly reorganized. Sub-folder `baseline` contains spec files testing the `Lox` files from official implementation
9
+ - Class `BackEnd::Engine` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
10
+ - Class `BackEnd::Resolver` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
11
+ - Method `Datatype::Number#/` now handles correctly expression like `0/0` (integer divide)
12
+
13
+ ### Fixed
14
+ - `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`
15
+ - `FrontEnd::Scanner` now always treats expression like `-123` as the unary or binary minus operator applied to a positive number.
16
+
17
+ ## [0.2.05] - 2021-04-26
18
+ - `Loxxy` now transforms for loops into while loops (desugaring), fix in Scanner class
19
+
20
+ ### Changed
21
+ - Method `Ast::ASTBuilder#reduce_for_stmt` converts 'for' loops into 'while' loops
22
+ - Method `Ast::ASTBuilder#reduce_for_control takes care of case for(expr1;;expr2) now test expression is set to true
23
+
24
+ ### Fixed
25
+ - Method `FrontEnd::Scanner#next_token` keyword recognition was case insensitive
26
+
27
+ ### Removed
28
+ - Method `Ast::Visitor#visitor_for_stmt`
29
+ - Method `BackEnd::Engine#after_for_stmt`
30
+ - Method `BackEnd::Resolver#before_for_stmt`
31
+ - Method `BackEnd::Resolver#after_for_stmt`
32
+
33
+
34
+ ## [0.2.04] - 2021-04-25
35
+ - `Loxxy` passes the test suite for `for` statements
36
+
37
+ ### Fixed
38
+ - Method `BackEnd::Engine#after_for_stmt` now a for(;;) executes the body once; a for without test will execute forever
39
+ - Method `BackEnd::Resolver#after_for_stmt now accepts nil test expression
40
+
41
+ ## [0.2.03] - 2021-04-24
42
+ - Fixes for the set (field) expressions, `accept` methods for AST nodes are meta-programmed
43
+
44
+ ### New
45
+ - Module `Ast::Visitee` provides the `define_accept` method that generate `accept` method with meta-programming
46
+
47
+ ### Fixed
48
+ - Method `BackEnd::Engine#before_set_expr` methos method that ensure that the receiver is evaluated first, then the assigned value
49
+ - Method `BackEnd::Engine#after_set_expr` now pushes the value assigned to the field also onto the stack
50
+ - Class `BackEnd::Engine` a number of StnadardError exceptions are replaced by Loxxy::RuntimeError
51
+
52
+
53
+ ## [0.2.02] - 2021-04-21
54
+ - Improvements in the scanner class (escape sequence for quotes and newlines), error messages closer to jlox.
55
+
56
+ ### Changed
57
+ - File `loxxy` executable doesn't show a stack trace for scanner errors
58
+ - Class `ScannerError` is now a subclass of `Loxxy::Error`
59
+ - Class `Scanner` now returns a specific error message for unterminated strings
60
+ - Class `Scanner` error messages are closer to the ones from jlox
61
+ - Class `Scanner` supports now escape sequences \" for double quotes, \n for newlines
62
+ - File `README.md` Reshuffled some text
63
+
1
64
  ## [0.2.01] - 2021-04-18
2
65
  - Minor improvements in CLI, starting re-documenting `README.md`.
3
66
 
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Build status](https://ci.appveyor.com/api/projects/status/8e5p7dgjanm0qjkp?svg=true)](https://ci.appveyor.com/project/famished-tiger/loxxy)
4
4
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://github.com/famished-tiger/loxxy/blob/main/LICENSE.txt)
5
5
 
6
- ### What is loxxy?
6
+ ## What is loxxy?
7
7
  A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html ),
8
8
  a simple language defined in Bob Nystrom's excellent online book [Crafting Interpreters](https://craftinginterpreters.com/ ).
9
9
 
@@ -16,15 +16,34 @@ Although __Lox__ is fairly simple, it is far from being a toy language:
16
16
  - Functions and closures
17
17
  - Object-orientation (classes, methods, inheritance).
18
18
 
19
+ ### Loxxy gem features
20
+ - Complete tree-walking interpreter including lexer, parser and resolver
21
+ - 100% pure Ruby with clean design (not a port from some other language)
22
+ - Minimal runtime dependency (Rley gem). Won't drag a bunch of gems...
23
+ - Ruby API for integrating a Lox interpreter with your code.
24
+ - A command-line interpreter `loxxy`
25
+ - Open for your language extensions...
26
+
19
27
  ## How to start in 1, 2, 3...?
20
28
  ... in less than 3 minutes...
21
29
 
22
30
  ### 1. Installing
23
- Install __Loxxy__ as a gem:
31
+ __Loxxy__'s installation is pretty standard:
24
32
 
25
33
 
26
34
  $ gem install loxxy
27
35
 
36
+ Alternatively, you can install `loxxy` with Bundler.
37
+ Add this line to your application's Gemfile:
38
+
39
+ gem 'loxxy'
40
+
41
+ And then execute:
42
+
43
+ $ bundle
44
+
45
+
46
+
28
47
  ### 2. Your first `Lox` program
29
48
  Create a text file and enter the following lines:
30
49
  ```javascript
@@ -42,10 +61,11 @@ Lo and behold! The output device displays the famous greeting:
42
61
  Hello, world.
43
62
 
44
63
 
45
- Congrats! You ran your first `Lox` program with __Loxxy__.
64
+ Congrats! You ran your first `Lox` program thanks __Loxxy__ gem.
65
+ For a less superficial encounter with the language jump to the next section.
46
66
 
47
- ### Something beefier?...
48
- Let's admit it, the hello world example was unimpressive.
67
+ ## So you want something beefier?...
68
+ Let's admit it, the hello world example was unimpressive.
49
69
  To a get a taste of `Lox` object-oriented capabilities, let's try another `Hello world` variant:
50
70
 
51
71
  ```javascript
@@ -172,12 +192,6 @@ Indeed, an open-source language that misses some features is an invitation for t
172
192
  There are already a number of programming languages derived from `Lox`...
173
193
 
174
194
  ## Why `Loxxy`? What's in it for me?...
175
- Features:
176
- - Complete tree-walking interpreter including lexer, parser and resolver
177
- - 100% pure Ruby with clean design (not a port from some other language)
178
- - Ruby API for integrating a Lox interpreter with your code.
179
- - Minimal runtime dependency (Rley gem). Won't drag a bunch of gems...
180
-
181
195
 
182
196
  ### Purpose of this project:
183
197
  - To deliver an open source example of a programming language fully implemented in Ruby
data/bin/loxxy CHANGED
@@ -17,11 +17,15 @@ class LoxxyRunner
17
17
  return if file_names.nil? || file_names.empty?
18
18
 
19
19
  lox = Loxxy::Interpreter.new
20
- file_names.each do |lox_file|
21
- fname = validate_filename(lox_file)
22
- next unless file_exist?(fname)
23
-
24
- File.open(fname, 'r') { |f| lox.evaluate(f.read) }
20
+ begin
21
+ file_names.each do |lox_file|
22
+ fname = validate_filename(lox_file)
23
+ next unless file_exist?(fname)
24
+
25
+ File.open(fname, 'r') { |f| lox.evaluate(f.read) }
26
+ end
27
+ rescue Loxxy::ScanError => e
28
+ $stderr.puts e.message
25
29
  end
26
30
  end
27
31
 
@@ -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