done with chapter 11
This commit is contained in:
@@ -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,
|
||||||
"Can’t assign undefined variable '#{name.lexeme}'.")
|
"Can’t 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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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()
|
||||||
|
Reference in New Issue
Block a user