chapter 12
This commit is contained in:
8
examples/simple_class.lox
Normal file
8
examples/simple_class.lox
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
class DevonshireCream {
|
||||||
|
fn serveOn() {
|
||||||
|
return "Scones";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(DevonshireCream);
|
||||||
|
print(DevonshireCream());
|
4
main.rb
4
main.rb
@@ -8,8 +8,8 @@ case ARGV.length
|
|||||||
when 1 then
|
when 1 then
|
||||||
begin
|
begin
|
||||||
file ARGV[0]
|
file ARGV[0]
|
||||||
rescue StandardError => e
|
#rescue StandardError => e
|
||||||
STDERR.puts e
|
# STDERR.puts e
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
puts "Usage: rlox [script]"
|
puts "Usage: rlox [script]"
|
||||||
|
23
rlox/call.rb
23
rlox/call.rb
@@ -1,12 +1,14 @@
|
|||||||
class Callable
|
class Callable
|
||||||
def initialize(name, arity, &block)
|
def initialize(name, params, body, env, &block)
|
||||||
|
@env = env
|
||||||
@name = name
|
@name = name
|
||||||
@arity = arity
|
@params = params
|
||||||
|
@body = body
|
||||||
@call = block
|
@call = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s()
|
def to_s()
|
||||||
"#{@name}:#{@arity}"
|
"#{@name}:#{arity}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def name()
|
def name()
|
||||||
@@ -14,7 +16,7 @@ class Callable
|
|||||||
end
|
end
|
||||||
|
|
||||||
def arity()
|
def arity()
|
||||||
@arity
|
@params.length
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(args)
|
def call(args)
|
||||||
@@ -24,4 +26,17 @@ class Callable
|
|||||||
e.value
|
e.value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bind(instance)
|
||||||
|
env = @env.child
|
||||||
|
env.vars.define("self", instance)
|
||||||
|
Callable.new(@name, @params, @env, @body) { | args |
|
||||||
|
nenv = env.child
|
||||||
|
for i in 0..@params.length-1
|
||||||
|
nenv.vars.define(@params[i].lexeme, args[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
@body.eval(env)
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
82
rlox/classes.rb
Normal file
82
rlox/classes.rb
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
class LoxInstance
|
||||||
|
def initialize(klass)
|
||||||
|
@klass = klass
|
||||||
|
@fields = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def fieldnames()
|
||||||
|
"(#{@fields.keys.join(", ")})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(name)
|
||||||
|
if @fields.has_key? name.lexeme
|
||||||
|
return @fields[name.lexeme]
|
||||||
|
end
|
||||||
|
|
||||||
|
method = @klass.find_method(self, name)
|
||||||
|
if method
|
||||||
|
return method
|
||||||
|
end
|
||||||
|
|
||||||
|
raise ExecError.new(
|
||||||
|
name.line,
|
||||||
|
"Undefined property '#{name.lexeme}' on #{self}, has #{fieldnames}."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(name, val)
|
||||||
|
@fields[name.lexeme] = val
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s()
|
||||||
|
"<instance of '#{@klass}'>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class LoxClass
|
||||||
|
def initialize(name, methods)
|
||||||
|
@name = name
|
||||||
|
@methods = methods
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s()
|
||||||
|
if @name
|
||||||
|
@name.to_s
|
||||||
|
else
|
||||||
|
"<anonymous class>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def arity()
|
||||||
|
init = @methods["init"]
|
||||||
|
if init
|
||||||
|
init.arity
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def name()
|
||||||
|
@name
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(args)
|
||||||
|
ins = LoxInstance.new(self)
|
||||||
|
init = @methods["init"]
|
||||||
|
if init
|
||||||
|
init.bind(ins).call(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
ins
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_method(instance, name)
|
||||||
|
if name.lexeme == "init"
|
||||||
|
raise ExecError.new(name.line, "Cannot re-initialize class.")
|
||||||
|
end
|
||||||
|
|
||||||
|
if @methods.has_key? name.lexeme
|
||||||
|
return @methods[name.lexeme].bind(instance)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@@ -4,8 +4,14 @@ class Environment
|
|||||||
def self.global()
|
def self.global()
|
||||||
env = self.new()
|
env = self.new()
|
||||||
|
|
||||||
env.define("print", Callable.new("print", 1) { | args | puts args[0] })
|
env.define("print", Callable.new("print", ["obj"], nil, env) {
|
||||||
env.define("clock", Callable.new("clock", 0) { | args | Time.now.to_i })
|
| args |
|
||||||
|
puts args[0]
|
||||||
|
})
|
||||||
|
env.define("clock", Callable.new("clock", [], nil, env) {
|
||||||
|
| args |
|
||||||
|
Time.now.to_i
|
||||||
|
})
|
||||||
env
|
env
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ require './rlox/environment'
|
|||||||
require './rlox/parse'
|
require './rlox/parse'
|
||||||
require './rlox/scan'
|
require './rlox/scan'
|
||||||
|
|
||||||
Env = Struct.new(:map, :scopes, :vars, :fn) do
|
Env = Struct.new(:map, :scopes, :vars, :fn, :cls) do
|
||||||
def child()
|
def child()
|
||||||
Env.new(map, scopes, Environment.new(vars), fn)
|
Env.new(map, scopes, Environment.new(vars), fn)
|
||||||
end
|
end
|
||||||
@@ -19,7 +19,7 @@ end
|
|||||||
|
|
||||||
class Executor
|
class Executor
|
||||||
def initialize()
|
def initialize()
|
||||||
@env = Env.new({}, [], Environment.global, nil)
|
@env = Env.new({}, [], Environment.global, nil, nil)
|
||||||
@scanner = Scanner.new
|
@scanner = Scanner.new
|
||||||
@parser = Parser.new
|
@parser = Parser.new
|
||||||
end
|
end
|
||||||
@@ -42,7 +42,7 @@ class Executor
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
ast.each { | stmt |
|
ast.map { | stmt |
|
||||||
stmt.eval(@env)
|
stmt.eval(@env)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
require './rlox/classes'
|
||||||
|
|
||||||
def truthy?(obj)
|
def truthy?(obj)
|
||||||
if [nil, false, "", 0].include? obj
|
if [nil, false, "", 0].include? obj
|
||||||
false
|
false
|
||||||
@@ -215,7 +217,8 @@ Unary = Struct.new(:operator, :right) do
|
|||||||
end
|
end
|
||||||
Var = Struct.new(:name) do
|
Var = Struct.new(:name) do
|
||||||
def resolve(env)
|
def resolve(env)
|
||||||
if env.scopes.length > 0 and env.scopes[-1][name.lexeme][:assigned] == false
|
if (env.scopes.length > 0 and env.scopes[-1][name.lexeme] and
|
||||||
|
env.scopes[-1][name.lexeme][:assigned] == false)
|
||||||
raise VarError.new(
|
raise VarError.new(
|
||||||
name.line,
|
name.line,
|
||||||
"Cannot read local variable #{name.lexeme} in its own initializer."
|
"Cannot read local variable #{name.lexeme} in its own initializer."
|
||||||
@@ -266,7 +269,7 @@ Fn = Struct.new(:params, :body) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def eval(env)
|
def eval(env)
|
||||||
Callable.new("fn", params.length) { | args |
|
Callable.new("fn", params, body, env) { | args |
|
||||||
nenv = env.child
|
nenv = env.child
|
||||||
for i in 0..params.length-1
|
for i in 0..params.length-1
|
||||||
nenv.vars.define(params[i].lexeme, args[i])
|
nenv.vars.define(params[i].lexeme, args[i])
|
||||||
@@ -275,6 +278,106 @@ Fn = Struct.new(:params, :body) do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Get = Struct.new(:object, :name) do
|
||||||
|
def resolve(env)
|
||||||
|
object.resolve(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
def eval(env)
|
||||||
|
obj = object.eval(env)
|
||||||
|
|
||||||
|
if obj.is_a? LoxInstance
|
||||||
|
return obj.get(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
raise ExecError.new(
|
||||||
|
expr.name.line,
|
||||||
|
"Only instances have properties, #{object.lexeme} is a #{obj.class}."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Set = Struct.new(:object, :name, :value) do
|
||||||
|
def resolve(env)
|
||||||
|
value.resolve(env)
|
||||||
|
object.resolve(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
def eval(env)
|
||||||
|
obj = object.eval(env)
|
||||||
|
|
||||||
|
if not obj.is_a? LoxInstance
|
||||||
|
raise ExecError.new(
|
||||||
|
name.line,
|
||||||
|
"Only instances have fields, #{obj} is a #{obj.class}."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
val = value.eval(env)
|
||||||
|
|
||||||
|
obj.set(name, val)
|
||||||
|
|
||||||
|
val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Klass = Struct.new(:name, :methods) do
|
||||||
|
def resolve(env)
|
||||||
|
enclosing = env.cls
|
||||||
|
env.cls = :class
|
||||||
|
declare(env.scopes, name)
|
||||||
|
define(env.scopes, name)
|
||||||
|
|
||||||
|
begin_scope(env.scopes)
|
||||||
|
dummy = Token.new(:id, "self", nil, 0)
|
||||||
|
declare(env.scopes, dummy)
|
||||||
|
define(env.scopes, dummy)
|
||||||
|
|
||||||
|
methods.each { | method |
|
||||||
|
method.resolve(env, method.name.lexeme == "init" ? :init : :method)
|
||||||
|
}
|
||||||
|
|
||||||
|
end_scope(env.scopes)
|
||||||
|
env.cls = enclosing
|
||||||
|
end
|
||||||
|
|
||||||
|
def eval(env)
|
||||||
|
if name
|
||||||
|
env.vars.define(name.lexeme, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
methods_dict = {}
|
||||||
|
methods.each { | method |
|
||||||
|
fn = Callable.new(method.name.lexeme, method.params, method.body, env) {
|
||||||
|
| args |
|
||||||
|
nenv = env.child
|
||||||
|
for i in 0..method.params.length-1
|
||||||
|
nenv.vars.define(method.params[i].lexeme, args[i])
|
||||||
|
end
|
||||||
|
method.body.eval(nenv)
|
||||||
|
}
|
||||||
|
methods_dict[method.name.lexeme] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
klass = LoxClass.new(name ? name.lexeme : nil, methods_dict)
|
||||||
|
|
||||||
|
if name
|
||||||
|
env.vars.assign(name, klass)
|
||||||
|
end
|
||||||
|
klass
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Self = Struct.new(:keyword) do
|
||||||
|
def resolve(env)
|
||||||
|
if env.cls != :class
|
||||||
|
raise VarError.new(keyword.line, "Cannot use 'self' outside of a class.")
|
||||||
|
end
|
||||||
|
|
||||||
|
resolve_local(env, self, keyword)
|
||||||
|
end
|
||||||
|
|
||||||
|
def eval(env)
|
||||||
|
env.lookup(self, keyword)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Expression = Struct.new(:expression) do
|
Expression = Struct.new(:expression) do
|
||||||
def resolve(env)
|
def resolve(env)
|
||||||
@@ -353,12 +456,12 @@ While = Struct.new(:cond, :body) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
FnDef = Struct.new(:name, :params, :body) do
|
FnDef = Struct.new(:name, :params, :body) do
|
||||||
def resolve(env)
|
def resolve(env, fn=:fn)
|
||||||
declare(env.scopes, name)
|
declare(env.scopes, name)
|
||||||
define(env.scopes, name)
|
define(env.scopes, name)
|
||||||
|
|
||||||
enclosing = env.fn
|
enclosing = env.fn
|
||||||
env.fn = :fn
|
env.fn = fn
|
||||||
begin_scope(env.scopes)
|
begin_scope(env.scopes)
|
||||||
|
|
||||||
params.each{ | param |
|
params.each{ | param |
|
||||||
@@ -372,7 +475,7 @@ FnDef = Struct.new(:name, :params, :body) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def eval(env)
|
def eval(env)
|
||||||
env.vars.define(name.lexeme, Callable.new(name.lexeme, params.length) {
|
env.vars.define(name.lexeme, Callable.new(name.lexeme, params, body, env) {
|
||||||
| args |
|
| args |
|
||||||
nenv = env.child
|
nenv = env.child
|
||||||
for i in 0..params.length-1
|
for i in 0..params.length-1
|
||||||
@@ -389,6 +492,9 @@ Return = Struct.new(:keyword, :value) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
if value
|
if value
|
||||||
|
if env.fn == :init
|
||||||
|
raise VarError.new(keyword.line, "Cannot return a value from 'init()'.")
|
||||||
|
end
|
||||||
value.resolve(env)
|
value.resolve(env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -88,6 +88,8 @@ class Parser
|
|||||||
if expr.is_a? Var
|
if expr.is_a? Var
|
||||||
name = expr.name
|
name = expr.name
|
||||||
return Assign.new(name, value)
|
return Assign.new(name, value)
|
||||||
|
elsif expr.is_a? Get
|
||||||
|
return Set.new(expr.object, expr.name, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
error(equals, "Invalid assignment target: '#{expr.dbg}'.")
|
error(equals, "Invalid assignment target: '#{expr.dbg}'.")
|
||||||
@@ -160,6 +162,9 @@ class Parser
|
|||||||
while true
|
while true
|
||||||
if match(:left_paren)
|
if match(:left_paren)
|
||||||
expr = finish_call(expr)
|
expr = finish_call(expr)
|
||||||
|
elsif match(:dot)
|
||||||
|
name = consume(:id, "Expect property name after '.'.")
|
||||||
|
expr = Get.new(expr, name)
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -198,6 +203,20 @@ class Parser
|
|||||||
Fn.new(params, body)
|
Fn.new(params, body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def anon_class()
|
||||||
|
consume(:left_brace, "Expect '{' before body of anonymous class.")
|
||||||
|
|
||||||
|
methods = []
|
||||||
|
|
||||||
|
while not check(:right_brace) and not is_at_end
|
||||||
|
methods << function("method")
|
||||||
|
end
|
||||||
|
|
||||||
|
consume(:right_brace, "Expect '}' after class body.")
|
||||||
|
|
||||||
|
Klass.new(nil, methods)
|
||||||
|
end
|
||||||
|
|
||||||
def primary()
|
def primary()
|
||||||
if match(:false)
|
if match(:false)
|
||||||
Literal.new(false)
|
Literal.new(false)
|
||||||
@@ -205,6 +224,8 @@ class Parser
|
|||||||
Literal.new(true)
|
Literal.new(true)
|
||||||
elsif match(:nil)
|
elsif match(:nil)
|
||||||
Literal.new(nil)
|
Literal.new(nil)
|
||||||
|
elsif match(:self)
|
||||||
|
Self.new(previous)
|
||||||
elsif match(:number, :string)
|
elsif match(:number, :string)
|
||||||
Literal.new(previous.literal)
|
Literal.new(previous.literal)
|
||||||
elsif match(:left_paren)
|
elsif match(:left_paren)
|
||||||
@@ -215,6 +236,8 @@ class Parser
|
|||||||
Var.new(previous)
|
Var.new(previous)
|
||||||
elsif match(:fn)
|
elsif match(:fn)
|
||||||
anon_fn
|
anon_fn
|
||||||
|
elsif match(:class)
|
||||||
|
anon_class
|
||||||
else
|
else
|
||||||
error(peek, "Expect expression.")
|
error(peek, "Expect expression.")
|
||||||
end
|
end
|
||||||
@@ -399,9 +422,28 @@ class Parser
|
|||||||
FnDef.new(name, params, body)
|
FnDef.new(name, params, body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def class_declaration()
|
||||||
|
consume(:class, "Expect 'class' keyword.")
|
||||||
|
name = consume(:id, "Expect class name.")
|
||||||
|
|
||||||
|
consume(:left_brace, "Expect '{' before body of class '#{name.lexeme}.'")
|
||||||
|
|
||||||
|
methods = []
|
||||||
|
|
||||||
|
while not check(:right_brace) and not is_at_end
|
||||||
|
methods << function("method")
|
||||||
|
end
|
||||||
|
|
||||||
|
consume(:right_brace, "Expect '}' after class body.")
|
||||||
|
|
||||||
|
Klass.new(name, methods)
|
||||||
|
end
|
||||||
|
|
||||||
def declaration()
|
def declaration()
|
||||||
begin
|
begin
|
||||||
if check(:fn) and check_next(:id)
|
if check(:class) and check_next(:id)
|
||||||
|
class_declaration
|
||||||
|
elsif check(:fn) and check_next(:id)
|
||||||
function("fn")
|
function("fn")
|
||||||
elsif match(:let)
|
elsif match(:let)
|
||||||
var_declaration
|
var_declaration
|
||||||
|
@@ -19,7 +19,12 @@ def prompt()
|
|||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
puts exec.run(buf).to_s
|
res = exec.run(buf)
|
||||||
|
if res
|
||||||
|
res.each { | res |
|
||||||
|
puts res.to_s
|
||||||
|
}
|
||||||
|
end
|
||||||
rescue LoxError => err
|
rescue LoxError => err
|
||||||
STDERR.puts err
|
STDERR.puts err
|
||||||
ensure
|
ensure
|
||||||
|
@@ -14,7 +14,7 @@ KEYWORDS = [
|
|||||||
"or",
|
"or",
|
||||||
"return",
|
"return",
|
||||||
"super",
|
"super",
|
||||||
"this",
|
"self",
|
||||||
"true",
|
"true",
|
||||||
"let",
|
"let",
|
||||||
"while",
|
"while",
|
||||||
|
Reference in New Issue
Block a user