1from collections import namedtuple
2
3from ..common.info import ID, UNKNOWN
4from ..common.util import classonly, _NTBase
5
6
7def normalize_vartype(vartype):
8    """Return the canonical form for a variable type (or func signature)."""
9    # We allow empty strring through for semantic reasons.
10    if vartype is None:
11        return None
12
13    # XXX finish!
14    # XXX Return (modifiers, type, pointer)?
15    return str(vartype)
16
17
18# XXX Variable.vartype -> decl (Declaration).
19
20class Variable(_NTBase,
21               namedtuple('Variable', 'id storage vartype')):
22    """Information about a single variable declaration."""
23
24    __slots__ = ()
25
26    STORAGE = (
27            'static',
28            'extern',
29            'implicit',
30            'local',
31            )
32
33    @classonly
34    def from_parts(cls, filename, funcname, name, decl, storage=None):
35        varid = ID(filename, funcname, name)
36        if storage is None:
37            self = cls.from_id(varid, decl)
38        else:
39            self = cls(varid, storage, decl)
40        return self
41
42    @classonly
43    def from_id(cls, varid, decl):
44        from ..parser.declarations import extract_storage
45        storage = extract_storage(decl, infunc=varid.funcname)
46        return cls(varid, storage, decl)
47
48    def __new__(cls, id, storage, vartype):
49        self = super().__new__(
50                cls,
51                id=ID.from_raw(id),
52                storage=str(storage) if storage else None,
53                vartype=normalize_vartype(vartype) if vartype else None,
54                )
55        return self
56
57    def __hash__(self):
58        return hash(self.id)
59
60    def __getattr__(self, name):
61        return getattr(self.id, name)
62
63    def _validate_id(self):
64        if not self.id:
65            raise TypeError('missing id')
66
67        if not self.filename or self.filename == UNKNOWN:
68            raise TypeError(f'id missing filename ({self.id})')
69
70        if self.funcname and self.funcname == UNKNOWN:
71            raise TypeError(f'id missing funcname ({self.id})')
72
73        self.id.validate()
74
75    def validate(self):
76        """Fail if the object is invalid (i.e. init with bad data)."""
77        self._validate_id()
78
79        if self.storage is None or self.storage == UNKNOWN:
80            raise TypeError('missing storage')
81        elif self.storage not in self.STORAGE:
82            raise ValueError(f'unsupported storage {self.storage:r}')
83
84        if self.vartype is None or self.vartype == UNKNOWN:
85            raise TypeError('missing vartype')
86
87    @property
88    def isglobal(self):
89        return self.storage != 'local'
90
91    @property
92    def isconst(self):
93        return 'const' in self.vartype.split()
94