mirror of
https://gitea.invidious.io/iv-org/shard-ameba.git
synced 2024-08-15 00:53:29 +00:00
181 lines
5 KiB
Crystal
181 lines
5 KiB
Crystal
require "./variabling/*"
|
|
|
|
module Ameba::AST
|
|
# Represents a context of the local variable visibility.
|
|
# This is where the local variables belong to.
|
|
class Scope
|
|
# Link to local variables
|
|
getter variables = [] of Variable
|
|
|
|
# Link to all variable references in currency scope
|
|
getter references = [] of Reference
|
|
|
|
# Link to the arguments in current scope
|
|
getter arguments = [] of Argument
|
|
|
|
# Link to the instance variables used in current scope
|
|
getter ivariables = [] of InstanceVariable
|
|
|
|
# Link to the outer scope
|
|
getter outer_scope : Scope?
|
|
|
|
# List of inner scopes
|
|
getter inner_scopes = [] of Scope
|
|
|
|
# The actual AST node that represents a current scope.
|
|
getter node : Crystal::ASTNode
|
|
|
|
delegate to_s, to: node
|
|
delegate location, to: node
|
|
delegate end_location, to: node
|
|
|
|
def_equals_and_hash node, location
|
|
|
|
# Creates a new scope. Accepts the AST node and the outer scope.
|
|
#
|
|
# ```
|
|
# scope = Scope.new(class_node, nil)
|
|
# ```
|
|
def initialize(@node, @outer_scope = nil)
|
|
@outer_scope.try &.inner_scopes.<<(self)
|
|
end
|
|
|
|
# Creates a new variable in the current scope.
|
|
#
|
|
# ```
|
|
# scope = Scope.new(class_node, nil)
|
|
# scope.add_variable(var_node)
|
|
# ```
|
|
def add_variable(node)
|
|
variables << Variable.new(node, self)
|
|
end
|
|
|
|
# Creates a new argument in the current scope.
|
|
#
|
|
# ```
|
|
# scope = Scope.new(class_node, nil)
|
|
# scope.add_argument(arg_node)
|
|
# ```
|
|
def add_argument(node)
|
|
add_variable Crystal::Var.new(node.name).at(node)
|
|
arguments << Argument.new(node, variables.last)
|
|
end
|
|
|
|
# Adds a new instance variable to the current scope.
|
|
#
|
|
# ```
|
|
# scope = Scope.new(class_node, nil)
|
|
# scope.add_ivariable(ivar_node)
|
|
# ```
|
|
def add_ivariable(node)
|
|
ivariables << InstanceVariable.new(node)
|
|
end
|
|
|
|
# Returns variable by its name or `nil` if it does not exist.
|
|
#
|
|
# ```
|
|
# scope = Scope.new(class_node, nil)
|
|
# scope.find_variable("foo")
|
|
# ```
|
|
def find_variable(name : String)
|
|
variables.find(&.name.==(name)) || outer_scope.try &.find_variable(name)
|
|
end
|
|
|
|
# Creates a new assignment for the variable.
|
|
#
|
|
# ```
|
|
# scope = Scope.new(class_node, nil)
|
|
# scope.assign_variable(var_name, assign_node)
|
|
# ```
|
|
def assign_variable(name, node)
|
|
find_variable(name).try &.assign(node, self)
|
|
end
|
|
|
|
# Returns `true` if current scope represents a block (or proc),
|
|
# `false` otherwise.
|
|
def block?
|
|
node.is_a?(Crystal::Block) || node.is_a?(Crystal::ProcLiteral)
|
|
end
|
|
|
|
# Returns `true` if current scope represents a spawn block, e. g.
|
|
#
|
|
# ```
|
|
# spawn do
|
|
# # ...
|
|
# end
|
|
# ```
|
|
def spawn_block?
|
|
return false unless node.is_a?(Crystal::Block)
|
|
call = node.as(Crystal::Block).call
|
|
call.try(&.name) == "spawn"
|
|
end
|
|
|
|
# Returns `true` if current scope sits inside a macro.
|
|
def in_macro?
|
|
(node.is_a?(Crystal::Macro) || node.is_a?(Crystal::MacroFor)) ||
|
|
!!outer_scope.try(&.in_macro?)
|
|
end
|
|
|
|
# Returns `true` if instance variable is assinged in this scope.
|
|
def assigns_ivar?(name)
|
|
arguments.find(&.name.== name) &&
|
|
ivariables.find(&.name.== "@#{name}")
|
|
end
|
|
|
|
# Returns `true` if and only if current scope represents some
|
|
# type definition, for example a class.
|
|
def type_definition?
|
|
node.is_a?(Crystal::ClassDef) ||
|
|
node.is_a?(Crystal::ModuleDef) ||
|
|
node.is_a?(Crystal::LibDef) ||
|
|
node.is_a?(Crystal::FunDef) ||
|
|
node.is_a?(Crystal::TypeDef) ||
|
|
node.is_a?(Crystal::CStructOrUnionDef)
|
|
end
|
|
|
|
# Returns true if current scope (or any of inner scopes) references variable,
|
|
# `false` otherwise.
|
|
def references?(variable : Variable, check_inner_scopes = true)
|
|
variable.references.any? do |reference|
|
|
return true if reference.scope == self
|
|
check_inner_scopes && inner_scopes.any?(&.references?(variable))
|
|
end || variable.used_in_macro?
|
|
end
|
|
|
|
# Returns `true` if current scope is a def, `false` otherwise.
|
|
def def?
|
|
node.is_a?(Crystal::Def)
|
|
end
|
|
|
|
# Returns `true` if this scope is a top level scope, `false` otherwise.
|
|
def top_level?
|
|
outer_scope.nil?
|
|
end
|
|
|
|
# Returns true if var is an argument in current scope, `false` otherwise.
|
|
def arg?(var)
|
|
case current_node = node
|
|
when Crystal::Def
|
|
var.is_a?(Crystal::Arg) && any_arg?(current_node.args, var)
|
|
when Crystal::Block
|
|
var.is_a?(Crystal::Var) && any_arg?(current_node.args, var)
|
|
when Crystal::ProcLiteral
|
|
var.is_a?(Crystal::Var) && any_arg?(current_node.def.args, var)
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
private def any_arg?(args, var)
|
|
args.any? { |arg| arg.name == var.name && arg.location == var.location }
|
|
end
|
|
|
|
# Returns `true` if the *node* represents exactly
|
|
# the same Crystal node as `@node`.
|
|
def eql?(node)
|
|
node == @node &&
|
|
node.location &&
|
|
node.location == @node.location
|
|
end
|
|
end
|
|
end
|