loxxy 0.2.00 → 0.2.05
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +47 -11
- data/CHANGELOG.md +81 -0
- data/README.md +188 -90
- data/bin/loxxy +54 -6
- data/lib/loxxy.rb +1 -0
- data/lib/loxxy/ast/all_lox_nodes.rb +0 -1
- data/lib/loxxy/ast/ast_builder.rb +27 -4
- data/lib/loxxy/ast/ast_visitee.rb +53 -0
- data/lib/loxxy/ast/ast_visitor.rb +2 -10
- data/lib/loxxy/ast/lox_assign_expr.rb +1 -5
- data/lib/loxxy/ast/lox_binary_expr.rb +1 -6
- data/lib/loxxy/ast/lox_block_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_call_expr.rb +1 -5
- data/lib/loxxy/ast/lox_class_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_fun_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_get_expr.rb +1 -6
- data/lib/loxxy/ast/lox_grouping_expr.rb +1 -5
- data/lib/loxxy/ast/lox_if_stmt.rb +2 -6
- data/lib/loxxy/ast/lox_literal_expr.rb +1 -5
- data/lib/loxxy/ast/lox_logical_expr.rb +1 -6
- data/lib/loxxy/ast/lox_node.rb +9 -1
- data/lib/loxxy/ast/lox_print_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_return_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_seq_decl.rb +1 -5
- data/lib/loxxy/ast/lox_set_expr.rb +1 -5
- data/lib/loxxy/ast/lox_super_expr.rb +2 -6
- data/lib/loxxy/ast/lox_this_expr.rb +1 -5
- data/lib/loxxy/ast/lox_unary_expr.rb +1 -6
- data/lib/loxxy/ast/lox_var_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_variable_expr.rb +1 -5
- data/lib/loxxy/ast/lox_while_stmt.rb +2 -6
- data/lib/loxxy/back_end/engine.rb +13 -22
- data/lib/loxxy/back_end/lox_instance.rb +1 -1
- data/lib/loxxy/back_end/resolver.rb +1 -12
- data/lib/loxxy/cli_parser.rb +68 -0
- data/lib/loxxy/error.rb +3 -0
- data/lib/loxxy/front_end/parser.rb +1 -1
- data/lib/loxxy/front_end/scanner.rb +37 -12
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +5 -1
- data/spec/front_end/scanner_spec.rb +41 -0
- data/spec/interpreter_spec.rb +23 -0
- metadata +8 -4
- 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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
@@ -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,
|
229
|
-
|
230
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
data/lib/loxxy/ast/lox_node.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|