import syntax_base
-# Associate symbols to variable and variables to type
+# Associate symbols to variable
# Can be nested
-abstract class VariableContext
+abstract class ScopeContext
# Look for the variable from its name
# Return null if nothing found
- meth [](s: Symbol): Variable
+ fun [](s: Symbol): nullable Variable
do
if _dico.has_key(s) then
return _dico[s]
end
# Register a new variable with its name
- meth add(v: Variable)
+ fun add(v: Variable)
do
+ var old_var = self[v.name]
+ if old_var != null then
+ _visitor.error(v.decl, "Error: '{v}' already defined at {old_var.decl.location.relative_to(v.decl.location)}.")
+ end
_dico[v.name] = v
_all_variables.add(v)
end
- meth mark_is_set(v: Variable)
+ # Build a new ScopeContext
+ fun sub(node: ANode): ScopeContext
+ do
+ return new SubScopeContext.with_prev(self, node)
+ end
+
+ # Variables by name (in the current context only)
+ var _dico: Map[Symbol, Variable] = new HashMap[Symbol, Variable]
+
+ # All variables in all contextes
+ var _all_variables: Set[Variable]
+
+ # The visitor of the context (used to display error)
+ var _visitor: AbsSyntaxVisitor
+
+ # The syntax node that introduced the context
+ readable var _node: ANode
+
+ init(visitor: AbsSyntaxVisitor, node: ANode)
+ do
+ _visitor = visitor
+ _node = node
+ end
+end
+
+# Root of a variable scope context hierarchy
+class RootScopeContext
+special ScopeContext
+ init(visitor: AbsSyntaxVisitor, node: ANode)
+ do
+ super(visitor, node)
+ _all_variables = new HashSet[Variable]
+ end
+end
+
+# Contexts that can see local variables of a prevous context
+# Local variables added to this context are not shared with the previous context
+class SubScopeContext
+special ScopeContext
+ readable var _prev: ScopeContext
+
+ redef fun [](s)
+ do
+ if _dico.has_key(s) then
+ return _dico[s]
+ else
+ return prev[s]
+ end
+ end
+
+ init with_prev(p: ScopeContext, node: ANode)
do
- _set_variables.add(v)
+ init(p._visitor, node)
+ _prev = p
+ _all_variables = p._all_variables
end
+end
- meth check_is_set(n: PNode, v: Variable)
+#################################################################
+
+# All-in-one context for flow control.
+# It features:
+# * reachability
+# * set/unset variable
+# * adaptive type of variable
+# FlowContextes are imutables, new contexts are created:
+# * as an empty root context
+# * as the adaptation of a existing context (see methods sub_*)
+# * as the merge of existing contexts
+abstract class FlowContext
+ # Display an error localised on `n' if the variable `v' is not set
+ fun check_is_set(n: ANode, v: Variable)
do
if v.must_be_set and not is_set(v) then
_visitor.error(n, "Error: variable '{v}' is possibly unset.")
- var x = self
- while true do
- print " {x.node.locate}: {x._set_variables.join(", ")} ; {x._dico.join(", ")}"
- var x0 = x
- if x0 isa SubVariableContext then
- x = x0.prev
- else
- break
- end
- end
end
end
# The effective static type of a given variable
# May be different from the declaration static type
- meth stype(v: Variable): MMType
+ fun stype(v: Variable): nullable MMType
do
- if _stypes.has_key(v) then
- return _stypes[v]
- else
- return v.stype
- end
+ return v.stype
end
- # Set effective static type of a given variable
- # May be different from the declaration static type
- meth stype=(v: Variable, t: MMType)
+ # Return a context where the variable is marked as set
+ fun sub_setvariable(v: Variable): FlowContext
do
- _stypes[v] = t
+ var ctx = new SubFlowContext.with_prev(self, node)
+ ctx._set_variables.add(v)
+ return ctx
end
- # Variables by name (in the current context only)
- attr _dico: Map[Symbol, Variable]
-
- # All variables in all contextes
- attr _all_variables: Set[Variable]
+ # Return a context where unreash == true
+ fun sub_unreash(node: ANode): FlowContext
+ do
+ var ctx = new SubFlowContext.with_prev(self, node)
+ ctx._unreash = true
+ return ctx
+ end
- # Updated static type of variables
- attr _stypes: Map[Variable, MMType] = new HashMap[Variable, MMType]
+ # Return a context where v is casted as t
+ fun sub_with(node: ANode, v: Variable, t: MMType): FlowContext
+ do
+ return new CastFlowContext(self, node, v, t)
+ end
- # Build a new VariableContext
- meth sub(node: PNode): SubVariableContext
+ # Merge various alternative contexts (all must be reashable)
+ # Note that self can belong to alternatives
+ # Base is self
+ fun merge(node: ANode, alternatives: Array[FlowContext]): FlowContext
do
- return new SubVariableContext.with_prev(self, node)
+ for a in alternatives do assert not a.unreash
+ if alternatives.length == 1 then return alternatives.first
+ return new MergeFlowContext(self, node, alternatives)
end
- # Build a nested VariableContext with new variable information
- meth sub_with(node: PNode, v: Variable, t: MMType): SubVariableContext
+ # Merge only context that are reachable
+ # Used for if/then/else merges
+ # Base is self
+ fun merge_reash(node: ANode, alt1, alt2: FlowContext): FlowContext
do
- var ctx = sub(node)
- ctx.stype(v) = t
- return ctx
+ if alt1.unreash then
+ if alt2.unreash then
+ return self.sub_unreash(node)
+ else
+ var t = alt2
+ alt2 = alt1
+ alt1 = t
+ end
+ end
+
+ if alt2.unreash or alt1 == alt2 then
+ return alt1
+ #if alt1 == self then
+ # return self
+ #else
+ # return merge(node, [alt1])
+ #end
+ else
+ return merge(node, [alt1, alt2])
+ end
end
# The visitor of the context (used to display error)
- attr _visitor: AbsSyntaxVisitor
+ var _visitor: AbsSyntaxVisitor
# The syntax node that introduced the context
- readable attr _node: PNode
+ readable var _node: ANode
- init(visitor: AbsSyntaxVisitor, node: PNode)
+ init(visitor: AbsSyntaxVisitor, node: ANode)
do
_visitor = visitor
_node = node
- _dico = new HashMap[Symbol, Variable]
end
# Is a control flow break met? (return, break, continue)
- readable writable attr _unreash: Bool = false
+ readable var _unreash: Bool = false
# Is a control flow already broken?
# Used to avoid repeating the same error message
- readable writable attr _already_unreash: Bool = false
+ readable writable var _already_unreash: Bool = false
# Set of variable that are set (assigned)
- readable attr _set_variables: HashSet[Variable] = new HashSet[Variable]
+ readable var _set_variables: HashSet[Variable] = new HashSet[Variable]
# Is a variable set?
- meth is_set(v: Variable): Bool
+ fun is_set(v: Variable): Bool
do
return _set_variables.has(v)
end
+end
- # Merge back one flow context information
- meth merge(ctx: VariableContext)
+# Root of a variable context hierarchy
+class RootFlowContext
+special FlowContext
+ init(visitor: AbsSyntaxVisitor, node: ANode)
do
- if ctx.unreash then
- unreash = true
- if ctx.already_unreash then already_unreash = true
- return
- end
- for v in _all_variables do
- if not is_set(v) and ctx.is_set(v) then
- mark_is_set(v)
- end
- var s = stype(v)
- var s1 = ctx.stype(v)
- if s1 != s then stype(v) = s1
- end
+ super(visitor, node)
end
+end
- # Merge back two alternative flow context informations
- meth merge2(ctx1, ctx2, basectx: VariableContext)
- do
- if ctx1.unreash then
- merge(ctx2)
- else if ctx2.unreash then
- merge(ctx1)
- end
- for v in _all_variables do
- if not is_set(v) and ctx1.is_set(v) and ctx2.is_set(v) then
- mark_is_set(v)
- end
+# Contexts that are an evolution of a single previous context
+class SubFlowContext
+special FlowContext
+ readable var _prev: FlowContext
- var s = stype(v)
- var s1 = ctx1.stype(v)
- var s2 = ctx2.stype(v)
- if s1 == s and s2 == s then
- # NOP
- else if s1 == s2 then
- stype(v) = s1
- else if s1 < s2 then
- stype(v) = s2
- else if s2 < s1 then
- stype(v) = s1
- else
- stype(v) = basectx.stype(v)
- end
- end
+ redef fun is_set(v)
+ do
+ return _set_variables.has(v) or _prev.is_set(v)
end
- redef meth to_s
+ redef fun stype(v)
do
- var s = new Buffer
- s.append(node.locate)
- for v in _all_variables do
- var t = stype(v)
- if t == null then continue
- s.append(" {v}:{t}")
- end
- return s.to_s
+ return prev.stype(v)
end
-end
-class RootVariableContext
-special VariableContext
- init(visitor: AbsSyntaxVisitor, node: PNode)
+ init with_prev(p: FlowContext, node: ANode)
do
- super(visitor, node)
- _all_variables = new HashSet[Variable]
+ init(p._visitor, node)
+ _prev = p
end
end
-class SubVariableContext
-special VariableContext
- readable attr _prev: VariableContext
+# A variable context where a variable got a type adptation
+class CastFlowContext
+special SubFlowContext
+ # The casted variable
+ var _variable: Variable
- redef meth [](s)
+ # The new static type of the variable
+ var _stype: nullable MMType
+
+ redef fun stype(v)
do
- if _dico.has_key(s) then
- return _dico[s]
+ if v == _variable then
+ return _stype
else
- return prev[s]
+ return prev.stype(v)
end
end
- redef meth stype(v)
+ init(p: FlowContext, node: ANode, v: Variable, s: nullable MMType)
+ do
+ with_prev(p, node)
+ _variable = v
+ _stype = s
+ end
+end
+
+# Context that resulting from the combinaisons of other contexts.
+# Most of the merge computation are done lasily.
+class MergeFlowContext
+special FlowContext
+ var _base: FlowContext
+ var _alts: Array[FlowContext]
+
+ # Updated static type of variables
+ var _stypes: Map[Variable, nullable MMType] = new HashMap[Variable, nullable MMType]
+
+ init(base: FlowContext, node: ANode, alts: Array[FlowContext])
+ do
+ super(base._visitor, node)
+ _alts = alts
+ _base = base
+ end
+
+ redef fun stype(v)
do
if _stypes.has_key(v) then
return _stypes[v]
else
- return prev.stype(v)
+ var s = merge_stype(v)
+ _stypes[v] = s
+ return s
end
end
- init with_prev(p: VariableContext, node: PNode)
+ private fun merge_stype(v: Variable): nullable MMType
do
- init(p._visitor, node)
- _prev = p
- _all_variables = p._all_variables
+ var candidate: nullable MMType = null
+ var is_nullable = false
+ var same_candidate: nullable MMType = _alts.first.stype(v)
+ for ctx in _alts do
+ var t = ctx.stype(v)
+ if t == null then
+ return null
+ end
+ if t != same_candidate then
+ same_candidate = null
+ end
+ if t isa MMTypeNone then
+ is_nullable = true
+ continue
+ end
+ if t isa MMNullableType then
+ is_nullable = true
+ t = t.as_notnull
+ end
+ if candidate == null or candidate < t then
+ candidate = t
+ end
+ end
+ if same_candidate != null then
+ return same_candidate
+ end
+ if is_nullable then
+ if candidate == null then
+ return _visitor.type_none
+ else
+ candidate = candidate.as_nullable
+ end
+ end
+ if candidate == null then
+ return _base.stype(v)
+ else
+ for ctx in _alts do
+ var t = ctx.stype(v)
+ if not t < candidate then
+ return _base.stype(v)
+ end
+ end
+ end
+ return candidate
end
- redef meth is_set(v)
+ redef fun is_set(v)
do
- return _set_variables.has(v) or _prev.is_set(v)
+ if _set_variables.has(v) then
+ return true
+ else
+ for ctx in _alts do
+ if not ctx.is_set(v) then
+ return false
+ end
+ end
+ _set_variables.add(v)
+ return true
+ end
end
end
+
redef class Variable
# Is the variable must be set before being used ?
- meth must_be_set: Bool do return false
+ fun must_be_set: Bool do return false
end
redef class VarVariable
- redef meth must_be_set do return true
+ redef fun must_be_set do return true
end