""" Module for scoping manipulation.
"""
# scope for type checking
import weakref
from collections import *
from pyrser.type_system.symbol import *
from pyrser.type_system.signature import *
from pyrser.type_system.evalctx import *
from pyrser.type_system.translator import *
from pyrser.parsing.node import *
from pyrser.passes.to_yml import *
StateScope = meta.enum('FREE', 'LINKED', 'EMBEDDED')
# forward just for annotation (not the same id that final type)
class Scope:
pass
[docs]class Scope(Symbol):
""" Scope of Signature for a Scope/namespace/type etc...
Basic abstraction of type checking.
Scope is not a 'pure' python set but something between a set and a dict...
"""
[docs] def __str__(self) -> str:
import pyrser.type_system.to_fmt
return str(self.to_fmt())
[docs] def __init__(self, name: str=None, sig: [Signature]=None,
state=StateScope.FREE, is_namespace=True):
""" Unnamed scope for global scope
A Scope have basically 3 possibles states:
FREE: it's a standalone Scope, generally the global Scope
LINKED: the Scope is related to another Scope for type resolution,
like compound statement
EMBEDDED: the Scope was added into another Scope,
it forwards all 'in' calls...like namespacing
A Scope could or couldn't act like a namespace.
All Signature added into a 'namespaced' Scope was prefixed
by the Scope name.
"""
if name is not None and not isinstance(name, str):
raise TypeError("name must be a str object.")
super().__init__(name)
# during typing, this scope need or not feedback pass
self.need_feedback = False
# state of the scope
self.state = state
self.is_namespace = is_namespace
# internal mapping for Type Conversion
self.mapTypeTranslate = MapSourceTranslate()
# AST Translator Injector use to add Translator in AST
# def astTranslatorInjector(old: Node, trans: Translator,
# d: Diagnostic, li: LocationInfo) -> Node
self.astTranslatorInjector = None
# TODO: ...could be unusable
self._ntypes = 0
self._nvars = 0
self._nfuns = 0
# internal store of Signature
self._hsig = {}
if sig is not None:
if isinstance(sig, Signature) or isinstance(sig, Scope):
self.add(sig)
elif len(sig) > 0:
self.update(sig)
[docs] def set_parent(self, parent: Scope) -> object:
Symbol.set_parent(self, parent)
if parent is not None:
self.mapTypeTranslate.set_parent(parent.mapTypeTranslate)
if hasattr(self, 'state') and self.state == StateScope.FREE:
self.state = StateScope.LINKED
return self
[docs] def set_name(self, name: str):
""" You could set the name after construction """
self.name = name
# update internal names
lsig = self._hsig.values()
self._hsig = {}
for s in lsig:
self._hsig[s.internal_name()] = s
[docs] def count_types(self) -> int:
""" Count subtypes """
n = 0
for s in self._hsig.values():
if type(s).__name__ == 'Type':
n += 1
return n
[docs] def count_vars(self) -> int:
""" Count var define by this scope """
n = 0
for s in self._hsig.values():
if hasattr(s, 'is_var') and s.is_var():
n += 1
return n
[docs] def count_funs(self) -> int:
""" Count function define by this scope """
n = 0
for s in self._hsig.values():
if hasattr(s, 'is_fun') and s.is_fun():
n += 1
return n
def __update_count(self):
""" Update internal counters """
self._ntypes = self.count_types()
self._nvars = self.count_vars()
self._nfuns = self.count_funs()
[docs] def __repr__(self) -> str:
""" Internal representation """
return repr(self._hsig)
[docs] def __getstate__(self):
""" For pickle, we don't handle weakrefs... """
state = self.__dict__.copy()
del state['parent']
return state
## TODO: __getitem__, __setitem__, __delete__??
# ======== SET OPERATORS OVERLOADING ========
# in
[docs] def __contains__(self, s: Signature) -> bool:
""" check if a Signature or a Type is declared in a Scope """
# fail if in global
from pyrser.type_system.type import Type
found = False
txt = ""
if isinstance(s, Signature):
txt = s.internal_name()
elif isinstance(s, Type):
txt = s.type_name
elif isinstance(s, str):
txt = s
found = (txt in self._hsig)
if not found and self.parent is not None:
if self.state != StateScope.FREE and isinstance(s, Type):
found = (s.type_name in self.parent())
if self.state == StateScope.EMBEDDED:
found = (txt in self.parent())
return found
# |=
[docs] def __ior__(self, sig: list or Scope) -> Scope:
""" \|= operator """
return self.update(sig)
[docs] def update(self, sig: list or Scope) -> Scope:
""" Update the Set with values of another Set """
values = sig
if hasattr(sig, 'values'):
values = sig.values()
for s in values:
if self.is_namespace:
s.set_parent(self)
if isinstance(s, Scope):
s.state = StateScope.EMBEDDED
self._hsig[s.internal_name()] = s
self.__update_count()
return self
# |
[docs] def __or__(self, sig: Scope) -> Scope:
""" | operator """
return self.union(sig)
[docs] def union(self, sig: Scope) -> Scope:
""" Create a new Set produce by the union of 2 Set """
new = Scope(sig=self._hsig.values(), state=self.state)
new |= sig
return new
# &=
[docs] def __iand__(self, sig: Scope) -> Scope:
""" &= operator """
return self.intersection_update(sig)
[docs] def intersection_update(self, oset: Scope) -> Scope:
""" Update Set with common values of another Set """
keys = list(self._hsig.keys())
for k in keys:
if k not in oset:
del self._hsig[k]
else:
self._hsig[k] = oset.get(k)
return self
# &
[docs] def __and__(self, sig: Scope) -> Scope:
""" & operator """
return self.intersection(sig)
[docs] def intersection(self, sig: Scope) -> Scope:
""" Create a new Set produce by the intersection of 2 Set """
new = Scope(sig=self._hsig.values(), state=self.state)
new &= sig
return new
# -=
[docs] def __isub__(self, sig: Scope) -> Scope:
""" -= operator """
return self.difference_update(sig)
[docs] def difference_update(self, oset: Scope) -> Scope:
""" Remove values common with another Set """
keys = list(self._hsig.keys())
for k in keys:
if k in oset:
del self._hsig[k]
return self
# -
[docs] def __sub__(self, sig: Scope) -> Scope:
""" - operator """
return self.difference(sig)
[docs] def difference(self, sig: Scope) -> Scope:
""" Create a new Set produce by a Set subtracted by another Set """
new = Scope(sig=self._hsig.values(), state=self.state)
new -= sig
return new
# ^=
[docs] def __ixor__(self, sig: Scope) -> Scope:
""" ^= operator """
return self.symmetric_difference_update(sig)
[docs] def symmetric_difference_update(self, oset: Scope) -> Scope:
""" Remove common values
and Update specific values from another Set
"""
skey = set()
keys = list(self._hsig.keys())
for k in keys:
if k in oset:
skey.add(k)
for k in oset._hsig.keys():
if k not in skey:
self._hsig[k] = oset.get(k)
for k in skey:
del self._hsig[k]
return self
# ^
[docs] def __xor__(self, sig: Scope) -> Scope:
""" ^ operator """
return self.symmetric_difference(sig)
[docs] def symmetric_difference(self, sig: Scope) -> Scope:
""" Create a new Set with values present in only one Set """
new = Scope(sig=self._hsig.values(), state=self.state)
new ^= sig
return new
# ======== SCOPE MANIPULATION ========
[docs] def add(self, it: Signature) -> bool:
""" Add it to the Set """
if isinstance(it, Scope):
it.state = StateScope.EMBEDDED
txt = it.internal_name()
it.set_parent(self)
if self.is_namespace:
txt = it.internal_name()
if txt == "":
txt = '_' + str(len(self._hsig))
if txt in self._hsig:
raise KeyError("Already exists %s" % txt)
self._hsig[txt] = it
self.__update_count()
return True
[docs] def remove(self, it: Signature) -> bool:
""" Remove it but raise KeyError if not found """
txt = it.internal_name()
if txt not in self._hsig:
raise KeyError(it.show_name() + ' not in Set')
sig = self._hsig[txt]
if isinstance(sig, Scope):
sig.state = StateScope.LINKED
del self._hsig[txt]
return True
[docs] def discard(self, it: Signature) -> bool:
""" Remove it only if present """
txt = it.internal_name()
if txt in self._hsig:
sig = self._hsig[txt]
if isinstance(sig, Scope):
sig.state = StateScope.LINKED
del self._hsig[txt]
return True
return False
[docs] def clear(self) -> bool:
""" Clear all signatures in the Set """
self._hsig.clear()
return True
[docs] def pop(self) -> Signature:
""" Pop a random Signature """
return self._hsig.popitem()
# ======== SCOPE ITERATION ========
[docs] def __len__(self) -> int:
""" Len of the Set """
return len(self._hsig)
[docs] def values(self) -> [Signature]:
""" Retrieve all values """
if self.state == StateScope.EMBEDDED and self.parent is not None:
return list(self._hsig.values()) + list(self.parent().values())
else:
return self._hsig.values()
[docs] def keys(self) -> [str]:
""" Retrieve all keys """
return self._hsig.keys()
# ======== SCOPE QUERY ========
[docs] def first(self) -> Signature:
""" Retrieve the first Signature ordered by mangling descendant """
k = sorted(self._hsig.keys())
return self._hsig[k[0]]
[docs] def last(self) -> Signature:
""" Retrieve the last Signature ordered by mangling descendant """
k = sorted(self._hsig.keys())
return self._hsig[k[-1]]
[docs] def get(self, key: str, default=None) -> Signature:
""" Get a signature instance by its internal_name """
item = default
if key in self._hsig:
item = self._hsig[key]
return item
[docs] def get_by_symbol_name(self, name: str) -> Scope:
""" Retrieve a Set of all signature by symbol name """
lst = []
for s in self.values():
if s.name == name:
# create an EvalCtx only when necessary
lst.append(EvalCtx.from_sig(s))
# include parent
# TODO: see all case of local redefinition for
# global overloads
# possible algos... take all with different internal_name
if len(lst) == 0:
p = self.get_parent()
if p is not None:
return p.get_by_symbol_name(name)
rscope = Scope(sig=lst, state=StateScope.LINKED, is_namespace=False)
# inherit type/translation from parent
rscope.set_parent(self)
return rscope
[docs] def getsig_by_symbol_name(self, name: str) -> Signature:
""" Retrieve the unique Signature of a symbol.
Fail if the Signature is not unique
"""
subscope = self.get_by_symbol_name(name)
if len(subscope) != 1:
raise KeyError("%s have multiple candidates in scope" % name)
v = list(subscope.values())
return v[0]
[docs] def get_by_return_type(self, tname: str) -> Scope:
""" Retrieve a Set of all signature by (return) type """
lst = []
for s in self.values():
if hasattr(s, 'tret') and s.tret == tname:
lst.append(EvalCtx.from_sig(s))
rscope = Scope(sig=lst, state=StateScope.LINKED, is_namespace=False)
# inherit type/translation from parent
rscope.set_parent(self)
return rscope
[docs] def get_all_polymorphic_return(self) -> bool:
""" For now, polymorphic return type are handle by symbol artefact.
--> possible multi-polymorphic but with different constraint attached!
"""
lst = []
for s in self.values():
if hasattr(s, 'tret') and s.tret.is_polymorphic:
# encapsulate s into a EvalCtx for meta-var resolution
lst.append(EvalCtx.from_sig(s))
rscope = Scope(sig=lst, state=StateScope.LINKED, is_namespace=False)
# inherit type/translation from parent
rscope.set_parent(self)
return rscope
[docs] def get_by_params(self, params: [Scope]) -> (Scope, [[Scope]]):
""" Retrieve a Set of all signature that match the parameter list.
Return a pair:
pair[0] the overloads for the functions
pair[1] the overloads for the parameters
(a list of candidate list of parameters)
"""
lst = []
scopep = []
# for each of our signatures
for s in self.values():
# for each params of this signature
if hasattr(s, 'tparams'):
# number of matched params
mcnt = 0
# temporary collect
nbparam_sig = len(s.tparams)
nbparam_candidates = len(params)
# don't treat signature too short
if nbparam_sig > nbparam_candidates:
continue
# don't treat call signature too long if not variadic
if nbparam_candidates > nbparam_sig and not s.variadic:
continue
tmp = [None] * nbparam_candidates
variadic_types = []
for i in range(nbparam_candidates):
tmp[i] = Scope(state=StateScope.LINKED)
tmp[i].set_parent(self)
# match param of the expr
if i < nbparam_sig:
if params[i].state == StateScope.EMBEDDED:
raise ValueError(
("params[%d] of get_by_params is a StateScope."
+ "EMBEDDED scope... "
+ "read the doc and try a StateScope.FREE"
+ " or StateScope.LINKED.") % i
)
m = params[i].get_by_return_type(s.tparams[i])
if len(m) > 0:
mcnt += 1
tmp[i].update(m)
else:
# co/contra-variance
# we just need to search a t1->t2
# and add it into the tree (with/without warnings)
t1 = params[i]
t2 = s.tparams[i]
# if exist a fun (t1) -> t2
(is_convertible,
signature,
translator
) = t1.findTranslationTo(t2)
if is_convertible:
# add a translator in the EvalCtx
signature.use_translator(translator)
mcnt += 1
nscope = Scope(
sig=[signature],
state=StateScope.LINKED,
is_namespace=False
)
nscope.set_parent(self)
tmp[i].update(nscope)
elif s.tparams[i].is_polymorphic:
# handle polymorphic parameter
mcnt += 1
if not isinstance(params[i], Scope):
raise Exception(
"params[%d] must be a Scope" % i
)
tmp[i].update(params[i])
else:
# handle polymorphic return type
m = params[i].get_all_polymorphic_return()
if len(m) > 0:
mcnt += 1
tmp[i].update(m)
# for variadic extra parameters
else:
mcnt += 1
if not isinstance(params[i], Scope):
raise Exception("params[%d] must be a Scope" % i)
variadic_types.append(params[i].first().tret)
tmp[i].update(params[i])
# we have match all candidates
if mcnt == len(params):
# select this signature but
# box it (with EvalCtx) for type resolution
lst.append(EvalCtx.from_sig(s))
lastentry = lst[-1]
if lastentry.variadic:
lastentry.use_variadic_types(variadic_types)
scopep.append(tmp)
rscope = Scope(sig=lst, state=StateScope.LINKED, is_namespace=False)
# inherit type/translation from parent
rscope.set_parent(self)
return (rscope, scopep)
[docs] def addTranslator(self, val: Translator, as_global=False):
return self.mapTypeTranslate.addTranslator(val, as_global=as_global)
[docs] def addTranslatorInjector(self, ast_method):
""" Could be redefine in a subscope """
if self.astTranslatorInjector is not None:
raise TypeError("Already define a translator injector")
self.astTranslatorInjector = ast_method
[docs] def callInjector(self, old: Node, trans: Translator) -> Node:
""" If don't have injector call from parent """
if self.astTranslatorInjector is None:
if self.parent is not None:
# TODO: think if we forward for all StateScope
# forward to parent scope
return self.parent().callInjector(old, trans)
else:
raise TypeError("Must define an Translator Injector")
return self.astTranslatorInjector(old, trans)
[docs] def findTranslationTo(self, t2: str) -> (bool, Signature, Translator):
""" Find an arrow (->)
aka a function able to translate something to t2
"""
if not t2.is_polymorphic:
collect = []
for s in self.values():
t1 = s.tret
if t1.is_polymorphic:
continue
if (s.tret in self.mapTypeTranslate):
if (t2 in self.mapTypeTranslate[t1]):
collect.append((
True,
s,
self.mapTypeTranslate[t1][t2]
))
# if len > 1 too many candidates
if len(collect) == 1:
return collect[0]
return (False, None, None)