loxxy 0.1.17 → 0.2.04
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 +72 -2
- data/README.md +188 -90
- data/bin/loxxy +54 -6
- data/lib/loxxy.rb +1 -0
- data/lib/loxxy/ast/all_lox_nodes.rb +1 -0
- data/lib/loxxy/ast/ast_builder.rb +20 -1
- data/lib/loxxy/ast/ast_visitee.rb +53 -0
- data/lib/loxxy/ast/ast_visitor.rb +8 -1
- 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 +6 -6
- data/lib/loxxy/ast/lox_for_stmt.rb +2 -6
- 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 +31 -0
- 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 +57 -10
- data/lib/loxxy/back_end/lox_class.rb +13 -2
- data/lib/loxxy/back_end/lox_instance.rb +1 -1
- data/lib/loxxy/back_end/resolver.rb +31 -1
- data/lib/loxxy/cli_parser.rb +68 -0
- data/lib/loxxy/error.rb +3 -0
- data/lib/loxxy/front_end/grammar.rb +2 -2
- data/lib/loxxy/front_end/parser.rb +1 -1
- data/lib/loxxy/front_end/scanner.rb +32 -7
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +6 -2
- data/spec/front_end/scanner_spec.rb +34 -0
- data/spec/interpreter_spec.rb +51 -0
- metadata +10 -4
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
@@ -165,7 +165,14 @@ module Loxxy
|
|
165
165
|
|
166
166
|
# rule('classDecl' => 'CLASS classNaming class_body')
|
167
167
|
def reduce_class_decl(_production, _range, _tokens, theChildren)
|
168
|
-
|
168
|
+
if theChildren[1].kind_of?(Array)
|
169
|
+
name = theChildren[1].first
|
170
|
+
parent = theChildren[1].last
|
171
|
+
else
|
172
|
+
name = theChildren[1]
|
173
|
+
parent = nil
|
174
|
+
end
|
175
|
+
Ast::LoxClassStmt.new(tokens[1].position, name, parent, theChildren[2])
|
169
176
|
end
|
170
177
|
|
171
178
|
# rule('classNaming' => 'IDENTIFIER')
|
@@ -173,6 +180,13 @@ module Loxxy
|
|
173
180
|
theChildren[0].token.lexeme
|
174
181
|
end
|
175
182
|
|
183
|
+
# rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
|
184
|
+
def reduce_class_subclassing(_production, _range, _tokens, theChildren)
|
185
|
+
super_token = theChildren[2].token
|
186
|
+
super_var = LoxVariableExpr.new(super_token.position, super_token.lexeme)
|
187
|
+
[theChildren[0].token.lexeme, super_var]
|
188
|
+
end
|
189
|
+
|
176
190
|
# rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE')
|
177
191
|
def reduce_class_body(_production, _range, _tokens, theChildren)
|
178
192
|
theChildren[1].nil? ? [] : theChildren[1]
|
@@ -368,6 +382,11 @@ module Loxxy
|
|
368
382
|
LoxThisExpr.new(tokens[0].position)
|
369
383
|
end
|
370
384
|
|
385
|
+
# rule('primary' => 'SUPER DOT IDENTIFIER')
|
386
|
+
def reduce_super_expr(_production, _range, _tokens, theChildren)
|
387
|
+
LoxSuperExpr.new(theChildren[0].token.position, theChildren[2].token.lexeme)
|
388
|
+
end
|
389
|
+
|
371
390
|
# rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
|
372
391
|
def reduce_function(_production, _range, _tokens, theChildren)
|
373
392
|
first_child = theChildren.first
|
@@ -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
|
@@ -133,7 +133,7 @@ module Loxxy
|
|
133
133
|
|
134
134
|
# @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
|
135
135
|
def visit_set_expr(aSetExpr)
|
136
|
-
broadcast(:before_set_expr, aSetExpr)
|
136
|
+
broadcast(:before_set_expr, aSetExpr, self)
|
137
137
|
traverse_subnodes(aSetExpr)
|
138
138
|
broadcast(:after_set_expr, aSetExpr, self)
|
139
139
|
end
|
@@ -212,6 +212,13 @@ module Loxxy
|
|
212
212
|
broadcast(:after_this_expr, aThisExpr, self)
|
213
213
|
end
|
214
214
|
|
215
|
+
# Visit event. The visitor is about to visit the super keyword.
|
216
|
+
# @param aSuperExpr [Ast::LoxSuperExpr] super expression
|
217
|
+
def visit_super_expr(aSuperExpr)
|
218
|
+
broadcast(:before_super_expr, aSuperExpr)
|
219
|
+
broadcast(:after_super_expr, aSuperExpr, self)
|
220
|
+
end
|
221
|
+
|
215
222
|
# Visit event. The visitor is about to visit the given terminal datatype value.
|
216
223
|
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
217
224
|
def visit_builtin(aValue)
|
@@ -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
|
@@ -8,23 +8,23 @@ module Loxxy
|
|
8
8
|
# @return [String] the class name
|
9
9
|
attr_reader :name
|
10
10
|
|
11
|
+
# @return [Ast::LoxVariableExpr] variable referencing the superclass (if any)
|
12
|
+
attr_reader :superclass
|
13
|
+
|
11
14
|
# @return [Array<Ast::LoxFunStmt>] the methods
|
12
15
|
attr_reader :body
|
13
16
|
|
14
17
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
18
|
# @param condExpr [Loxxy::Ast::LoxNode] iteration condition
|
16
19
|
# @param theBody [Array<Loxxy::Ast::LoxNode>]
|
17
|
-
def initialize(aPosition, aName, theMethods)
|
20
|
+
def initialize(aPosition, aName, aSuperclassName, theMethods)
|
18
21
|
super(aPosition, [])
|
19
22
|
@name = aName.dup
|
23
|
+
@superclass = aSuperclassName
|
20
24
|
@body = theMethods
|
21
25
|
end
|
22
26
|
|
23
|
-
#
|
24
|
-
# @param visitor [Ast::ASTVisitor] the visitor
|
25
|
-
def accept(visitor)
|
26
|
-
visitor.visit_class_stmt(self)
|
27
|
-
end
|
27
|
+
define_accept # Add `accept` method as found in Visitor design pattern
|
28
28
|
end # class
|
29
29
|
end # module
|
30
30
|
end # module
|
@@ -25,17 +25,13 @@ module Loxxy
|
|
25
25
|
@update_expr = updateExpr
|
26
26
|
end
|
27
27
|
|
28
|
-
# Part of the 'visitee' role in Visitor design pattern.
|
29
|
-
# @param visitor [Ast::ASTVisitor] the visitor
|
30
|
-
def accept(visitor)
|
31
|
-
visitor.visit_for_stmt(self)
|
32
|
-
end
|
33
|
-
|
34
28
|
# Accessor to the condition expression
|
35
29
|
# @return [LoxNode]
|
36
30
|
def condition
|
37
31
|
subnodes[0]
|
38
32
|
end
|
33
|
+
|
34
|
+
define_accept # Add `accept` method as found in Visitor design pattern
|
39
35
|
end # class
|
40
36
|
end # module
|
41
37
|
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
|