Files
rlox/rlox/expression.rb
2018-07-27 17:50:56 +02:00

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