done with chapter 11

This commit is contained in:
2018-07-25 18:03:31 +02:00
parent 5adcf93d74
commit da141de5e9
5 changed files with 271 additions and 12 deletions

View File

@@ -22,6 +22,10 @@ class Environment
@values[name] = value @values[name] = value
end end
def parent()
@parent
end
def get(name) def get(name)
if @values.has_key? name.lexeme if @values.has_key? name.lexeme
return @values[name.lexeme] return @values[name.lexeme]
@@ -32,6 +36,24 @@ class Environment
raise ExecError.new(name.line, "Undefined variable '#{name.lexeme}'.") raise ExecError.new(name.line, "Undefined variable '#{name.lexeme}'.")
end end
def get_at(name, distance)
env = self
distance.times {
env = env.parent
}
env.get(name)
end
def get_global(name)
env = self
while env.parent
env = env.parent
end
env.get(name)
end
def assign(name, value) def assign(name, value)
if @values.has_key? name.lexeme if @values.has_key? name.lexeme
@values[name.lexeme] = value @values[name.lexeme] = value
@@ -43,4 +65,22 @@ class Environment
raise ExecError.new(name.line, raise ExecError.new(name.line,
"Cant assign undefined variable '#{name.lexeme}'.") "Cant assign undefined variable '#{name.lexeme}'.")
end end
def assign_at(name, value, distance)
env = self
distance.times {
env = env.parent
}
env.assign(name, value)
end
def assign_global(name, value)
env = self
while env.parent
env = env.parent
end
env.assign(name, value)
end
end end

View File

@@ -18,6 +18,9 @@ end
class ParseError < LoxError class ParseError < LoxError
end end
class VarError < LoxError
end
class ExecError < LoxError class ExecError < LoxError
end end

View File

@@ -2,17 +2,46 @@ require './rlox/environment'
require './rlox/parse' require './rlox/parse'
require './rlox/scan' require './rlox/scan'
Env = Struct.new(:map, :scopes, :vars, :fn) do
def child()
Env.new(map, scopes, Environment.new(vars), fn)
end
def lookup(expr, name)
distance = map[expr]
if distance
vars.get_at(name, distance)
else
vars.get_global(name)
end
end
end
class Executor class Executor
def initialize() def initialize()
@env = Environment.global @env = Env.new({}, [], Environment.global, nil)
@scanner = Scanner.new @scanner = Scanner.new
@parser = Parser.new @parser = Parser.new
end end
def run(source) def run(source)
errord = false
tokens = @scanner.scan_on(source) tokens = @scanner.scan_on(source)
ast = @parser.parse_on(tokens) ast = @parser.parse_on(tokens)
ast.each { | stmt |
begin
stmt.resolve(@env)
rescue VarError => e
STDERR.puts e
errord = true
end
}
if errord
return
end
ast.each { | stmt | ast.each { | stmt |
stmt.eval(@env) stmt.eval(@env)
} }

View File

