diff --git a/examples/inheritance.lox b/examples/inheritance.lox new file mode 100644 index 0000000..6a9b918 --- /dev/null +++ b/examples/inheritance.lox @@ -0,0 +1,9 @@ +class Doughnut { + fn cook() { + print("Fry until golden brown."); + } +} + +class BostonCream < Doughnut {} + +BostonCream().cook(); diff --git a/examples/super.lox b/examples/super.lox new file mode 100644 index 0000000..63a8027 --- /dev/null +++ b/examples/super.lox @@ -0,0 +1,14 @@ +class Doughnut { + fn cook() { + print("Fry until golden brown."); + } +} + +class BostonCream < Doughnut { + fn cook() { + super.cook(); + print("Pipe full of custard and coat with chocolate."); + } +} + +BostonCream().cook(); diff --git a/rlox/classes.rb b/rlox/classes.rb index c92e37b..3abba3c 100644 --- a/rlox/classes.rb +++ b/rlox/classes.rb @@ -34,9 +34,10 @@ class LoxInstance end class LoxClass - def initialize(name, methods) + def initialize(name, methods, superclass) @name = name @methods = methods + @super = superclass end def to_s() @@ -78,5 +79,9 @@ class LoxClass if @methods.has_key? name.lexeme return @methods[name.lexeme].bind(instance) end + + if @super + return @super.find_method(instance, name) + end end end diff --git a/rlox/executor.rb b/rlox/executor.rb index 4da723a..086215e 100644 --- a/rlox/executor.rb +++ b/rlox/executor.rb @@ -2,9 +2,9 @@ require './rlox/environment' require './rlox/parse' require './rlox/scan' -Env = Struct.new(:map, :scopes, :vars, :fn, :cls) do +Env = Struct.new(:map, :scopes, :vars, :fn, :cls, :parent) do def child() - Env.new(map, scopes, Environment.new(vars), fn) + Env.new(map, scopes, Environment.new(vars), fn, cls, self) end def lookup(expr, name) @@ -19,7 +19,7 @@ end class Executor def initialize() - @env = Env.new({}, [], Environment.global, nil, nil) + @env = Env.new({}, [], Environment.global, nil, nil, nil) @scanner = Scanner.new @parser = Parser.new end diff --git a/rlox/expression.rb b/rlox/expression.rb index c0ddf07..2aa6457 100644 --- a/rlox/expression.rb +++ b/rlox/expression.rb @@ -319,13 +319,27 @@ Set = Struct.new(:object, :name, :value) do val end end -Klass = Struct.new(:name, :methods) do +Klass = Struct.new(:name, :methods, :superclass) do def resolve(env) enclosing = env.cls env.cls = :class declare(env.scopes, name) + + if superclass != nil + env.cls = :subclass + superclass.resolve(env) + end + define(env.scopes, name) + if superclass + begin_scope(env.scopes) + + dummy = Token.new(:id, "super", nil, 0) + declare(env.scopes, dummy) + define(env.scopes, dummy) + end + begin_scope(env.scopes) dummy = Token.new(:id, "self", nil, 0) declare(env.scopes, dummy) @@ -336,14 +350,33 @@ Klass = Struct.new(:name, :methods) do } end_scope(env.scopes) + + if superclass + end_scope(env.scopes) + end + env.cls = enclosing end def eval(env) + supcls = nil + if superclass + supcls = superclass.eval(env) + + if not supcls.is_a? LoxClass + raise ExecError.new(name.line, "Superclass must be a class.") + end + end + if name env.vars.define(name.lexeme, nil) end + if superclass + env = env.child + env.vars.define("super", supcls) + end + methods_dict = {} methods.each { | method | fn = Callable.new(method.name.lexeme, method.params, method.body, env) { @@ -357,7 +390,11 @@ Klass = Struct.new(:name, :methods) do methods_dict[method.name.lexeme] = fn } - klass = LoxClass.new(name ? name.lexeme : nil, methods_dict) + klass = LoxClass.new(name ? name.lexeme : nil, methods_dict, supcls) + + if superclass + env = env.parent + end if name env.vars.assign(name, klass) @@ -378,6 +415,37 @@ Self = Struct.new(:keyword) do env.lookup(self, keyword) end end +Super = Struct.new(:keyword, :method) do + def resolve(env) + if not env.cls + raise VarError.new(keyword.line, "Cannot use 'super' outside of a class.") + elsif env.cls != :subclass + raise VarError.new( + keyword.line, + "Cannot use 'super' in a class without superclass." + ) + end + + resolve_local(env, self, keyword) + end + + def eval(env) + distance = env.map[self] + superclass = env.vars.get_at(keyword, distance) + dummy = Token.new(:id, "self", nil, 0) + obj = env.vars.get_at(dummy, distance-1) + m = superclass.find_method(obj, method) + + if not m + raise ExecError.new( + method.line, + "Undefined 'super' property '#{method.lexeme}'." + ) + end + + m + end +end Expression = Struct.new(:expression) do def resolve(env) diff --git a/rlox/parse.rb b/rlox/parse.rb index 742ba54..0174836 100644 --- a/rlox/parse.rb +++ b/rlox/parse.rb @@ -226,6 +226,11 @@ class Parser Literal.new(nil) elsif match(:self) Self.new(previous) + elsif match(:super) + kw = previous + consume(:dot, "Expect '.' after 'super'.") + method = consume(:id, "Expect superclass method name.") + Super.new(kw, method) elsif match(:number, :string) Literal.new(previous.literal) elsif match(:left_paren) @@ -426,6 +431,13 @@ class Parser consume(:class, "Expect 'class' keyword.") name = consume(:id, "Expect class name.") + superclass = nil + + if match(:lt) + consume(:id, "Expect superclass name.") + superclass = Var.new(previous) + end + consume(:left_brace, "Expect '{' before body of class '#{name.lexeme}.'") methods = [] @@ -436,7 +448,7 @@ class Parser consume(:right_brace, "Expect '}' after class body.") - Klass.new(name, methods) + Klass.new(name, methods, superclass) end def declaration()