chapter 12

This commit is contained in:
2018-07-27 17:50:56 +02:00
parent da141de5e9
commit 580795cd2b
10 changed files with 283 additions and 19 deletions

View File

@@ -0,0 +1,8 @@
class DevonshireCream {
fn serveOn() {
return "Scones";
}
}
print(DevonshireCream);
print(DevonshireCream());

View File

@@ -8,8 +8,8 @@ case ARGV.length
when 1 then
begin
file ARGV[0]
rescue StandardError => e
STDERR.puts e
#rescue StandardError => e
# STDERR.puts e
end
else
puts "Usage: rlox [script]"

View File

@@ -1,12 +1,14 @@
class Callable
def initialize(name, arity, &block)
def initialize(name, params, body, env, &block)
@env = env
@name = name
@arity = arity
@params = params
@body = body
@call = block
end
def to_s()
"#{@name}:#{@arity}"
"#{@name}:#{arity}"
end
def name()
@@ -14,7 +16,7 @@ class Callable
end
def arity()
@arity
@params.length
end
def call(args)
@@ -24,4 +26,17 @@ class Callable
e.value
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

82
rlox/classes.rb Normal file
View 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

View File

@@ -4,8 +4,14 @@ class Environment
def self.global()
env = self.new()
env.define("print", Callable.new("print", 1) { | args | puts args[0] })
env.define("clock", Callable.new("clock", 0) { | args | Time.now.to_i })
env.define("print", Callable.new("print", ["obj"], nil, env) {
| args |
puts args[0]
})
env.define("clock", Callable.new("clock", [], nil, env) {
| args |
Time.now.to_i
})
env
end

View File

@@ -2,7 +2,7 @@ require './rlox/environment'
require './rlox/parse'
require './rlox/scan'
Env = Struct.new(:map, :scopes, :vars, :fn) do
Env = Struct.new(:map, :scopes, :vars, :fn, :cls) do
def child()
Env.new(map, scopes, Environment.new(vars), fn)
end
@@ -19,7 +19,7 @@ end
class Executor
def initialize()
@env = Env.new({}, [], Environment.global, nil)
@env = Env.new({}, [], Environment.global, nil, nil)
@scanner = Scanner.new
@parser = Parser.new
end
@@ -42,7 +42,7 @@ class Executor
return
end
ast.each { | stmt |
ast.map { | stmt |
stmt.eval(@env)
}
end

View File

@@ -1,3 +1,5 @@
require './rlox/classes'
def truthy?(obj)
if [nil, false, "", 0].include? obj
false
@@ -215,7 +217,8 @@ Unary = Struct.new(:operator, :right) do
end
Var = Struct.new(:name) do
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(
name.line,
"Cannot read local variable #{name.lexeme} in its own initializer."
@@ -266,7 +269,7 @@ Fn = Struct.new(:params, :body) do
end
def eval(env)
Callable.new("fn", params.length) { | args |
Callable.new("fn", params, body, env) { | args |
nenv = env.child
for i in 0..params.length-1
nenv.vars.define(params[i].lexeme, args[i])
@@ -275,6 +278,106 @@ Fn = Struct.new(:params, :body) do
}
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
def resolve(env)
@@ -353,12 +456,12 @@ While = Struct.new(:cond, :body) do
end
end
FnDef = Struct.new(:name, :params, :body) do
def resolve(env)
def resolve(env, fn=:fn)
declare(env.scopes, name)
define(env.scopes, name)
enclosing = env.fn
env.fn = :fn
env.fn = fn
begin_scope(env.scopes)
params.each{ | param |
@@ -372,7 +475,7 @@ FnDef = Struct.new(:name, :params, :body) do
end
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 |
nenv = env.child
for i in 0..params.length-1
@@ -389,6 +492,9 @@ Return = Struct.new(:keyword, :value) do
end
if value
if env.fn == :init
raise VarError.new(keyword.line, "Cannot return a value from 'init()'.")
end
value.resolve(env)
end
end

View File

@@ -88,6 +88,8 @@ class Parser
if expr.is_a? Var
name = expr.name
return Assign.new(name, value)
elsif expr.is_a? Get
return Set.new(expr.object, expr.name, value)
end
error(equals, "Invalid assignment target: '#{expr.dbg}'.")
@@ -160,6 +162,9 @@ class Parser
while true
if match(:left_paren)
expr = finish_call(expr)
elsif match(:dot)
name = consume(:id, "Expect property name after '.'.")
expr = Get.new(expr, name)
else
break
end
@@ -198,6 +203,20 @@ class Parser
Fn.new(params, body)
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()
if match(:false)
Literal.new(false)
@@ -205,6 +224,8 @@ class Parser
Literal.new(true)
elsif match(:nil)
Literal.new(nil)
elsif match(:self)
Self.new(previous)
elsif match(:number, :string)
Literal.new(previous.literal)
elsif match(:left_paren)
@@ -215,6 +236,8 @@ class Parser
Var.new(previous)
elsif match(:fn)
anon_fn
elsif match(:class)
anon_class
else
error(peek, "Expect expression.")
end
@@ -399,9 +422,28 @@ class Parser
FnDef.new(name, params, body)
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()
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")
elsif match(:let)
var_declaration

View File

@@ -19,7 +19,12 @@ def prompt()
end
begin
puts exec.run(buf).to_s
res = exec.run(buf)
if res
res.each { | res |
puts res.to_s
}
end
rescue LoxError => err
STDERR.puts err
ensure

View File

@@ -14,7 +14,7 @@ KEYWORDS = [
"or",
"return",
"super",
"this",
"self",
"true",
"let",
"while",