loxxy 0.2.01 → 0.2.06

Sign up to get free protection for your applications and to get access to all the features.
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