@@ -6,6 +6,68 @@ def truthy?(obj)
end end
end end
def begin_scope(scopes)
scopes.push({})
end
def end_scope(scopes)
last_scope = scopes.pop()
last_scope.each { | key, val |
if val[:used] == 0
raise VarError.new(val[:line], "Variable #{key} declared but never used.")
end
}
end
def declare(scopes, name)
if scopes.length == 0
return
end
scope = scopes[-1]
if scope.has_key? name.lexeme
raise VarError.new(
name.line,
"Variable with the name '#{name.lexeme}' already declared in this scope."
)
end
scope[name.lexeme] = {:assigned => false, :used => 0, :line => name.line}
end
def define(scopes, name)
if scopes.length == 0
return
end
scope = scopes[-1]
if not scope.has_key? name.lexeme
raise VarError.new(
name.line,
"Variable '#{name.lexeme}' was assigned, but not declared."
)
end
scope[name.lexeme][:assigned] = true
scope[name.lexeme][:used] += 1
end
def resolve_local(env, expr, name)
if env.scopes.length == 0
return
end
(env.scopes.length-1).downto(0) { | i |
if env.scopes[i].has_key? name.lexeme
env.map[expr] = env.scopes.length - 2 - i
env.scopes[i][name.lexeme][:used] += 1
return
end
}
end
def check_number_op(operator, *operands) def check_number_op(operator, *operands)
operands.each{ | operand | operands.each{ | operand |
if not operand.is_a? Numeric if not operand.is_a? Numeric
@@ -24,7 +86,16 @@ def check_same_type_op(operator, *operands)
end end
} }
end end
Call = Struct.new(:callee, :paren, :arguments) do Call = Struct.new(:callee, :paren, :arguments) do
def resolve(env)
callee.resolve(env)
arguments.each{ | arg |
arg.resolve(env)
}
end
def eval(env) def eval(env)
to_call = callee.eval(env) to_call = callee.eval(env)
al = arguments.length al = arguments.length
@@ -50,13 +121,28 @@ Call = Struct.new(:callee, :paren, :arguments) do
end end
Assign = Struct.new(:name, :value) do Assign = Struct.new(:name, :value) do
def resolve(env)
value.resolve(env)
resolve_local(env, self, name)
end
def eval(env) def eval(env)
val = value.eval(env) val = value.eval(env)
env.assign(name, val) distance = env.map[self]
if distance
env.vars.assign_at(name, val, distance)
else
env.vars.assign_global(name, val)
end
val val
end end
end end
Binary = Struct.new(:left, :operator, :right) do Binary = Struct.new(:left, :operator, :right) do
def resolve(env)
left.resolve(env)
right.resolve(env)
end
def eval(env) def eval(env)
l = left.eval(env) l = left.eval(env)
r = right.eval(env) r = right.eval(env)
@@ -94,16 +180,27 @@ Binary = Struct.new(:left, :operator, :right) do
end end
end end
Grouping = Struct.new(:expression) do Grouping = Struct.new(:expression) do
def resolve(env)
expression.resolve(env)
end
def eval(env) def eval(env)
expression.eval(env) expression.eval(env)
end end
end end
Literal = Struct.new(:value) do Literal = Struct.new(:value) do
def resolve(env)
end
def eval(env) def eval(env)
value value
end end
end end
Unary = Struct.new(:operator, :right) do Unary = Struct.new(:operator, :right) do
def resolve(env)
right.resolve(env)
end
def eval(env) def eval(env)
r = right.eval(env) r = right.eval(env)
@@ -117,11 +214,27 @@ Unary = Struct.new(:operator, :right) do
end end
end end
Var = Struct.new(:name) do Var = Struct.new(:name) do
def resolve(env)
if env.scopes.length > 0 and env.scopes[-1][name.lexeme][:assigned] == false
raise VarError.new(
name.line,
"Cannot read local variable #{name.lexeme} in its own initializer."
)
end
resolve_local(env, self, name)
end
def eval(env) def eval(env)
env.get(name) env.lookup(self, name)
end end
end end
Logical = Struct.new(:left, :operator, :right) do Logical = Struct.new(:left, :operator, :right) do
def resolve(env)
left.resolve(env)
right.resolve(env)
end
def eval(env) def eval(env)
l = left.eval(env) l = left.eval(env)
@@ -140,11 +253,23 @@ Logical = Struct.new(:left, :operator, :right) do
end end
end end
Fn = Struct.new(:params, :body) do Fn = Struct.new(:params, :body) do
def resolve(env)
begin_scope(env.scopes)
params.each{ | param |
declare(env.scopes, param)
define(env.scopes, param)
}
body.resolve(env)
end_scope(env.scopes)
end
def eval(env) def eval(env)
Callable.new("fn", params.length) { | args | Callable.new("fn", params.length) { | args |
nenv = Environment.new(env) nenv = env.child
for i in 0..params.length-1 for i in 0..params.length-1
nenv.define(params[i].lexeme, args[i]) nenv.vars.define(params[i].lexeme, args[i])
end end
body.eval(nenv) body.eval(nenv)
} }
@@ -152,23 +277,43 @@ Fn = Struct.new(:params, :body) do
end end
Expression = Struct.new(:expression) do Expression = Struct.new(:expression) do
def resolve(env)
expression.resolve(env)
end
def eval(env) def eval(env)
expression.eval(env) expression.eval(env)
end end
end end
Variable = Struct.new(:name, :initializer) do Variable = Struct.new(:name, :initializer) do
def resolve(env)
declare(env.scopes, name)
if initializer
initializer.resolve(env)
end
define(env.scopes, name)
end
def eval(env) def eval(env)
value = nil value = nil
if initializer != nil if initializer != nil
value = initializer.eval(env) value = initializer.eval(env)
end end
env.define(name.lexeme, value) env.vars.define(name.lexeme, value)
end end
end end
Block = Struct.new(:stmts) do Block = Struct.new(:stmts) do
def resolve(env)
begin_scope(env.scopes)
stmts.each{ | stmt |
stmt.resolve(env)
}
end_scope(env.scopes)
end
def eval(env) def eval(env)
child = Environment.new(env) child = env.child
ret = nil ret = nil
stmts.each{ | stmt | stmts.each{ | stmt |
ret = stmt.eval(child) ret = stmt.eval(child)
@@ -177,6 +322,14 @@ Block = Struct.new(:stmts) do
end end
end end
If = Struct.new(:cond, :thn, :els) do If = Struct.new(:cond, :thn, :els) do
def resolve(env)
cond.resolve(env)
thn.resolve(env)
if els
els.resolve(env)
end
end
def eval(env) def eval(env)
if truthy? cond.eval(env) if truthy? cond.eval(env)
thn.eval(env) thn.eval(env)
@@ -186,6 +339,11 @@ If = Struct.new(:cond, :thn, :els) do
end end
end end
While = Struct.new(:cond, :body) do While = Struct.new(:cond, :body) do
def resolve(env)
cond.resolve(env)
body.resolve(env)
end
def eval(env) def eval(env)
ret = nil ret = nil
while truthy? cond.eval(env) while truthy? cond.eval(env)
@@ -195,18 +353,46 @@ 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)
declare(env.scopes, name)
define(env.scopes, name)
enclosing = env.fn
env.fn = :fn
begin_scope(env.scopes)
params.each{ | param |
declare(env.scopes, param)
define(env.scopes, param)
}
body.resolve(env)
end_scope(env.scopes)
env.fn = enclosing
end
def eval(env) def eval(env)
env.define(name.lexeme, Callable.new(name.lexeme, params.length) { env.vars.define(name.lexeme, Callable.new(name.lexeme, params.length) {
| args | | args |
nenv = Environment.new(env) nenv = env.child
for i in 0..params.length-1 for i in 0..params.length-1
nenv.define(params[i].lexeme, args[i]) nenv.vars.define(params[i].lexeme, args[i])
end end
body.eval(nenv) body.eval(nenv)
}) })
end end
end end
Return = Struct.new(:value) do Return = Struct.new(:keyword, :value) do
def resolve(env)
if not env.fn
raise VarError.new(keyword.line, "Cannot return from top-level code.")
end
if value
value.resolve(env)
end
end
def eval(env) def eval(env)
raise ReturnError.new(value == nil ? nil : value.eval(env)) raise ReturnError.new(value == nil ? nil : value.eval(env))
end end

View File

@@ -343,6 +343,7 @@ class Parser
end end
def return_statement() def return_statement()
keyword = previous
value = nil value = nil
if !check(:semicolon) if !check(:semicolon)
@@ -350,7 +351,7 @@ class Parser
end end
consume(:semicolon, "Expect ';' after return value.") consume(:semicolon, "Expect ';' after return value.")
Return.new(value) Return.new(keyword, value)
end end
def statement() def statement()