You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
3.1 KiB

2 years ago
require_relative "error"
2 years ago
module Lox
class Resolver
def initialize(interpreter)
@interpreter = interpreter
@scopes = []
2 years ago
@current_func = :NONE
2 years ago
end
def resolve(*resolvees)
resolvees.each do |resolvee|
resolvee.accept(self)
2 years ago
end
nil
2 years ago
end
def visit_block(stmt)
with_scope do
2 years ago
resolve(*stmt.stmts)
2 years ago
end
nil
end
2 years ago
def visit_class(stmt)
declare(stmt.name)
define(stmt.name)
nil
end
def visit_expr(stmt) = resolve(stmt.expr)
2 years ago
def visit_function(stmt)
declare(stmt.name)
define(stmt.name)
2 years ago
resolve_function(stmt, :FUNCTION)
2 years ago
nil
end
def visit_if(stmt)
2 years ago
resolve(stmt.cond, stmt.then)
2 years ago
resolve(stmt.else) if stmt.else
end
def visit_print(stmt) = resolve(stmt.expr)
2 years ago
def visit_return(stmt)
2 years ago
raise ResolverError.new(stmt.keyword, "Can't return from top-level code.") if @current_func == :NONE
2 years ago
resolve(stmt.value) if stmt.value
end
2 years ago
2 years ago
def visit_var(stmt)
declare(stmt.name)
resolve(stmt.initializer) if stmt.initializer
define(stmt.name)
nil
end
2 years ago
def visit_while(stmt) = resolve(stmt.cond, stmt.body)
2 years ago
2 years ago
def visit_variable(expr)
if !@scopes.empty? && @scopes.last[expr.name.lexeme] == false
raise ResolverError.new(expr.name, "Can't read local variable in its own initializer.")
end
resolve_local(expr, expr.name)
nil
end
2 years ago
def visit_assign(expr)
resolve(expr.value)
resolve_local(expr, expr.name)
nil
end
def visit_binary(expr) = resolve(expr.left, expr.right)
def visit_call(expr) = resolve(expr.callee, *expr.args)
2 years ago
def visit_get(expr)
resolve(expr.object)
nil
end
def visit_grouping(expr) = resolve(expr.expr)
def visit_literal(expr) = nil
def visit_logical(expr) = resolve(expr.left, expr.right)
2 years ago
def visit_set(expr)
resolve(expr.value)
resolve(expr.object)
nil
end
def visit_unary(expr) = resolve(expr.right)
2 years ago
2 years ago
private
2 years ago
def with_scope
@scopes.push({})
2 years ago
yield
2 years ago
@scopes.pop
2 years ago
end
2 years ago
def declare(name)
scope = @scopes.last
return if scope.nil?
2 years ago
raise ResolverError.new(name, "Already a variable with this name in this scope") if scope.has_key?(name.lexeme)
2 years ago
scope[name.lexeme] = false
end
def define(name)
scope = @scopes.last
return if scope.nil?
2 years ago
scope[name.lexeme] = true
2 years ago
end
2 years ago
2 years ago
def resolve_local(expr, name)
2 years ago
scope_and_depth = @scopes.reverse.each.with_index.find {|scope, depth| scope.has_key?(name.lexeme) }
2 years ago
return unless scope_and_depth
scope, depth = scope_and_depth
@interpreter.resolve(expr, depth)
end
2 years ago
def resolve_function(fn, type)
with_func_type(type) do
with_scope do
fn.params.each do |param|
declare(param)
define(param)
end
resolve(*fn.body)
2 years ago
end
end
end
2 years ago
def with_func_type(type)
enclosing_func = @current_func
@current_func = type
yield
@current_func = enclosing_func
end
2 years ago
end
end