loxxy 0.2.00 → 0.2.05

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +47 -11
  3. data/CHANGELOG.md +81 -0
  4. data/README.md +188 -90
  5. data/bin/loxxy +54 -6
  6. data/lib/loxxy.rb +1 -0
  7. data/lib/loxxy/ast/all_lox_nodes.rb +0 -1
  8. data/lib/loxxy/ast/ast_builder.rb +27 -4
  9. data/lib/loxxy/ast/ast_visitee.rb +53 -0
  10. data/lib/loxxy/ast/ast_visitor.rb +2 -10
  11. data/lib/loxxy/ast/lox_assign_expr.rb +1 -5
  12. data/lib/loxxy/ast/lox_binary_expr.rb +1 -6
  13. data/lib/loxxy/ast/lox_block_stmt.rb +1 -5
  14. data/lib/loxxy/ast/lox_call_expr.rb +1 -5
  15. data/lib/loxxy/ast/lox_class_stmt.rb +1 -5
  16. data/lib/loxxy/ast/lox_fun_stmt.rb +1 -5
  17. data/lib/loxxy/ast/lox_get_expr.rb +1 -6
  18. data/lib/loxxy/ast/lox_grouping_expr.rb +1 -5
  19. data/lib/loxxy/ast/lox_if_stmt.rb +2 -6
  20. data/lib/loxxy/ast/lox_literal_expr.rb +1 -5
  21. data/lib/loxxy/ast/lox_logical_expr.rb +1 -6
  22. data/lib/loxxy/ast/lox_node.rb +9 -1
  23. data/lib/loxxy/ast/lox_print_stmt.rb +1 -5
  24. data/lib/loxxy/ast/lox_return_stmt.rb +1 -5
  25. data/lib/loxxy/ast/lox_seq_decl.rb +1 -5
  26. data/lib/loxxy/ast/lox_set_expr.rb +1 -5
  27. data/lib/loxxy/ast/lox_super_expr.rb +2 -6
  28. data/lib/loxxy/ast/lox_this_expr.rb +1 -5
  29. data/lib/loxxy/ast/lox_unary_expr.rb +1 -6
  30. data/lib/loxxy/ast/lox_var_stmt.rb +1 -5
  31. data/lib/loxxy/ast/lox_variable_expr.rb +1 -5
  32. data/lib/loxxy/ast/lox_while_stmt.rb +2 -6
  33. data/lib/loxxy/back_end/engine.rb +13 -22
  34. data/lib/loxxy/back_end/lox_instance.rb +1 -1
  35. data/lib/loxxy/back_end/resolver.rb +1 -12
  36. data/lib/loxxy/cli_parser.rb +68 -0
  37. data/lib/loxxy/error.rb +3 -0
  38. data/lib/loxxy/front_end/parser.rb +1 -1
  39. data/lib/loxxy/front_end/scanner.rb +37 -12
  40. data/lib/loxxy/version.rb +1 -1
  41. data/loxxy.gemspec +5 -1
  42. data/spec/front_end/scanner_spec.rb +41 -0
  43. data/spec/interpreter_spec.rb +23 -0
  44. metadata +8 -4
  45. data/lib/loxxy/ast/lox_for_stmt.rb +0 -41
data/bin/loxxy CHANGED
@@ -3,9 +3,57 @@
3
3
 
4
4
  require 'loxxy'
5
5
 
6
- if ARGV[0]
7
- lox = Loxxy::Interpreter.new
8
- File.open(ARGV[0], 'r') do |f|
9
- lox.evaluate(f.read)
10
- end
11
- end
6
+ class LoxxyRunner
7
+ DefaultLoxExtension = 'lox'
8
+ attr_reader(:cli_options)
9
+
10
+ def initialize(prog_name, args)
11
+ my_version = Loxxy::VERSION
12
+ cli = Loxxy::CLIParser.new(prog_name, my_version)
13
+ @cli_options = cli.parse!(args)
14
+ end
15
+
16
+ def run!(file_names)
17
+ return if file_names.nil? || file_names.empty?
18
+
19
+ lox = Loxxy::Interpreter.new
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
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def validate_filename(raw_fname)
35
+ # When necessary add extension to file name
36
+ fname = raw_fname.dup
37
+ basename = File.basename(fname)
38
+ has_extension = basename =~ /(?<=[^.])\.[^.]+$/
39
+ fname << '.' << DefaultLoxExtension unless has_extension
40
+
41
+ fname
42
+ end
43
+
44
+ def file_exist?(fname)
45
+ exists = File.exist?(fname)
46
+ $stderr.puts "No such file '#{fname}'" unless exists
47
+
48
+ exists
49
+ end
50
+ end # class
51
+
52
+ ########################################
53
+ # ENTRY POINT
54
+ ########################################
55
+ program = LoxxyRunner.new(File.basename(__FILE__), ARGV)
56
+
57
+ # All options from CLI gobbled from ARGV, remains only file name
58
+ program.run!(ARGV)
59
+ # End of file
data/lib/loxxy.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'loxxy/version'
4
+ require_relative 'loxxy/cli_parser'
4
5
  require_relative 'loxxy/interpreter'
5
6
  require_relative 'loxxy/front_end/raw_parser'
6
7
 
@@ -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)
@@ -11,11 +11,7 @@ module Loxxy
11
11
  super(aPosition, [anExpression])
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_print_stmt(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
@@ -12,11 +12,7 @@ module Loxxy
12
12
  super(aPosition, [expr])
13
13
  end
14
14
 
15
- # Part of the 'visitee' role in Visitor design pattern.
16
- # @param visitor [Ast::ASTVisitor] the visitor
17
- def accept(visitor)
18
- visitor.visit_return_stmt(self)
19
- end
15
+ define_accept # Add `accept` method as found in Visitor design pattern
20
16
  end # class
21
17
  end # module
22
18
  end # module
@@ -5,11 +5,7 @@ require_relative 'lox_compound_expr'
5
5
  module Loxxy
6
6
  module Ast
7
7
  class LoxSeqDecl < LoxCompoundExpr
8
- # Part of the 'visitee' role in Visitor design pattern.
9
- # @param visitor [Ast::ASTVisitor] the visitor
10
- def accept(visitor)
11
- visitor.visit_seq_decl(self)
12
- end
8
+ define_accept # Add `accept` method as found in Visitor design pattern
13
9
  end # class
14
10
  end # module
15
11
  end # module