506 lines
9.7 KiB
Ruby
506 lines
9.7 KiB
Ruby
require './rlox/classes'
|
|
|
|
def truthy?(obj)
|
|
if [nil, false, "", 0].include? obj
|
|
false
|
|
else
|
|
true
|
|
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
|
|
raise ExecError.new(operator.line,
|
|
"Operand to '#{operator.lexeme}' must be a number.")
|
|
end
|
|
}
|
|
end
|
|
|
|
def check_same_type_op(operator, *operands)
|
|
fst = operands[0]
|
|
operands.each{ | operand |
|
|
if not operand.is_a? fst.class
|
|
raise ExecError.new(operator.line,
|
|
"Operand to '#{operator.lexeme}' must be a #{fst.class}.")
|
|
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
|
|
|
|
if not to_call.class.method_defined? :call
|
|
raise ExecError.new(paren.line, "Can only call functions and classes.")
|
|
end
|
|
|
|
if al != to_call.arity
|
|
raise ExecError.new(
|
|
paren.line,
|
|
"#{to_call.name} expected #{to_call.arity} arguments but got #{al}."
|
|
)
|
|
end
|
|
|
|
args = []
|
|
arguments.each{ | arg |
|
|
args << arg.eval(env)
|
|
}
|
|
|
|
to_call.call(args)
|
|
end
|
|
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)
|
|
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)
|
|
|
|
case operator.type
|
|
when :minus then
|
|
check_number_op(operator, l, r)
|
|
l - r
|
|
when :plus then
|
|
check_same_type_op(operator, l, r)
|
|
l + r
|
|
when :slash then
|
|
check_number_op(operator, l, r)
|
|
l / r
|
|
when :star then
|
|
check_number_op(operator, l, r)
|
|
l * r
|
|
when :gt then
|
|
check_number_op(operator, l, r)
|
|
l > r
|
|
when :geq then
|
|
check_number_op(operator, l, r)
|
|
l >= r
|
|
when :lt then
|
|
check_number_op(operator, l, r)
|
|
l < r
|
|
when :leq then
|
|
check_number_op(operator, l, r)
|
|
l <= r
|
|
when :bang_eq then l != r
|
|
when :eq_eq then l == r
|
|
when :comma then r
|
|
else nil
|
|
end
|
|
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)
|
|
|
|
case operator.type
|
|
when :bang then truthy?(r)
|
|
when :minus then
|
|
check_number_op(operator, r)
|
|
-r
|
|
else nil
|
|
end
|
|
end
|
|
end
|
|
Var = Struct.new(:name) do
|
|
def resolve(env)
|
|
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."
|
|
)
|
|
end
|
|
|
|
resolve_local(env, self, name)
|
|
end
|
|
|
|
def eval(env)
|
|
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)
|
|
|
|
case operator.type
|
|
when :or then
|
|
if truthy?(l)
|
|
return l
|
|
end
|
|
when :and then
|
|
if not truthy?(l)
|
|
return l
|
|
end
|
|
end
|
|
|
|
right.eval(env)
|
|
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, body, env) { | args |
|
|
nenv = env.child
|
|
for i in 0..params.length-1
|
|
nenv.vars.define(params[i].lexeme, args[i])
|
|
end
|
|
body.eval(nenv)
|
|
}
|
|
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)
|
|
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.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 = env.child
|
|
ret = nil
|
|
stmts.each{ | stmt |
|
|
ret = stmt.eval(child)
|
|
}
|
|
ret
|
|
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)
|
|
elsif els != nil
|
|
els.eval(env)
|
|
end
|
|
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)
|
|
ret = body.eval(env)
|
|
end
|
|
ret
|
|
end
|
|
end
|
|
FnDef = Struct.new(:name, :params, :body) do
|
|
def resolve(env, fn=:fn)
|
|
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.vars.define(name.lexeme, Callable.new(name.lexeme, params, body, env) {
|
|
| args |
|
|
nenv = env.child
|
|
for i in 0..params.length-1
|
|
nenv.vars.define(params[i].lexeme, args[i])
|
|
end
|
|
body.eval(nenv)
|
|
})
|
|
end
|
|
end
|
|
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
|
|
if env.fn == :init
|
|
raise VarError.new(keyword.line, "Cannot return a value from 'init()'.")
|
|
end
|
|
value.resolve(env)
|
|
end
|
|
end
|
|
|
|
def eval(env)
|
|
raise ReturnError.new(value == nil ? nil : value.eval(env))
|
|
end
|
|
end
|