loxxy 0.1.17 → 0.2.00
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +1 -0
- data/lib/loxxy/ast/ast_builder.rb +20 -1
- data/lib/loxxy/ast/ast_visitor.rb +7 -0
- data/lib/loxxy/ast/lox_class_stmt.rb +5 -1
- data/lib/loxxy/ast/lox_super_expr.rb +35 -0
- data/lib/loxxy/back_end/engine.rb +35 -1
- data/lib/loxxy/back_end/lox_class.rb +13 -2
- data/lib/loxxy/back_end/resolver.rb +30 -0
- data/lib/loxxy/front_end/grammar.rb +2 -2
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +2 -2
- data/spec/interpreter_spec.rb +28 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a071e03ebadb94c255a118d3bebd75227a2d36c535495a79ad2525e4fc2bf7c
|
|
4
|
+
data.tar.gz: 35aa1fc822287f7b86628acac39e5c3ca1c12c478cbb330d302705597ff2f13b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 51b1a4622ee11c20582fa9ffa72f499a64b280c0092b82a8f07d922309c800f787409e8c273a5316d09de594d82026af0d979e2ad7f52ee6d3eb02233549ebf5
|
|
7
|
+
data.tar.gz: 2649d33c23f73351f9ecb3b9b4b9899541398c94fa4effa8ca4f4c766589df575b1f68685a039f595ca1efb83a515d0a3620afa3721a0604e187b321449b9716
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
## [0.1.17] - 2021-04-11
|
|
2
|
-
-
|
|
2
|
+
- `Loxxy` now support custom initializer.
|
|
3
|
+
|
|
4
|
+
### Changed
|
|
5
|
+
- Method `BackEnd::Class#call` updated for custom initializer.
|
|
6
|
+
- Class `BackEnd::LoxFunction` added an attribute `is_initializer`
|
|
7
|
+
- Class `BackEnd::Resolver#before_return_stmt` added a check that return in initializer may not return a value
|
|
3
8
|
|
|
4
9
|
### Fixed
|
|
5
|
-
-
|
|
10
|
+
- Method `BackEnd::Engine#after_call_expr` now does arity checking also for initalizer.
|
|
11
|
+
- Method `BackEnd::LoxInstance#set` removed the check of field existence that prevented the creation of ... fields
|
|
6
12
|
|
|
7
13
|
## [0.1.16] - 2021-04-10
|
|
8
14
|
- Fixed an issue in name lookup. All the `this` test suite is passing.
|
|
@@ -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
|
|
@@ -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)
|
|
@@ -8,15 +8,19 @@ 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
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lox_node'
|
|
4
|
+
|
|
5
|
+
module Loxxy
|
|
6
|
+
module Ast
|
|
7
|
+
class LoxSuperExpr < LoxNode
|
|
8
|
+
# @return [Ast::LoxNode] the object to which the property belongs to
|
|
9
|
+
attr_accessor :object
|
|
10
|
+
|
|
11
|
+
# @return [String] Name of a method name
|
|
12
|
+
attr_reader :property
|
|
13
|
+
|
|
14
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
|
15
|
+
# @param aMethodName [String] Name of a method
|
|
16
|
+
def initialize(aPosition, aMethodName)
|
|
17
|
+
super(aPosition)
|
|
18
|
+
@property = aMethodName
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
|
22
|
+
# @param visitor [ASTVisitor] the visitor
|
|
23
|
+
def accept(visitor)
|
|
24
|
+
visitor.visit_super_expr(self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Quack like a LoxVariableExpr
|
|
28
|
+
# @return [String] the `super` keyword
|
|
29
|
+
def name
|
|
30
|
+
'super'
|
|
31
|
+
end
|
|
32
|
+
alias callee= object=
|
|
33
|
+
end # class
|
|
34
|
+
end # module
|
|
35
|
+
end # module
|
|
@@ -70,6 +70,21 @@ module Loxxy
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def after_class_stmt(aClassStmt, aVisitor)
|
|
73
|
+
if aClassStmt.superclass
|
|
74
|
+
aClassStmt.superclass.accept(aVisitor)
|
|
75
|
+
parent = stack.pop
|
|
76
|
+
unless parent.kind_of?(LoxClass)
|
|
77
|
+
raise StandardError, 'Superclass must be a class.'
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
parent = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if parent # Create an environment specific for 'super'
|
|
84
|
+
super_env = Environment.new(symbol_table.current_env)
|
|
85
|
+
symbol_table.enter_environment(super_env)
|
|
86
|
+
end
|
|
87
|
+
|
|
73
88
|
# Convert LoxFunStmt into LoxFunction
|
|
74
89
|
meths = aClassStmt.body.map do |func_node|
|
|
75
90
|
func_node.is_method = true
|
|
@@ -79,7 +94,12 @@ module Loxxy
|
|
|
79
94
|
mth
|
|
80
95
|
end
|
|
81
96
|
|
|
82
|
-
klass = LoxClass.new(aClassStmt.name, meths, self)
|
|
97
|
+
klass = LoxClass.new(aClassStmt.name, parent, meths, self)
|
|
98
|
+
if parent
|
|
99
|
+
super_var = Variable.new('super', klass)
|
|
100
|
+
symbol_table.insert(super_var)
|
|
101
|
+
symbol_table.leave_environment
|
|
102
|
+
end
|
|
83
103
|
new_var = Variable.new(aClassStmt.name, klass)
|
|
84
104
|
symbol_table.insert(new_var)
|
|
85
105
|
end
|
|
@@ -280,6 +300,20 @@ module Loxxy
|
|
|
280
300
|
var.value.accept(aVisitor) # Evaluate this value then push on stack
|
|
281
301
|
end
|
|
282
302
|
|
|
303
|
+
def after_super_expr(aSuperExpr, aVisitor)
|
|
304
|
+
offset = resolver.locals[aSuperExpr]
|
|
305
|
+
env = symbol_table.current_env
|
|
306
|
+
(offset - 1).times { env = env.enclosing }
|
|
307
|
+
instance = env.defns['this'].value.accept(aVisitor)[0]
|
|
308
|
+
superklass = variable_lookup(aSuperExpr).value.superclass
|
|
309
|
+
method = superklass.find_method(aSuperExpr.property)
|
|
310
|
+
unless method
|
|
311
|
+
raise StandardError, "Undefined property '#{aSuperExpr.property}'."
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
stack.push method.bind(instance)
|
|
315
|
+
end
|
|
316
|
+
|
|
283
317
|
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
|
284
318
|
def before_visit_builtin(aValue)
|
|
285
319
|
stack.push(aValue)
|
|
@@ -7,17 +7,23 @@ module Loxxy
|
|
|
7
7
|
module BackEnd
|
|
8
8
|
# Runtime representation of a Lox class.
|
|
9
9
|
class LoxClass
|
|
10
|
+
# rubocop: disable Style/AccessorGrouping
|
|
11
|
+
|
|
10
12
|
# @return [String] The name of the class
|
|
11
13
|
attr_reader :name
|
|
14
|
+
attr_reader :superclass
|
|
12
15
|
|
|
13
16
|
# @return [Hash{String => LoxFunction}] the list of methods
|
|
14
17
|
attr_reader :meths
|
|
15
18
|
attr_reader :stack
|
|
16
19
|
|
|
20
|
+
# rubocop: enable Style/AccessorGrouping
|
|
21
|
+
|
|
17
22
|
# Create a class with given name
|
|
18
23
|
# @param aName [String] The name of the class
|
|
19
|
-
def initialize(aName, theMethods, anEngine)
|
|
24
|
+
def initialize(aName, aSuperclass, theMethods, anEngine)
|
|
20
25
|
@name = aName.dup
|
|
26
|
+
@superclass = aSuperclass
|
|
21
27
|
@meths = {}
|
|
22
28
|
theMethods.each do |func|
|
|
23
29
|
meths[func.name] = func
|
|
@@ -47,7 +53,12 @@ module Loxxy
|
|
|
47
53
|
|
|
48
54
|
# @param aName [String] the method name to search for
|
|
49
55
|
def find_method(aName)
|
|
50
|
-
meths[aName]
|
|
56
|
+
found = meths[aName]
|
|
57
|
+
unless found || superclass.nil?
|
|
58
|
+
found = superclass.find_method(aName)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
found
|
|
51
62
|
end
|
|
52
63
|
|
|
53
64
|
# Logical negation.
|
|
@@ -67,6 +67,16 @@ module Loxxy
|
|
|
67
67
|
previous_class = current_class
|
|
68
68
|
@current_class = :class
|
|
69
69
|
define(aClassStmt.name)
|
|
70
|
+
if aClassStmt.superclass
|
|
71
|
+
if aClassStmt.name == aClassStmt.superclass.name
|
|
72
|
+
raise StandardError, "'A class can't inherit from itself."
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@current_class = :subclass
|
|
76
|
+
aClassStmt.superclass.accept(aVisitor)
|
|
77
|
+
begin_scope
|
|
78
|
+
define('super')
|
|
79
|
+
end
|
|
70
80
|
begin_scope
|
|
71
81
|
define('this')
|
|
72
82
|
aClassStmt.body.each do |fun_stmt|
|
|
@@ -74,6 +84,7 @@ module Loxxy
|
|
|
74
84
|
resolve_function(fun_stmt, mth_type, aVisitor)
|
|
75
85
|
end
|
|
76
86
|
end_scope
|
|
87
|
+
end_scope if aClassStmt.superclass
|
|
77
88
|
@current_class = previous_class
|
|
78
89
|
end
|
|
79
90
|
|
|
@@ -169,6 +180,25 @@ module Loxxy
|
|
|
169
180
|
resolve_local(aThisExpr, aVisitor)
|
|
170
181
|
end
|
|
171
182
|
|
|
183
|
+
# rubocop: disable Style/CaseLikeIf
|
|
184
|
+
# rubocop: disable Style/StringConcatenation
|
|
185
|
+
def after_super_expr(aSuperExpr, aVisitor)
|
|
186
|
+
msg_prefix = "Error at 'super': Can't use 'super' "
|
|
187
|
+
if current_class == :none
|
|
188
|
+
err_msg = msg_prefix + 'outside of a class.'
|
|
189
|
+
raise StandardError, err_msg
|
|
190
|
+
|
|
191
|
+
elsif current_class == :class
|
|
192
|
+
err_msg = msg_prefix + 'in a class without superclass.'
|
|
193
|
+
raise StandardError, err_msg
|
|
194
|
+
|
|
195
|
+
end
|
|
196
|
+
# 'super' behaves closely to a local variable
|
|
197
|
+
resolve_local(aSuperExpr, aVisitor)
|
|
198
|
+
end
|
|
199
|
+
# rubocop: enable Style/StringConcatenation
|
|
200
|
+
# rubocop: enable Style/CaseLikeIf
|
|
201
|
+
|
|
172
202
|
# function declaration creates a new scope for its body & binds its parameters for that scope
|
|
173
203
|
def before_fun_stmt(aFunStmt, aVisitor)
|
|
174
204
|
declare(aFunStmt.name)
|
|
@@ -38,7 +38,7 @@ module Loxxy
|
|
|
38
38
|
rule('declaration' => 'stmt')
|
|
39
39
|
|
|
40
40
|
rule('classDecl' => 'CLASS classNaming class_body').as 'class_decl'
|
|
41
|
-
rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
|
|
41
|
+
rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER').as 'class_subclassing'
|
|
42
42
|
rule('classNaming' => 'IDENTIFIER').as 'class_name'
|
|
43
43
|
rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE').as 'class_body'
|
|
44
44
|
rule('methods_opt' => 'method_plus')
|
|
@@ -143,7 +143,7 @@ module Loxxy
|
|
|
143
143
|
rule('primary' => 'STRING').as 'literal_expr'
|
|
144
144
|
rule('primary' => 'IDENTIFIER').as 'variable_expr'
|
|
145
145
|
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
|
|
146
|
-
rule('primary' => 'SUPER DOT IDENTIFIER')
|
|
146
|
+
rule('primary' => 'SUPER DOT IDENTIFIER').as 'super_expr'
|
|
147
147
|
|
|
148
148
|
# Utility rules
|
|
149
149
|
rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
|
data/lib/loxxy/version.rb
CHANGED
data/loxxy.gemspec
CHANGED
|
@@ -40,8 +40,8 @@ Gem::Specification.new do |spec|
|
|
|
40
40
|
spec.version = Loxxy::VERSION
|
|
41
41
|
spec.authors = ['Dimitri Geshef']
|
|
42
42
|
spec.email = ['famished.tiger@yahoo.com']
|
|
43
|
-
spec.summary = 'An implementation of the Lox programming language.
|
|
44
|
-
spec.description = 'An implementation of the Lox programming language.
|
|
43
|
+
spec.summary = 'An implementation of the Lox programming language.'
|
|
44
|
+
spec.description = 'An implementation of the Lox programming language.'
|
|
45
45
|
spec.homepage = 'https://github.com/famished-tiger/loxxy'
|
|
46
46
|
spec.license = 'MIT'
|
|
47
47
|
spec.required_ruby_version = '~> 2.4'
|
data/spec/interpreter_spec.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'stringio'
|
|
|
6
6
|
# Load the class under test
|
|
7
7
|
require_relative '../lib/loxxy/interpreter'
|
|
8
8
|
|
|
9
|
+
# rubocop: disable Metrics/ModuleLength
|
|
9
10
|
module Loxxy
|
|
10
11
|
# This spec contains the bare bones test for the Interpreter class.
|
|
11
12
|
# The execution of Lox code is tested elsewhere.
|
|
@@ -591,7 +592,34 @@ LOX_END
|
|
|
591
592
|
predicted = 'Enjoy your bacon and toast, Dear Reader.'
|
|
592
593
|
expect(sample_cfg[:ostream].string).to eq(predicted)
|
|
593
594
|
end
|
|
595
|
+
|
|
596
|
+
it 'should support class inheritance and super keyword' do
|
|
597
|
+
lox_snippet = <<-LOX_END
|
|
598
|
+
class A {
|
|
599
|
+
method() {
|
|
600
|
+
print "A method";
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
class B < A {
|
|
605
|
+
method() {
|
|
606
|
+
print "B method";
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
test() {
|
|
610
|
+
super.method();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
class C < B {}
|
|
615
|
+
|
|
616
|
+
C().test();
|
|
617
|
+
LOX_END
|
|
618
|
+
expect { subject.evaluate(lox_snippet) }.not_to raise_error
|
|
619
|
+
expect(sample_cfg[:ostream].string).to eq('A method')
|
|
620
|
+
end
|
|
594
621
|
end # context
|
|
595
622
|
end # describe
|
|
596
623
|
# rubocop: enable Metrics/BlockLength
|
|
597
624
|
end # module
|
|
625
|
+
# rubocop: enable Metrics/ModuleLength
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: loxxy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.00
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dimitri Geshef
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-04-
|
|
11
|
+
date: 2021-04-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rley
|
|
@@ -66,7 +66,7 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '3.0'
|
|
69
|
-
description: An implementation of the Lox programming language.
|
|
69
|
+
description: An implementation of the Lox programming language.
|
|
70
70
|
email:
|
|
71
71
|
- famished.tiger@yahoo.com
|
|
72
72
|
executables:
|
|
@@ -108,6 +108,7 @@ files:
|
|
|
108
108
|
- lib/loxxy/ast/lox_return_stmt.rb
|
|
109
109
|
- lib/loxxy/ast/lox_seq_decl.rb
|
|
110
110
|
- lib/loxxy/ast/lox_set_expr.rb
|
|
111
|
+
- lib/loxxy/ast/lox_super_expr.rb
|
|
111
112
|
- lib/loxxy/ast/lox_this_expr.rb
|
|
112
113
|
- lib/loxxy/ast/lox_unary_expr.rb
|
|
113
114
|
- lib/loxxy/ast/lox_var_stmt.rb
|
|
@@ -178,7 +179,7 @@ requirements: []
|
|
|
178
179
|
rubygems_version: 3.1.4
|
|
179
180
|
signing_key:
|
|
180
181
|
specification_version: 4
|
|
181
|
-
summary: An implementation of the Lox programming language.
|
|
182
|
+
summary: An implementation of the Lox programming language.
|
|
182
183
|
test_files:
|
|
183
184
|
- spec/back_end/engine_spec.rb
|
|
184
185
|
- spec/back_end/environment_spec.rb
|