done with chapter 11
This commit is contained in:
@@ -22,6 +22,10 @@ class Environment
|
||||
@values[name] = value
|
||||
end
|
||||
|
||||
def parent()
|
||||
@parent
|
||||
end
|
||||
|
||||
def get(name)
|
||||
if @values.has_key? name.lexeme
|
||||
return @values[name.lexeme]
|
||||
@@ -32,6 +36,24 @@ class Environment
|
||||
raise ExecError.new(name.line, "Undefined variable '#{name.lexeme}'.")
|
||||
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)
|
||||
if @values.has_key? name.lexeme
|
||||
@values[name.lexeme] = value
|
||||
@@ -43,4 +65,22 @@ class Environment
|
||||
raise ExecError.new(name.line,
|
||||
"Can’t assign undefined variable '#{name.lexeme}'.")
|
||||
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
|
||||
|
@@ -18,6 +18,9 @@ end
|
||||
class ParseError < LoxError
|
||||
end
|
||||
|
||||
class VarError < LoxError
|
||||
end
|
||||
|
||||
class ExecError < LoxError
|
||||
end
|
||||
|
||||
|
@@ -2,17 +2,46 @@ require './rlox/environment'
|
||||
require './rlox/parse'
|
||||
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
|
||||
def initialize()
|
||||
@env = Environment.global
|
||||
@env = Env.new({}, [], Environment.global, nil)
|
||||
@scanner = Scanner.new
|
||||
@parser = Parser.new
|
||||
end
|
||||
|
||||
def run(source)
|
||||
errord = false
|
||||
tokens = @scanner.scan_on(source)
|
||||
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 |
|
||||
stmt.eval(@env)
|
||||
}
|
||||
|
@@ -6,6 +6,68 @@ def truthy?(obj)
|
||||
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)
|
||||
operands.each{ | operand |
|
||||
if not operand.is_a? Numeric
|
||||
@@ -24,7 +86,16 @@ def check_same_type_op(operator, *operands)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
Call = Struct.new(:callee, :paren, :arguments) do
|
||||
def resolve(env)
|
||||
callee.resolve(env)
|
||||
|
||||
arguments.each{ | arg |
|
||||
arg.resolve(env)
|
||||
}
|
||||
end
|
||||
|
||||
def eval(env)
|
||||
to_call = callee.eval(env)
|
||||
al = arguments.length
|
||||
@@ -50,13 +121,28 @@ Call = Struct.new(:callee, :paren, :arguments) do
|
||||
end
|
||||
|
||||
Assign = Struct.new(:name, :value) do
|
||||
def resolve(env)
|
||||
value.resolve(env)
|
||||
resolve_local(env, self, name)
|
||||
end
|
||||
|
||||
def 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
|
||||
end
|
||||
end
|
||||
Binary = Struct.new(:left, :operator, :right) do
|
||||
def resolve(env)
|
||||
left.resolve(env)
|
||||
right.resolve(env)
|
||||
end
|
||||
|
||||
def eval(env)
|
||||
l = left.eval(env)
|
||||
r = right.eval(env)
|
||||
@@ -94,16 +180,27 @@ Binary = Struct.new(:left, :operator, :right) do
|
||||
end
|
||||
end
|
||||
Grouping = Struct.new(:expression) do
|
||||
def resolve(env)
|
||||
expression.resolve(env)
|
||||
end
|
||||
|
||||
def eval(env)
|
||||
expression.eval(env)
|
||||
end
|
||||
end
|
||||
Literal = Struct.new(:value) do
|
||||
def resolve(env)
|
||||
end
|
||||
|
||||
def eval(env)
|
||||
value
|
||||
end
|
||||
end
|
||||
Unary = Struct.new(:operator, :right) do
|
||||
def resolve(env)
|
||||
right.resolve(env)
|
||||
end
|
||||
|
||||
def eval(env)
|
||||
r = right.eval(env)
|
||||
|
||||
@@ -117,11 +214,27 @@ Unary = Struct.new(:operator, :right) do
|
||||
end
|
||||
end
|
||||
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)
|
||||
env.get(name)
|
||||
env.lookup(self, name)
|
||||
end
|
||||
end
|
||||
Logical = Struct.new(:left, :operator, :right) do
|
||||
def resolve(env)
|
||||
left.resolve(env)
|
||||
right.resolve(env)
|
||||
end
|
||||
|
||||
def eval(env)
|
||||
l = left.eval(env)
|
||||
|
||||
@@ -140,11 +253,23 @@ Logical = Struct.new(:left, :operator, :right) do
|
||||
end
|
||||
end
|
||||
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)
|
||||
Callable.new("fn", params.length) { | args |
|
||||
nenv = Environment.new(env)
|
||||
nenv = env.child
|
||||
for i in 0..params.length-1
|
||||
nenv.define(params[i].lexeme, args[i])
|
||||
nenv.vars.define(params[i].lexeme, args[i])
|
||||
end
|
||||
body.eval(nenv)
|
||||
}
|
||||
@@ -152,23 +277,43 @@ Fn = Struct.new(:params, :body) do
|
||||
end
|
||||
|
||||
Expression = Struct.new(:expression) do
|
||||
def resolve(env)
|
||||
expression.resolve(env)
|
||||
end
|
||||
|
||||
def eval(env)
|
||||
expression.eval(env)
|
||||
end
|
||||
end
|
||||
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)
|
||||
value = nil
|
||||
if initializer != nil
|
||||
value = initializer.eval(env)
|
||||
end
|
||||
|
||||
env.define(name.lexeme, value)
|
||||
env.vars.define(name.lexeme, value)
|
||||
end
|
||||
end
|
||||
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)
|
||||
child = Environment.new(env)
|
||||
child = env.child
|
||||
ret = nil
|
||||
stmts.each{ | stmt |
|
||||
ret = stmt.eval(child)
|
||||
@@ -177,6 +322,14 @@ Block = Struct.new(:stmts) do
|
||||
end
|
||||
end
|
||||
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)
|
||||
if truthy? cond.eval(env)
|
||||
thn.eval(env)
|
||||
@@ -186,6 +339,11 @@ If = Struct.new(:cond, :thn, :els) do
|
||||
end
|
||||
end
|
||||
While = Struct.new(:cond, :body) do
|
||||
def resolve(env)
|
||||
cond.resolve(env)
|
||||
body.resolve(env)
|
||||
end
|
||||
|
||||
def eval(env)
|
||||
ret = nil
|
||||
while truthy? cond.eval(env)
|
||||
@@ -195,18 +353,46 @@ While = Struct.new(:cond, :body) do
|
||||
end
|
||||
end
|
||||
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)
|
||||
env.define(name.lexeme, Callable.new(name.lexeme, params.length) {
|
||||
env.vars.define(name.lexeme, Callable.new(name.lexeme, params.length) {
|
||||
| args |
|
||||
nenv = Environment.new(env)
|
||||
nenv = env.child
|
||||
for i in 0..params.length-1
|
||||
nenv.define(params[i].lexeme, args[i])
|
||||
nenv.vars.define(params[i].lexeme, args[i])
|
||||
end
|
||||
body.eval(nenv)
|
||||
})
|
||||
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)
|
||||
raise ReturnError.new(value == nil ? nil : value.eval(env))
|
||||
end
|
||||
|
@@ -343,6 +343,7 @@ class Parser
|
||||
end
|
||||
|
||||
def return_statement()
|
||||
keyword = previous
|
||||
value = nil
|
||||
|
||||
if !check(:semicolon)
|
||||
@@ -350,7 +351,7 @@ class Parser
|
||||
end
|
||||
|
||||
consume(:semicolon, "Expect ';' after return value.")
|
||||
Return.new(value)
|
||||
Return.new(keyword, value)
|
||||
end
|
||||
|
||||
def statement()
|
||||
|
Reference in New Issue
Block a user