nitc :: scope $ ACallAssignExpr
A setter call with a standard method-name and any number of arguments. egx.m(y)=z
. OR just a simple assignment.
nitc :: scope $ ACallFormExpr
Whatever looks-like a call with a standard method and any number of arguments.nitc :: scope $ ACallReassignExpr
A complex setter call with a standard method-name and any number of arguments. egx.m(y)+=z
. OR just a simple complex assignment.
nitc :: scope $ ALambdaExpr
nitc :: scope $ AVarFormExpr
Whatever is an access to a local variablenitc :: scope $ AVardeclExpr
A declaration of a local variable. egvar x: X = y
nitc :: scope $ ACallAssignExpr
A setter call with a standard method-name and any number of arguments. egx.m(y)=z
. OR just a simple assignment.
nitc :: scope $ ACallFormExpr
Whatever looks-like a call with a standard method and any number of arguments.nitc :: scope $ ACallReassignExpr
A complex setter call with a standard method-name and any number of arguments. egx.m(y)+=z
. OR just a simple complex assignment.
nitc :: scope $ ALambdaExpr
nitc :: scope $ AVarFormExpr
Whatever is an access to a local variablenitc :: scope $ AVardeclExpr
A declaration of a local variable. egvar x: X = y
Serializable::inspect
to show more useful information
more_collections :: more_collections
Highly specific, but useful, collections-related classes.serialization :: serialization_core
Abstract services to serialize Nit objects to different formatsnitc :: toolcontext
Common command-line tool infrastructure than handle options and error messagescore :: union_find
union–find algorithm using an efficient disjoint-set data structurenitc :: modelbuilder
nitc :: api_metrics
nitc :: astbuilder
Instantiation and transformation of semantic nodes in the AST of expressions and statementscflags
and ldflags
to specify
extra_java_files
to compile extra java files
nitc :: i18n_phase
Basic support of internationalization through the generation of id-to-string tablesnitc :: light_only
Compiler support for the light FFI only, detects unsupported usage of callbacksnitc
.
nitc :: nitmetrics
A program that collects various metrics on nit programs and librariesnitc :: nitrestful
Tool generating boilerplate code linking RESTful actions to Nit methodsthreaded
annotation
nitc :: separate_erasure_compiler
Separate compilation of a Nit program with generic type erasureclone
method of the astbuilder tool
# Identification and scoping of local variables and labels.
module scope
import phase
import modelbuilder
redef class ToolContext
# Run `APropdef::do_scope` on each propdef.
var scope_phase: Phase = new ScopePhase(self, null)
end
private class ScopePhase
super Phase
redef fun process_npropdef(npropdef) do npropdef.do_scope(toolcontext)
end
# A local variable (including parameters, automatic variables and self)
class Variable
# The name of the variable (as used in the program)
var name: String is writable
# Alias of `name`
redef fun to_s do return self.name
# The declaration of the variable, if any
var location: nullable Location = null is writable
# Is the local variable not read and need a warning?
var warn_unread = false is writable
end
# Mark where break and continue will branch.
# Marks are either associated with a label of with a for_loop structure
class EscapeMark
# The name of the label (unless the mark is an anonymous loop mark)
var name: nullable String
# The associated `continue` mark, if any.
# If the mark attached to a loop (loop, while, for), a distinct mark is used.
private var continue_mark: nullable EscapeMark = null
# Each break/continue attached to the mark
var escapes = new Array[AEscapeExpr]
end
# Visit a npropdef and:
# * Identify variables and labels
# * Associate each break and continue to its escapemark
# * Transform `ACallFormExpr` that access a variable into `AVarFormExpr`
# FIXME: Should the class be private?
private class ScopeVisitor
super Visitor
# The tool context used to display errors
var toolcontext: ToolContext
# The analysed property
var propdef: APropdef
var selfvariable = new Variable("self")
init
do
scopes.add(new Scope)
end
# All stacked scope. `scopes.first` is the current scope
var scopes = new List[Scope]
# Shift and check the last scope
fun shift_scope
do
assert not scopes.is_empty
var scope = scopes.shift
for v in scope.variables.values do
if v.warn_unread then
toolcontext.advice(v.location, "unread-variable", "Warning: local variable {v.name} is never read.")
end
end
end
# Register a local variable.
# Display an error on toolcontext if a variable with the same name is masked.
fun register_variable(node: ANode, variable: Variable): Bool
do
var name = variable.name
var found = search_variable(name)
if found != null then
self.error(node, "Error: a variable named `{name}` already exists.")
return false
end
scopes.first.variables[name] = variable
variable.location = node.location
return true
end
# Look for a variable named `name`.
# Return null if no such a variable is found.
fun search_variable(name: String): nullable Variable
do
for scope in scopes do
var res = scope.get_variable(name)
if res != null then
return res
end
end
return null
end
redef fun visit(n)
do
n.accept_scope_visitor(self)
end
# Enter in a statement block `node` as inside a new scope.
# The block can be optionally attached to an `escapemark`.
fun enter_visit_block(node: nullable AExpr, escapemark: nullable EscapeMark)
do
if node == null then return
var scope = new Scope
scope.escapemark = escapemark
scopes.unshift(scope)
enter_visit(node)
shift_scope
end
# Look for a label `name`.
# Return null if no such a label is found.
fun search_label(name: String): nullable EscapeMark
do
for scope in scopes do
var res = scope.escapemark
if res != null and res.name == name then
return res
end
end
return null
end
# Create a new escape mark (possibly with a label)
# Display an error on toolcontext if a label with the same name is masked.
fun make_escape_mark(nlabel: nullable ALabel, for_loop: Bool): EscapeMark
do
var name: nullable String
if nlabel != null then
var nid = nlabel.n_id
if nid == null then
var res = search_label("")
if res != null then
self.error(nlabel, "Syntax Error: anonymous label already defined.")
end
name = ""
else
name = nid.text
var found = self.search_label(name)
if found != null then
self.error(nlabel, "Syntax Error: label `{name}` already defined.")
end
end
else
name = null
end
var res = new EscapeMark(name)
if for_loop then res.continue_mark = new EscapeMark(name)
return res
end
# Look for an escape mark optionally associated with a label.
# If a label is given, the escapemark of this label is returned.
# If there is no label, the nearest escapemark that is `for loop` is returned.
# If there is no valid escapemark, then an error is displayed ans null is returned.
# Return null if no such a label is found.
fun get_escapemark(node: ANode, nlabel: nullable ALabel): nullable EscapeMark
do
if nlabel != null then
var nid = nlabel.n_id
if nid == null then
var res = search_label("")
if res == null then
self.error(nlabel, "Syntax Error: invalid anonymous label.")
node.is_broken = true
return null
end
return res
end
var name = nid.text
var res = search_label(name)
if res == null then
self.error(nlabel, "Syntax Error: invalid label `{name}`.")
node.is_broken = true
return null
end
return res
else
for scope in scopes do
var res = scope.escapemark
if res != null then
return res
end
end
self.error(node, "Syntax Error: `break` statement outside block.")
return null
end
end
# Display an error
fun error(node: ANode, message: String)
do
self.toolcontext.error(node.hot_location, message)
node.is_broken = true
end
end
private class Scope
var variables = new HashMap[String, Variable]
var escapemark: nullable EscapeMark = null
fun get_variable(name: String): nullable Variable
do
if self.variables.has_key(name) then
return self.variables[name]
else
return null
end
end
end
redef class ANode
private fun accept_scope_visitor(v: ScopeVisitor)
do
visit_all(v)
end
end
redef class APropdef
# The break escape mark associated with the return
var return_mark: nullable EscapeMark
# Entry point of the scope analysis
fun do_scope(toolcontext: ToolContext)
do
var v = new ScopeVisitor(toolcontext, self)
v.enter_visit(self)
v.shift_scope
end
end
redef class AParam
# The variable associated with the parameter
var variable: nullable Variable
redef fun accept_scope_visitor(v)
do
if variable != null then
v.register_variable(self.n_id, variable.as(not null))
return
end
super
var nid = self.n_id
var variable = new Variable(nid.text)
v.register_variable(nid, variable)
self.variable = variable
end
end
redef class AVardeclExpr
# The variable associated with the variable declaration
var variable: nullable Variable
redef fun accept_scope_visitor(v)
do
super
var nid = self.n_id
var variable = new Variable(nid.text)
v.register_variable(nid, variable)
variable.warn_unread = true # wait for some read mark.
self.variable = variable
end
end
redef class ASelfExpr
# The variable associated with the self receiver
var variable: nullable Variable
redef fun accept_scope_visitor(v)
do
super
self.variable = v.selfvariable
end
end
redef class AEscapeExpr
# The escape mark associated with the break/continue
var escapemark: nullable EscapeMark
end
redef class AContinueExpr
redef fun accept_scope_visitor(v)
do
super
var escapemark = v.get_escapemark(self, self.n_label)
if escapemark == null then return # Skip error
escapemark = escapemark.continue_mark
if escapemark == null then
v.error(self, "Error: cannot 'continue', only 'break'.")
return
end
escapemark.escapes.add(self)
self.escapemark = escapemark
end
end
redef class ABreakExpr
redef fun accept_scope_visitor(v)
do
super
var escapemark = v.get_escapemark(self, self.n_label)
if escapemark == null then return # Skip error
escapemark.escapes.add(self)
self.escapemark = escapemark
end
end
redef class AReturnExpr
redef fun accept_scope_visitor(v)
do
super
var escapemark = v.propdef.return_mark
if escapemark == null then
escapemark = new EscapeMark
v.propdef.return_mark = escapemark
end
escapemark.escapes.add(self)
self.escapemark = escapemark
end
end
redef class ADoExpr
# The break escape mark associated with the 'do' block
var break_mark: nullable EscapeMark
redef fun accept_scope_visitor(v)
do
self.break_mark = v.make_escape_mark(n_label, false)
v.enter_visit_block(n_block, self.break_mark)
v.enter_visit_block(n_catch)
end
end
redef class AIfExpr
redef fun accept_scope_visitor(v)
do
v.enter_visit(n_expr)
v.enter_visit_block(n_then, null)
v.enter_visit_block(n_else, null)
end
end
redef class AWhileExpr
# The break escape mark associated with the 'while'
var break_mark: nullable EscapeMark
# The continue escape mark associated with the 'while'
var continue_mark: nullable EscapeMark
redef fun accept_scope_visitor(v)
do
var escapemark = v.make_escape_mark(n_label, true)
self.break_mark = escapemark
self.continue_mark = escapemark.continue_mark
v.enter_visit(n_expr)
v.enter_visit_block(n_block, escapemark)
end
end
redef class ALoopExpr
# The break escape mark associated with the 'loop'
var break_mark: nullable EscapeMark
# The continue escape mark associated with the 'loop'
var continue_mark: nullable EscapeMark
redef fun accept_scope_visitor(v)
do
var escapemark = v.make_escape_mark(n_label, true)
self.break_mark = escapemark
self.continue_mark = escapemark.continue_mark
v.enter_visit_block(n_block, escapemark)
end
end
redef class AForExpr
# The break escape mark associated with the 'for'
var break_mark: nullable EscapeMark
# The continue escape mark associated with the 'for'
var continue_mark: nullable EscapeMark
redef fun accept_scope_visitor(v)
do
for g in n_groups do
v.enter_visit(g.n_expr)
end
# Protect automatic variables
v.scopes.unshift(new Scope)
for g in n_groups do
# Create the automatic variables
var variables = new Array[Variable]
g.variables = variables
for nid in g.n_ids do
var va = new Variable(nid.text)
v.register_variable(nid, va)
variables.add(va)
end
end
var escapemark = v.make_escape_mark(n_label, true)
self.break_mark = escapemark
self.continue_mark = escapemark.continue_mark
v.enter_visit_block(n_block, escapemark)
v.shift_scope
end
end
redef class AForGroup
# The automatic variables in order
var variables: nullable Array[Variable]
end
redef class AWithExpr
# The break escape mark associated with the 'with'
var break_mark: nullable EscapeMark
redef fun accept_scope_visitor(v)
do
v.scopes.unshift(new Scope)
var escapemark = v.make_escape_mark(n_label, true)
self.break_mark = escapemark
v.enter_visit(n_expr)
v.enter_visit_block(n_block, escapemark)
v.shift_scope
end
end
redef class AAssertExpr
redef fun accept_scope_visitor(v)
do
v.enter_visit(n_expr)
v.enter_visit_block(n_else, null)
end
end
redef class AVarFormExpr
# The associated variable
var variable: nullable Variable is writable
end
redef class ACallFormExpr
redef fun accept_scope_visitor(v)
do
if n_expr isa AImplicitSelfExpr then
var name = n_qid.n_id.text
var variable = v.search_variable(name)
if variable != null then
var n: AExpr
if not n_args.n_exprs.is_empty or n_args isa AParExprs or self isa ACallrefExpr then
v.error(self, "Error: `{name}` is a variable, not a method.")
return
end
n = variable_create(variable)
n.variable = variable
replace_with(n)
n.accept_scope_visitor(v)
return
end
end
super
end
# Create a variable access corresponding to the call form
private fun variable_create(variable: Variable): AVarFormExpr is abstract
end
redef class ACallExpr
redef fun variable_create(variable)
do
variable.warn_unread = false
return new AVarExpr.init_avarexpr(n_qid.n_id)
end
end
redef class ACallAssignExpr
redef fun variable_create(variable)
do
return new AVarAssignExpr.init_avarassignexpr(n_qid.n_id, n_assign, n_value)
end
end
redef class ACallReassignExpr
redef fun variable_create(variable)
do
variable.warn_unread = false
return new AVarReassignExpr.init_avarreassignexpr(n_qid.n_id, n_assign_op, n_value)
end
end
redef class ALambdaExpr
redef fun accept_scope_visitor(v)
do
# TODO
return
end
end
src/semantize/scope.nit:17,1--538,3