#!/usr/bin/python3 # Slang AST import abc from . import sld from .lexer import * from utils import * DEFAULT_OLEVEL = 0 TAB_SIZE = 4 DEBUG_PRECEDENCE = False def warn(class_, msg, node, ns): if (ns.warnclasses and class_ not in ns.warnclasses): return logexception(Warning(f"{msg} \033[2m(at line {node.lineno}, offset {node.offset})\033[0m \033[8m({class_})\033[0m"), raw=True, once=True) def eval_literal(x): return eval(literal_repr(x)) def literal_repr(x): return (str if (isinstance(x, ASTNode)) else repr)(x) def literal_type(x): r = eval(str(x)) if (isinstance(r, str) and len(r) == 1 and re.match(r"'.+?'", x.strip())): return stdlib.char return type(r) def common_type(l, ns): # TODO r = Slist(Signature.build(i, ns) for i in l).uniquize() r.reverse(); r = r.uniquize() if (not r): return None r = [i for i in r if not isinstance(i, stdlib.auto)] if (len(r) > 1): raise TODO(r) return first(r) class ASTNode(ABCSlots): lineno: ... offset: ... flags: ... @abc.abstractmethod @init_defaults def __init__(self, *, lineno, offset, flags: paramset): self.lineno, self.offset, self.flags = lineno, offset, flags def __repr__(self): return f"<{self.typename} `{self.__str__()}' on line {self.lineno}, offset {self.offset}>" @abc.abstractmethod def __str__(self): return '' @abc.abstractclassmethod def build(cls, tl): if (not tl): raise SlSyntaxNoToken() def validate(self, ns): for i in allslots(self): v = getattr(self, i) if (isiterablenostr(v)): for jj, j in enumerate(v): if (hasattr(j, 'validate')): j.validate(ns) elif (hasattr(v, 'validate') and not isinstance(v, ASTCodeNode)): v.validate(ns) def optimize(self, ns): for i in allslots(self): v = getattr(self, i) if (isiterablenostr(v)): for jj, j in enumerate(v.copy()): if (hasattr(j, 'optimize')): r = j.optimize(ns) if (r is not None): v[jj] = r if (v[jj].flags.optimized_out): del v[jj] elif (hasattr(v, 'optimize') and not isinstance(v, ASTCodeNode)): r = v.optimize(ns) if (r is not None): setattr(self, i, r) if (getattr(self, i).flags.optimized_out): setattr(self, i, None) self.flags.optimized = 1 @classproperty def typename(cls): return cls.__name__[3:-4] @property def length(self): #dlog(max((getattr(self, i) for i in allslots(self) if hasattr(getattr(self, i), 'offset')), key=lambda x: (x.lineno, x.offset) return sum(getattr(self, i).length for i in allslots(self) if hasattr(getattr(self, i), 'length')) class ASTRootNode(ASTNode): code: ... def __init__(self, code, **kwargs): super().__init__(**kwargs) self.code = code def __repr__(self): return '' def __str__(self): return '' @classmethod def build(cls, name=None): code = (yield from ASTCodeNode.build([], name=name)) return cls(code, lineno=code.lineno, offset=code.offset) def validate(self, ns=None): if (ns is None): ns = Namespace(self.code.name) super().validate(ns) self.code.validate(ns) return ns def optimize(self, ns, level=DEFAULT_OLEVEL): ns.olevel = level super().optimize(ns) self.code.optimize(ns) class ASTCodeNode(ASTNode): nodes: ... name: ... def __init__(self, nodes, *, name='', **kwargs): super().__init__(**kwargs) self.nodes, self.name = nodes, name def __repr__(self): return f"""""" def __str__(self): return (S('\n').join(map(lambda x: x.join('\n\n') if ('\n' in x) else x, map(str, self.nodes))).indent().replace('\n\n\n', '\n\n').strip('\n').join('\n\n') if (self.nodes) else '').join('{}') @classmethod def build(cls, tl, *, name): if (tl): cdef = ASTSpecialNode.build(tl) if (cdef.special != '{'): raise SlSyntaxExpectedError("'{'", cdef) lineno, offset = cdef.lineno, cdef.offset else: lineno = offset = None yield name nodes = list() while (True): c = yield if (c is None): break if (lineno is None): lineno, offset = c.lineno, c.offset nodes.append(c) return cls(nodes, name=name, lineno=lineno, offset=offset) #def validate(self, ns): # super # for i in self.nodes: # i.validate(ns) def optimize(self, ns): for ii, i in enumerate(self.nodes): r = i.optimize(ns) if (r is not None): self.nodes[ii] = r self.nodes = [i for i in self.nodes if not i.flags.optimized_out] class ASTTokenNode(ASTNode): length: ... def __init__(self, **kwargs): super().__init__(**kwargs) self.length = sum(len(getattr(self, i)) for i in allslots(self) if isinstance(getattr(self, i, None), str)) @abc.abstractclassmethod def build(cls, tl): super().build(tl) off = int() for ii, i in enumerate(tl.copy()): if (i.typename == 'SPECIAL' and (i.token[0] == '#' or i.token == '\\')): del tl[ii-off]; off += 1 if (not tl): raise SlSyntaxEmpty() class ASTIdentifierNode(ASTTokenNode): identifier: ... def __init__(self, identifier, **kwargs): self.identifier = identifier super().__init__(**kwargs) def __str__(self): return str(self.identifier) @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset if (tl[0].typename != 'IDENTIFIER'): raise SlSyntaxExpectedError('IDENTIFIER', tl[0]) identifier = tl.pop(0).token return cls(identifier, lineno=lineno, offset=offset) class ASTKeywordNode(ASTTokenNode): keyword: ... def __init__(self, keyword, **kwargs): self.keyword = keyword super().__init__(**kwargs) def __str__(self): return str(self.keyword) @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset if (tl[0].typename != 'KEYWORD'): raise SlSyntaxExpectedError('KEYWORD', tl[0]) keyword = tl.pop(0).token return cls(keyword, lineno=lineno, offset=offset) class ASTLiteralNode(ASTTokenNode): literal: ... def __init__(self, literal, **kwargs): self.literal = literal super().__init__(**kwargs) def __str__(self): return str(self.literal) @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset if (tl[0].typename != 'LITERAL'): raise SlSyntaxExpectedError('LITERAL', tl[0]) literal = tl.pop(0).token return cls(literal, lineno=lineno, offset=offset) class ASTOperatorNode(ASTTokenNode): operator: ... def __init__(self, operator, **kwargs): self.operator = operator super().__init__(**kwargs) def __str__(self): return str(self.operator) @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset if (tl[0].typename != 'OPERATOR'): raise SlSyntaxExpectedError('OPERATOR', tl[0]) operator = tl.pop(0).token return cls(operator, lineno=lineno, offset=offset) class ASTUnaryOperatorNode(ASTOperatorNode): @classmethod def build(cls, tl): operator = super().build(tl) if (not isinstance(operator.operator, UnaryOperator)): raise SlSyntaxExpectedError('UnaryOperator', operator) operator.operator = UnaryOperator(operator.operator) return operator class ASTBinaryOperatorNode(ASTOperatorNode): @classmethod def build(cls, tl): operator = super().build(tl) if (not isinstance(operator.operator, BinaryOperator)): raise SlSyntaxExpectedError('BinaryOperator', operator) operator.operator = BinaryOperator(operator.operator) return operator class ASTSpecialNode(ASTTokenNode): special: ... def __init__(self, special, **kwargs): self.special = special super().__init__(**kwargs) def __str__(self): return str(self.special) @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset if (tl[0].typename != 'SPECIAL'): raise SlSyntaxExpectedError('SPECIAL', tl[0]) special = tl.pop(0).token if (special == '\\'): raise SlSyntaxEmpty() return cls(special, lineno=lineno, offset=offset) class ASTPrimitiveNode(ASTNode): pass class ASTValueNode(ASTPrimitiveNode): value: ... def __init__(self, value, **kwargs): super().__init__(**kwargs) self.value = value def __str__(self): return str(self.value) @classmethod def build(cls, tl, *, fcall=False, attrget=False): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset types = [*allsubclasses(ASTUnaryOperationNode), ASTFunccallNode, ASTItemgetNode, ASTAttrgetNode, ASTIdentifierNode, ASTLambdaNode, ASTLiteralNode, *allsubclasses(ASTLiteralStructNode)] if (fcall): types.remove(ASTFunccallNode); types.remove(ASTLambdaNode) # XXX lambda too? if (attrget): types.remove(ASTAttrgetNode) err = set() for i in types: tll = tl.copy() try: value = i.build(tll, **{'attrget': attrget} if (i in (ASTFunccallNode,)) else {}) except SlSyntaxExpectedError as ex: err.add(ex); continue except SlSyntaxException: continue else: tl[:] = tll; break else: raise SlSyntaxMultiExpectedError.from_list(err) return cls(value, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) if (isinstance(self.value, ASTIdentifierNode)): if (self.value.identifier not in ns): raise SlValidationNotDefinedError(self.value, self, scope=ns.scope) if (ns.values.get(self.value.identifier) is None): raise SlValidationError(f"{self.value.identifier} is not initialized", self.value, self, scope=ns.scope) def optimize(self, ns): super().optimize(ns) if (isinstance(self.value, ASTIdentifierNode) and ns.values.get(self.value) not in (None, ...)): self.value = ns.values[self.value] if (isinstance(ns.values[self.value], ASTNode)) else ASTLiteralNode(literal_repr(ns.values[self.value]), lineno=self.lineno, offset=self.offset) # TODO FIXME in functions class ASTItemgetNode(ASTPrimitiveNode): value: ... key: ... def __init__(self, value, key, **kwargs): super().__init__(**kwargs) self.value, self.key = value, key def __str__(self): return f"{self.value}[{self.key}]" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset value = ASTIdentifierNode.build(tl) # TODO: value/expr bracket = ASTSpecialNode.build(tl) if (bracket.special != '['): raise SlSyntaxExpectedError("'['", bracket) start = None stop = None step = None if (tl and not (tl[0].typename == 'SPECIAL' and tl[0].token == ':')): start = ASTExprNode.build(tl) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ':'): ASTSpecialNode.build(tl) if (tl and not (tl[0].typename == 'SPECIAL' and tl[0].token in ']:')): stop = ASTExprNode.build(tl) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ':'): ASTSpecialNode.build(tl) if (tl and not (tl[0].typename == 'SPECIAL' and tl[0].token == ']')): step = ASTExprNode.build(tl) key = slice(start, stop, step) else: key = start bracket = ASTSpecialNode.build(tl) if (bracket.special != ']'): raise SlSyntaxExpectedError("']'", bracket) return cls(value, key, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) valsig = Signature.build(self.value, ns) keysig = Signature.build(self.key, ns) if ((keysig, self.key) not in valsig.itemget): raise SlValidationError(f"`{valsig}' does not support itemget by key of type `{keysig}'", self.key, self, scope=ns.scope) class ASTAttrgetNode(ASTPrimitiveNode): value: ... optype: ... attr: ... def __init__(self, value, optype, attr, **kwargs): super().__init__(**kwargs) self.value, self.optype, self.attr = value, optype, attr def __str__(self): return f"{self.value}{self.optype}{self.attr}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset value = ASTValueNode.build(tl, attrget=True) optype = ASTSpecialNode.build(tl) if (optype.special not in attrops): raise SlSyntaxExpectedError(f"one of {attrops}", optype) attr = ASTIdentifierNode.build(tl) return cls(value, optype, attr, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) valsig = Signature.build(self.value, ns) if ((self.optype.special, self.attr.identifier) not in valsig.attrops): raise SlValidationError(f"`{valsig}' does not support attribute operation `{self.optype}' with attr `{self.attr}'", self.optype, self, scope=ns.scope) class ASTExprNode(ASTPrimitiveNode): @classmethod def build(cls, tl, *, fcall=False, attrget=False): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset for ii, p in enumerate(operators[::-1]): tll = tl.copy() try: value = ASTBinaryExprNode.build(tll, p) except SlSyntaxException: continue else: tl[:] = tll; return value for i in allsubclasses(ASTUnaryOperationNode): tll = tl.copy() try: value = i.build(tll) except SlSyntaxException: pass else: tl[:] = tll; return value tll = tl.copy() try: value = ASTUnaryExprNode.build(tll) except SlSyntaxException: pass else: tl[:] = tll; return value tll = tl.copy() try: value = ASTValueNode.build(tll, fcall=fcall, attrget=attrget) except SlSyntaxException as ex: pass else: tl[:] = tll; return value try: parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != '('): raise SlSyntaxExpectedError('Expr', parenthesis) parenthesized = list() lvl = 1 while (tl): if (tl[0].typename == 'SPECIAL'): lvl += 1 if (tl[0].token == '(') else -1 if (tl[0].token == ')') else 0 if (lvl == 0): parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) break assert (lvl > 0) parenthesized.append(tl.pop(0)) value = ASTExprNode.build(parenthesized) if (parenthesized): raise SlSyntaxExpectedNothingError(parenthesized[0]) except SlSyntaxException: pass # TODO else: return value raise SlSyntaxExpectedError('Expr', lineno=lineno, offset=offset) class ASTUnaryExprNode(ASTExprNode): operator: ... value: ... def __init__(self, operator, value, **kwargs): super().__init__(**kwargs) self.operator, self.value = operator, value def __str__(self): return f"{self.operator}{' ' if (self.operator.operator.isalpha()) else ''}{str(self.value).join('()') if (DEBUG_PRECEDENCE or isinstance(self.value, ASTBinaryExprNode) and operator_precedence(self.value.operator.operator) >= operator_precedence(self.operator.operator)) else self.value}" @classmethod def build(cls, tl): ASTPrimitiveNode.build(tl) lineno, offset = tl[0].lineno, tl[0].offset operator = ASTUnaryOperatorNode.build(tl) value = ASTExprNode.build(tl) return cls(operator, value, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) valsig = Signature.build(self.value, ns) op = self.operator.operator if (op not in valsig.operators): raise SlValidationError(f"`{valsig}' does not support unary operator `{op}'", self.operator, self, scope=ns.scope) def optimize(self, ns): super().optimize(ns) if (isinstance(self.value, ASTUnaryExprNode)): return self.value.value elif (ns.values.get(self.value) not in (None, ...)): return ASTValueNode(ASTLiteralNode(literal_repr(eval(f"{'not' if (self.operator.operator == '!') else self.operator} ({literal_repr(ns.values[self.value])})")), lineno=self.lineno, offset=self.offset), lineno=self.lineno, offset=self.offset) class ASTBinaryExprNode(ASTExprNode): lvalue: ... operator: ... rvalue: ... def __init__(self, lvalue, operator, rvalue, **kwargs): super().__init__(**kwargs) self.lvalue, self.operator, self.rvalue = lvalue, operator, rvalue def __str__(self): opp = operator_precedence(self.operator.operator) return f"{str(self.lvalue).join('()') if (DEBUG_PRECEDENCE or isinstance(self.lvalue, ASTBinaryExprNode) and operator_precedence(self.lvalue.operator.operator) > opp) else self.lvalue}{str(self.operator).join(' ') if (self.operator.operator not in '+-**/') else self.operator}{str(self.rvalue).join('()') if (DEBUG_PRECEDENCE or isinstance(self.rvalue, ASTBinaryExprNode) and operator_precedence(self.rvalue.operator.operator) > opp) else self.rvalue}" @classmethod def build(cls, tl, opset): ASTPrimitiveNode.build(tl) lineno, offset = tl[0].lineno, tl[0].offset lasti = list() lvl = int() for ii, i in enumerate(tl): if (i.typename == 'SPECIAL'): lvl += 1 if (i.token == '(') else -1 if (i.token == ')') else 0 if (lvl > 0): continue if (i.typename == 'OPERATOR' and isinstance(i.token, BinaryOperator) and i.token in opset): lasti.append(ii) for i in lasti[::-1]: tlr, tll = tl[:i], tl[i:] err = set() try: lvalue = ASTExprNode.build(tlr) if (tlr): raise SlSyntaxExpectedNothingError(tlr[0]) operator = ASTBinaryOperatorNode.build(tll) rvalue = ASTExprNode.build(tll) except SlSyntaxException: pass else: tl[:] = tll; break else: raise SlSyntaxExpectedError('BinaryOperator', tl[0]) return cls(lvalue, operator, rvalue, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) lsig = Signature.build(self.lvalue, ns) rsig = Signature.build(self.rvalue, ns) op = self.operator.operator if ((op, rsig) not in lsig.operators): raise SlValidationError(f"`{lsig}' does not support operator `{op}' with operand of type `{rsig}'", self.operator, self, scope=ns.scope) def optimize(self, ns): super().optimize(ns) if (self.operator.operator == '**' and ns.values.get(self.lvalue) == 2 and ns.values.get(self.rvalue) is not ... and (ns.values.get(self.rvalue) or 0) > 0): self.operator.operator, self.lvalue.value = BinaryOperator('<<'), ASTLiteralNode('1', lineno=self.lvalue.value.lineno, offset=self.lvalue.value.offset) if (ns.values.get(self.lvalue) not in (None, ...) and ns.values.get(self.rvalue) not in (None, ...) and self.operator.operator != 'to'): return ASTValueNode(ASTLiteralNode(literal_repr(eval(f"({literal_repr(ns.values[self.lvalue])}) {self.operator} ({literal_repr(ns.values[self.rvalue])})")), lineno=self.lineno, offset=self.offset), lineno=self.lineno, offset=self.offset) class ASTLiteralStructNode(ASTNode): pass class ASTListNode(ASTLiteralStructNode): type: ... values: ... def __init__(self, type, values, **kwargs): super().__init__(**kwargs) self.type, self.values = type, values def __repr__(self): return f"" def __str__(self): return f"[{self.type}{': ' if (self.values) else ''}{S(', ').join(self.values)}]" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset bracket = ASTSpecialNode.build(tl) if (bracket.special != '['): raise SlSyntaxExpectedError("'['", bracket) type = ASTIdentifierNode.build(tl) values = list() if (not (tl[0].typename == 'SPECIAL' and tl[0].token == ']')): colon = ASTSpecialNode.build(tl) if (colon.special != ':'): raise SlSyntaxExpectedError("':'", colon) while (tl and tl[0].typename != 'SPECIAL'): values.append(ASTExprNode.build(tl)) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) bracket = ASTSpecialNode.build(tl) if (bracket.special != ']'): raise SlSyntaxExpectedError("']'", bracket) return cls(type, values, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) typesig = Signature.build(self.type, ns) for i in self.values: if (Signature.build(i, ns) != typesig): raise SlValidationError(f"List item `{i}' does not match list type `{self.type}'", i, self, scope=ns.scope) class ASTTupleNode(ASTLiteralStructNode): types: ... values: ... def __init__(self, types, values, **kwargs): super().__init__(**kwargs) self.types, self.values = types, values def __repr__(self): return f"" def __str__(self): return f"({S(', ').join((str(self.types[i])+' ' if (self.types[i] is not None) else '')+str(self.values[i]) for i in range(len(self.values)))}{','*(len(self.values) == 1)})" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != '('): raise SlSyntaxExpectedError("'('", parenthesis) types = list() values = list() while (tl and not (tl[0].typename == 'SPECIAL' and tl[0].token == ')')): types.append(ASTIdentifierNode.build(tl) if (len(tl) >= 2 and tl[0].typename == 'IDENTIFIER' and tl[1].token != ',') else None) values.append(ASTExprNode.build(tl)) if (len(values) < 2 or tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) return cls(types, values, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) for i in range(len(self.values)): if (Signature.build(self.values[i], ns) != Signature.build(self.types[i], ns)): raise SlValidationError(f"Tuple item `{self.values[i]}' does not match its type `{self.types[i]}'", self.values[i], self, scope=ns.scope) class ASTNonFinalNode(ASTNode): pass class ASTTypedefNode(ASTNonFinalNode): modifiers: ... type: ... def __init__(self, modifiers, type, **kwargs): super().__init__(**kwargs) self.modifiers, self.type = modifiers, type def __str__(self): return f"{S(' ').join(self.modifiers)}{' ' if (self.modifiers and self.type) else ''}{self.type or ''}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset modifiers = list() while (tl and tl[0].typename == 'KEYWORD'): if (not isinstance(tl[0].token, Modifier)): raise SlSyntaxExpectedError('Modifier', tl[0]) modifiers.append(ASTKeywordNode.build(tl)) type = ASTIdentifierNode.build(tl) return cls(modifiers, type, lineno=lineno, offset=offset) class ASTArgdefNode(ASTNonFinalNode): type: ... name: ... modifier: ... defvalue: ... def __init__(self, type, name, modifier, defvalue, **kwargs): super().__init__(**kwargs) self.type, self.name, self.modifier, self.defvalue = type, name, modifier, defvalue def __str__(self): return f"{f'{self.type} ' if (self.type) else ''}{self.name}{self.modifier or ''}{f'={self.defvalue}' if (self.defvalue is not None) else ''}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset type = ASTTypedefNode.build(tl) name = ASTIdentifierNode.build(tl) modifier = ASTOperatorNode.build(tl) if (tl and tl[0].typename == 'OPERATOR' and tl[0].token in '+**') else ASTSpecialNode.build(tl) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token in '?=') else None defvalue = ASTExprNode.build(tl) if (isinstance(modifier, ASTSpecialNode) and modifier.special == '=') else None return cls(type, name, modifier, defvalue, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) assert (self.modifier != '=' or self.defvalue is not None) if (isinstance(Signature.build(self.type, ns), stdlib.void)): raise SlValidationError(f"Argument cannot have type `{self.type}'", self.type, self, scope=ns.scope) @property def mandatory(self): return not (self.modifier is not None and self.modifier in '?=') class ASTCallargsNode(ASTNonFinalNode): callargs: ... starargs: ... def __init__(self, callargs, starargs, **kwargs): super().__init__(**kwargs) self.callargs, self.starargs = callargs, starargs def __str__(self): return S(', ').join((*self.callargs, *(f'*{i}' for i in self.starargs))) @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset callargs = list() starargs = list() if (tl and not (tl[0].typename == 'SPECIAL' and tl[0].token == ')')): while (tl): if (tl[0].typename == 'OPERATOR' and tl[0].token == '*'): ASTOperatorNode.build(tl) starargs.append(ASTExprNode.build(tl)) elif (len(tl) >= 2 and tl[1].typename == 'SPECIAL' and tl[1].token in '=:'): break else: callargs.append(ASTExprNode.build(tl)) if (not tl or tl[0].typename != 'SPECIAL' or tl[0].token == ')'): break comma = ASTSpecialNode.build(tl) if (comma.special != ','): raise SlSyntaxExpectedError("','", comma) return cls(callargs, starargs, lineno=lineno, offset=offset) class ASTCallkwargsNode(ASTNonFinalNode): callkwargs: ... starkwargs: ... def __init__(self, callkwargs, starkwargs, **kwargs): super().__init__(**kwargs) self.callkwargs, self.starkwargs = callkwargs, starkwargs def __str__(self): return S(', ').join((*(f'{k}={v}' for k, v in self.callkwargs), *(f'**{i}' for i in self.starkwargs))) @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset callkwargs = list() starkwargs = list() if (tl and not (tl[0].typename == 'SPECIAL' and tl[0].token == ')')): while (tl): if (tl[0].typename == 'OPERATOR' and tl[0].token == '**'): ASTOperatorNode.build(tl) starkwargs.append(ASTExprNode.build(tl)) else: key = ASTIdentifierNode.build(tl) eq = ASTSpecialNode.build(tl) if (eq.special not in '=:'): raise SlSyntaxExpectedError("'=' or ':'", eq) value = ASTExprNode.build(tl) callkwargs.append((key, value)) if (not tl or tl[0].typename != 'SPECIAL' or tl[0].token == ')'): break comma = ASTSpecialNode.build(tl) if (comma.special != ','): raise SlSyntaxExpectedError("','", comma) return cls(callkwargs, starkwargs, lineno=lineno, offset=offset) class ASTCallableNode(ASTNode): def validate(self, ns): # XXX. super().validate(ns) code_ns = ns.derive(self.code.name) if (hasattr(self, 'name')): code_ns.define(self, redefine=True) code_ns.values[self.name] = ... for i in self.argdefs: code_ns.define(i, redefine=True) code_ns.values[i.name] = ... self.code.validate(code_ns) def optimize(self, ns): super().optimize(ns) code_ns = ns.derive(self.code.name) for i in self.argdefs: code_ns.values[i.name] = ... code_ns.values.parent = None # XXX self.code.optimize(code_ns) class ASTFunctionNode(ASTCallableNode): def validate(self, ns): # XXX. super().validate(ns) code_ns = ns.derive(self.code.name) rettype = Signature.build(self.type, ns) return_nodes = tuple(i.value for i in self.code.nodes if (isinstance(i, ASTKeywordExprNode) and i.keyword.keyword == 'return')) if (not return_nodes and rettype != stdlib.void()): raise SlValidationError(f"Not returning value from function with return type `{rettype}'", self.code, self, scope=ns.scope) for i in return_nodes: fsig = Signature.build(i, code_ns) if (rettype == stdlib.void() and fsig != rettype): raise SlValidationError(f"Returning value from function with return type `{rettype}'", i, self, scope=ns.scope) if (common_type((fsig, rettype), code_ns) is None): raise SlValidationError(f"Returning value of incompatible type `{fsig}' from function with return type `{rettype}'", i, self, scope=ns.scope) class ASTLambdaNode(ASTNonFinalNode, ASTFunctionNode): argdefs: ... type: ... code: ... def __init__(self, argdefs, type, code, **kwargs): super().__init__(**kwargs) self.argdefs, self.type, self.code = argdefs, type, code def __fsig__(self): return f"({S(', ').join(self.argdefs)}) -> {self.type}" def __repr__(self): return f"" def __str__(self): return f"{self.__fsig__()} {f'= {self.code.nodes[0].value}' if (len(self.code.nodes) == 1 and isinstance(self.code.nodes[0], ASTKeywordExprNode) and self.code.nodes[0].keyword.keyword == 'return') else self.code}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != '('): raise SlSyntaxExpectedError("'('", parenthesis) argdefs = list() while (tl and tl[0].typename != 'SPECIAL'): argdef = ASTArgdefNode.build(tl) if (argdefs and argdef.defvalue is None and argdefs[-1].defvalue is not None): raise SlSyntaxError(f"Non-default argument {argdef} follows default argument {argdefs[-1]}") argdefs.append(argdef) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) arrow = ASTSpecialNode.build(tl) if (arrow.special != '->'): raise SlSyntaxExpectedError("'->'", arrow) type = ASTTypedefNode.build(tl) if (tl and (tl[0].typename != 'SPECIAL' or tl[0].token not in (*'={',))): raise SlSyntaxExpectedError("'=' or '{'", tl[0]) cdef = ASTSpecialNode.build(tl) if (cdef.special != '='): raise SlSyntaxExpectedError('=', cdef) code = ASTCodeNode([ASTKeywordExprNode(ASTKeywordNode('return', lineno=lineno, offset=offset), ASTExprNode.build(tl), lineno=lineno, offset=offset)], name='', lineno=lineno, offset=offset) return cls(argdefs, type, code, lineno=lineno, offset=offset) class ASTBlockNode(ASTNonFinalNode): code: ... def __init__(self, code, **kwargs): super().__init__(**kwargs) self.code = code def __str__(self): return str(self.code) if (len(self.code.nodes) > 1) else str(self.code)[1:-1].strip() @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset if (tl[0].typename == 'SPECIAL' and tl[0].token == '{'): code = (yield from ASTCodeNode.build(tl, name='')) else: yield '' expr = ASTExprNode.build(tl) code = ASTCodeNode([expr], name='', lineno=expr.lineno, offset=expr.offset) return cls(code, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) self.code.validate(ns) def optimize(self, ns): super().optimize(ns) self.code.optimize(ns) class ASTFinalNode(ASTNode): pass class ASTDefinitionNode(ASTNode): def validate(self, ns): # XXX. Signature.build(self, ns) ns.define(self) super().validate(ns) class ASTFuncdefNode(ASTFinalNode, ASTDefinitionNode, ASTFunctionNode): type: ... name: ... argdefs: ... code: ... def __init__(self, type, name, argdefs, code, **kwargs): super().__init__(**kwargs) self.type, self.name, self.argdefs, self.code = type, name, argdefs, code def __fsig__(self): return f"{self.type or 'def'} {self.name}({S(', ').join(self.argdefs)})" def __repr__(self): return f"" def __str__(self): isexpr = (len(self.code.nodes) == 1 and isinstance(self.code.nodes[0], ASTKeywordExprNode) and self.code.nodes[0].keyword.keyword == 'return') r = f"{self.__fsig__()} {f'= {self.code.nodes[0].value}' if (isexpr) else self.code}" return r @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset type = ASTTypedefNode.build(tl) name = ASTIdentifierNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != '('): raise SlSyntaxExpectedError("'('", parenthesis) argdefs = list() while (tl and tl[0].typename != 'SPECIAL'): argdef = ASTArgdefNode.build(tl) if (argdefs and argdef.defvalue is None and argdefs[-1].defvalue is not None): raise SlSyntaxError(f"Non-default argument {argdef} follows default argument {argdefs[-1]}") argdefs.append(argdef) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) if (not tl): raise SlSyntaxExpectedError("'=' or '{'", lineno=lineno, offset=-1) if (tl[0].typename != 'SPECIAL' or tl[0].token not in (*'={',)): raise SlSyntaxExpectedError("'=' or '{'", tl[0]) if (tl[0].token == '{'): code = (yield from ASTCodeNode.build(tl, name=name.identifier)) else: cdef = ASTSpecialNode.build(tl) assert (cdef.special == '=') yield name.identifier code = ASTCodeNode([ASTKeywordExprNode(ASTKeywordNode('return', lineno=lineno, offset=offset), ASTExprNode.build(tl), lineno=lineno, offset=offset)], name=name.identifier, lineno=lineno, offset=offset) return cls(type, name, argdefs, code, lineno=lineno, offset=offset) class ASTClassdefNode(ASTFinalNode, ASTDefinitionNode, ASTCallableNode): name: ... bases: ... code: ... type: ... argdefs = () def __init__(self, name, bases, code, **kwargs): super().__init__(**kwargs) self.name, self.bases, self.code = name, bases, code self.type = ASTTypedefNode([], self.name, lineno=self.lineno, offset=self.offset) def __repr__(self): return f"" def __str__(self): return f"class {self.name}{S(', ').join(self.bases).join('()') if (self.bases) else ''} {self.code}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset class_ = ASTKeywordNode.build(tl) if (class_.keyword != 'class'): raise SlSyntaxExpectedError("'class'", class_) name = ASTIdentifierNode.build(tl) bases = list() if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == '('): parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != '('): raise SlSyntaxExpectedError("'('", parenthesis) while (tl and tl[0].typename != 'SPECIAL'): bases.append(ASTIdentifierNode.build(tl)) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) if (not tl or tl[0].typename != 'SPECIAL' or tl[0].token != '{'): raise SlSyntaxExpectedError("'{'", tl[0]) code = (yield from ASTCodeNode.build(tl, name=name.identifier)) return cls(name, bases, code, lineno=lineno, offset=offset) class ASTKeywordExprNode(ASTFinalNode): keyword: ... value: ... def __init__(self, keyword, value, **kwargs): super().__init__(**kwargs) self.keyword, self.value = keyword, value def __str__(self): return f"{self.keyword}{f' {self.value}' if (self.value is not None) else ''}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset keyword = ASTKeywordNode.build(tl) if (not isinstance(keyword.keyword, ExprKeyword)): raise SlSyntaxExpectedError('ExprKeyword', keyword) if (keyword.keyword == 'import'): if (not tl): raise SlSyntaxExpectedMoreTokensError('import', lineno=lineno) lineno_, offset_ = tl[0].lineno, tl[0].offset value = ASTIdentifierNode(str().join(tl.pop(0).token for _ in range(len(tl))), lineno=lineno_, offset=offset_) # TODO Identifier? (or document it) elif (keyword.keyword == 'delete'): value = ASTIdentifierNode.build(tl) elif (tl): value = ASTExprNode.build(tl) else: value = None return cls(keyword, value, lineno=lineno, offset=offset) def validate(self, ns): if (self.keyword.keyword == 'import'): m = re.fullmatch(r'(?:(?:(\w+):)?(?:([\w./]+)/)?([\w.]+):)?([\w*]+)', self.value.identifier) assert (m is not None) namespace, path, pkg, name = m.groups() if (namespace is None): namespace = 'sl' if (path is None): path = '.' if (pkg is None): pkg = name pkg = pkg.replace('.', '/') if (namespace != 'sl'): filename = f"{os.path.join(path, pkg)}.sld" f = sld.parse(open(filename).read()) module_ns = f.namespace else: filename = f"{os.path.join(path, pkg)}.sl" src = open(filename, 'r').read() try: tl = parse_string(src) ast = build_ast(tl, filename) optimize_ast(ast, validate_ast(ast)) module_ns = validate_ast(ast) except (SlSyntaxError, SlValidationError) as ex: ex.srclines = src.split('\n') raise SlValidationError(f"Error importing {self.value}", self.value, self, scope=ns.scope) from ex if (name != '*'): ns.define(ASTIdentifierNode(name, lineno=self.value.lineno, offset=self.value.offset)) # TODO object else: ns.signatures.update(module_ns.signatures) # TODO? elif (self.keyword.keyword == 'delete'): if (self.value.identifier not in ns): raise SlValidationNotDefinedError(self.value, self, scope=ns.scope) ns.delete(self.value) super().validate(ns) class ASTKeywordDefNode(ASTFinalNode): keyword: ... name: ... argdefs: ... code: ... def __init__(self, keyword, name, argdefs, code, **kwargs): super().__init__(**kwargs) self.keyword, self.name, self.argdefs, self.code = keyword, name, argdefs, code if (self.name is None): self.name = ASTIdentifierNode(self.code.name, lineno=self.lineno, offset=self.offset) def __repr__(self): return f"" def __str__(self): return f"{self.keyword}{' '+S(', ').join(self.argdefs).join('()') if (isinstance(self.keyword.keyword, DefArgsKeyword)) else f' {self.name}' if (isinstance(self.keyword.keyword, DefNamedKeyword)) else ''} {self.code}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset keyword = ASTKeywordNode.build(tl) if (not isinstance(keyword.keyword, DefKeyword)): raise SlSyntaxExpectedError('DefKeyword', keyword) name = None argdefs = None if (isinstance(keyword.keyword, DefNamedKeyword)): name = ASTIdentifierNode.build(tl) elif (isinstance(keyword.keyword, DefArgsKeyword)): parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != '('): raise SlSyntaxExpectedError("'('", parenthesis) argdefs = list() while (tl and tl[0].typename != 'SPECIAL'): argdef = ASTArgdefNode.build(tl) if (argdefs and argdef.defvalue is None and argdefs[-1].defvalue is not None): raise SlSyntaxError(f"Non-default argument {argdef} follows default argument {argdefs[-1]}") argdefs.append(argdef) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) if (not tl or tl[0].typename != 'SPECIAL' or tl[0].token != '{'): raise SlSyntaxExpectedError('{', tl[0]) code = (yield from ASTCodeNode.build(tl, name=f"<{keyword}>")) return cls(keyword, name, argdefs, code, lineno=lineno, offset=offset) def validate(self, ns): # XXX. super().validate(ns) code_ns = ns.derive(self.code.name) if (isinstance(self.keyword.keyword, DefArgsKeyword)): for i in self.argdefs: code_ns.define(i, redefine=True) code_ns.values[i.name] = ... self.code.validate(code_ns) def optimize(self, ns): super().optimize(ns) code_ns = ns.derive(self.code.name) if (isinstance(self.keyword.keyword, DefArgsKeyword)): for i in self.argdefs: code_ns.values[i.name] = ... self.code.optimize(code_ns) class ASTAssignvalNode(ASTNode): def validate(self, ns): super().validate(ns) if (self.name.identifier not in ns): raise SlValidationNotDefinedError(self.name, self, scope=ns.scope) varsig = Signature.build(self.name, ns) if (self.value is not None): valsig = Signature.build(self.value, ns) if (valsig != varsig and varsig != valsig): raise SlValidationError(f"Assignment of value `{self.value}' of type `{valsig}' to variable `{self.name}' of type `{varsig}'", self.value, self, scope=ns.scope) varsig.flags.modified = True ns.values[self.name] = self.value if (not varsig.modifiers.volatile) else ... def optimize(self, ns): super().optimize(ns) varsig = Signature.build(self.name, ns) varsig.flags.modified = True ns.values[self.name] = self.value if (not varsig.modifiers.volatile) else ... class ASTVardefNode(ASTFinalNode, ASTAssignvalNode, ASTDefinitionNode): type: ... name: ... value: ... def __init__(self, type, name, value, **kwargs): super().__init__(**kwargs) self.type, self.name, self.value = type, name, value def __str__(self): return f"{self.type} {self.name}{f' = {self.value}' if (self.value is not None) else ''}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset type = ASTTypedefNode.build(tl) name = ASTIdentifierNode.build(tl) assignment = None value = None if (tl and tl[0].typename == 'SPECIAL'): assignment = ASTSpecialNode.build(tl) if (assignment.special != '='): raise SlSyntaxExpectedError('assignment', assignment) value = ASTExprNode.build(tl) return cls(type, name, value, lineno=lineno, offset=offset) def validate(self, ns): # XXX. if (self.type.type.identifier == 'auto'): self.type.type.identifier = Signature.build(self.value, ns).typename self.flags.optimized_out = False super().validate(ns) if (self.value is not None): varsig = Signature.build(self.name, ns) varsig.flags.modified = False def optimize(self, ns): super().optimize(ns) varsig = Signature.build(self.name, ns) self.flags.optimized_out = False if (self.value is not None): varsig.flags.modified = False #if (not varsig.flags.modified and not varsig.modifiers.volatile): # TODO # self.value = None # self.flags.optimized_out = True class ASTAssignmentNode(ASTFinalNode, ASTAssignvalNode): name: ... isattr: ... assignment: ... inplace_operator: ... value: ... def __init__(self, name, isattr, assignment, inplace_operator, value, **kwargs): super().__init__(**kwargs) self.name, self.isattr, self.assignment, self.inplace_operator, self.value = name, isattr, assignment, inplace_operator, value def __str__(self): return f"{'.'*self.isattr}{self.name} {self.inplace_operator or ''}{self.assignment} {self.value}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset isattr = False if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == '.'): ASTSpecialNode.build(tl); isattr = True name = ASTIdentifierNode.build(tl) inplace_operator = None if (tl and tl[0].typename == 'OPERATOR'): inplace_operator = ASTBinaryOperatorNode.build(tl) assignment = ASTSpecialNode.build(tl) if (assignment.special not in ('=', ':=')): raise SlSyntaxExpectedError('assignment', assignment) value = ASTExprNode.build(tl) return cls(name, isattr, assignment, inplace_operator, value, lineno=lineno, offset=offset) def validate(self, ns): # XXX. valsig = Signature.build(self.value, ns) if (self.assignment.special == ':='): ns.define(self.name, valsig, redefine=True) if (self.isattr): return # TODO super().validate(ns) varsig = Signature.build(self.name, ns) if (varsig.modifiers.const): raise SlValidationError(f"Assignment to const `{self.name}'", self.name, self, scope=ns.scope) if (self.inplace_operator is not None): ns.values[self.name] = ... # TODO folding def optimize(self, ns): super().optimize(ns) if (self.inplace_operator is not None): ns.values[self.name] = ... # TODO folding class ASTUnpackAssignmentNode(ASTFinalNode, ASTAssignvalNode): names: ... assignment: ... inplace_operator: ... value: ... def __init__(self, names, assignment, inplace_operator, value, **kwargs): super().__init__(**kwargs) self.names, self.assignment, self.inplace_operator, self.value = names, assignment, inplace_operator, value def __str__(self): return f"{S(', ').join(self.names)} {self.inplace_operator or ''}{self.assignment} {self.value}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset names = list() while (tl and tl[0].typename != 'SPECIAL'): names.append(ASTIdentifierNode.build(tl)) if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == ','): ASTSpecialNode.build(tl) inplace_operator = ASTOperatorNode.build(tl) if (tl and tl[0].typename == 'OPERATOR') else None if (inplace_operator is not None and not isinstance(inplace_operator.operator, BinaryOperator)): raise SlSyntaxExpectedError('BinaryOperator', inplace_operator) assignment = ASTSpecialNode.build(tl) if (assignment.special not in ('=', ':=')): raise SlSyntaxExpectedError('assignment', assignment) value = ASTExprNode.build(tl) return cls(names, assignment, inplace_operator, value, lineno=lineno, offset=offset) def validate(self, ns): # XXX. valsig = Signature.build(self.value, ns) if (self.assignment.special == ':='): for name, type in zip(self.names, valsig.valtypes): ns.define(name, type, redefine=True) ASTNode.validate(self, ns) for name, (ii, valtype) in zip(self.names, enumerate(valsig.valtypes)): if (name.identifier not in ns): raise SlValidationNotDefinedError(name, self, scope=ns.scope) varsig = Signature.build(name, ns) if (varsig.modifiers.const): raise SlValidationError(f"Assignment to const `{name}'", name, self, scope=ns.scope) if (varsig != valtype): raise SlValidationError(f"Assignment of `{valtype}' to variable {name} of type {varsig}", self.value.value.values[ii] if (isinstance(self.value, ASTValueNode) and hasattr(self.value.value, 'values')) else name, self, scope=ns.scope) varsig.flags.modified = True if (self.inplace_operator is not None): ns.values[name] = ... # TODO folding def optimize(self, ns): super().optimize(ns) if (self.inplace_operator is not None): ns.values[self.name] = ... # TODO folding class ASTUnaryOperationNode(ASTPrimitiveNode): name: ... isattr: ... unary_operator: ... def __init__(self, name, isattr, unary_operator, **kwargs): super().__init__(**kwargs) self.name, self.isattr, self.unary_operator = name, isattr, unary_operator def validate(self, ns): if (self.isattr): return # TODO super().validate(ns) varsig = Signature.build(self.name, ns) if (varsig.modifiers.const): raise SlValidationError(f"Unary operation `{self.unary_operator}' on const `{self.name}'", self.name, self, scope=ns.scope) class ASTUnaryPreOperationNode(ASTUnaryOperationNode, ASTFinalNode): def __str__(self): return f"{self.unary_operator}{'.'*self.isattr}{self.name}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset unary_operator = ASTUnaryOperatorNode.build(tl) if (unary_operator.operator not in unaryops): raise SlSyntaxExpectedError('Unary operation', unary_operator) isattr = False if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == '.'): ASTSpecialNode.build(tl); isattr = True name = ASTIdentifierNode.build(tl) return cls(name, isattr, unary_operator, lineno=lineno, offset=offset) class ASTUnaryPostOperationNode(ASTUnaryOperationNode, ASTFinalNode): def __str__(self): return f"{'.'*self.isattr}{self.name}{self.unary_operator}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset isattr = False if (tl and tl[0].typename == 'SPECIAL' and tl[0].token == '.'): ASTSpecialNode.build(tl); isattr = True name = ASTIdentifierNode.build(tl) unary_operator = ASTUnaryOperatorNode.build(tl) if (unary_operator.operator not in unaryops): raise SlSyntaxExpectedError('Unary operation', unary_operator) return cls(name, isattr, unary_operator, lineno=lineno, offset=offset) class ASTAttrsetNode(ASTFinalNode): value: ... assignment: ... def __init__(self, value, assignment, **kwargs): super().__init__(**kwargs) self.value, self.assignment = value, assignment def __str__(self): return f"{self.value}{self.assignment}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset value = ASTIdentifierNode.build(tl) assignment = ASTAssignmentNode.build(tl) if (not assignment.isattr): raise SlSyntaxExpectedError('attrset', assignment) return cls(value, assignment, lineno=lineno, offset=offset) def validate(self, ns): assert (self.assignment.isattr) super().validate(ns) # TODO: attr check #valsig = Signature.build(self.value, ns) #if ((self.optype.special, self.attr.identifier) not in valsig.attrops): raise SlValidationError(f"`{valsig}' does not support attribute operation `{self.optype}' with attr `{self.attr}'", self.optype, self, scope=ns.scope) class ASTFunccallNode(ASTFinalNode): callable: ... callargs: ... callkwargs: ... def __init__(self, callable, callargs, callkwargs, **kwargs): super().__init__(**kwargs) self.callable, self.callargs, self.callkwargs = callable, callargs, callkwargs def __str__(self): return f"{str(self.callable).join('()') if (isinstance(self.callable, ASTValueNode) and isinstance(self.callable.value, (ASTFunccallNode, ASTLambdaNode))) else self.callable}({self.callargs}{', ' if (str(self.callargs) and str(self.callkwargs)) else ''}{self.callkwargs})" @classmethod def build(cls, tl, *, attrget=False): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset callable = ASTExprNode.build(tl, fcall=True, attrget=attrget) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != '('): raise SlSyntaxExpectedError("'('", parenthesis) callargs = ASTCallargsNode.build(tl) callkwargs = ASTCallkwargsNode.build(tl) parenthesis = ASTSpecialNode.build(tl) if (parenthesis.special != ')'): raise SlSyntaxExpectedError("')'", parenthesis) return cls(callable, callargs, callkwargs, lineno=lineno, offset=offset) def validate(self, ns): super().validate(ns) fsig = Signature.build(self.callable, ns) if (not isinstance(fsig, Callable)): raise SlValidationError(f"`{self.callable}' of type `{fsig}' is not callable", self.callable, self, scope=ns.scope) callarguments = CallArguments.build(self, ns) if (fsig.compatible_call(callarguments, ns) is None): raise SlValidationError(f"Parameters `({callarguments})' don't match any of `{self.callable}' signatures:\n{S(fsig.callargssigstr).indent()}\n", self, scope=ns.scope) def optimize(self, ns): fsig = Signature.build(self.callable, ns) if (fsig.code is not None): code_ns = ns.derive(fsig.code.name) fsig.code.validate(code_ns) super().optimize(ns) class ASTConditionalNode(ASTFinalNode): condition: ... code: ... def __init__(self, condition, code, **kwargs): super().__init__(**kwargs) self.condition, self.code = condition, code def __repr__(self): return f"" def __str__(self): return f"if {self.condition} {self.code}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset if_ = ASTKeywordNode.build(tl) if (if_.keyword != 'if'): raise SlSyntaxExpectedError("'if'", if_) condition = ASTExprNode.build(tl) code = (yield from ASTBlockNode.build(tl)) return cls(condition, code, lineno=lineno, offset=offset) class ASTForLoopNode(ASTFinalNode): name: ... iterable: ... code: ... def __init__(self, name, iterable, code, **kwargs): super().__init__(**kwargs) self.name, self.iterable, self.code = name, iterable, code def __repr__(self): return f"" def __str__(self): return f"for {self.name} in {self.iterable} {self.code}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset for_ = ASTKeywordNode.build(tl) if (for_.keyword != 'for'): raise SlSyntaxExpectedError("'for'", for_) name = ASTIdentifierNode.build(tl) in_ = ASTOperatorNode.build(tl) if (in_.operator != 'in'): raise SlSyntaxExpectedError("'in'", in_) iterable = ASTExprNode.build(tl) code = (yield from ASTBlockNode.build(tl)) return cls(name, iterable, code, lineno=lineno, offset=offset) def validate(self, ns): # TODO: validate iterability ns.define(self.name, Signature.build(self.iterable, ns).valtype) ns.weaken(self.name) ns.values[self.name] = ... super().validate(ns) def optimize(self, ns): self.code.validate(ns) super().optimize(ns) class ASTWhileLoopNode(ASTFinalNode): code: ... # needs to be validated/optimized first (case when the condition is modified from loop body) condition: ... def __init__(self, condition, code, **kwargs): super().__init__(**kwargs) self.condition, self.code = condition, code def __repr__(self): return f"" def __str__(self): return f"while {self.condition} {self.code}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset while_ = ASTKeywordNode.build(tl) if (while_.keyword != 'while'): raise SlSyntaxExpectedError("'while'", while_) condition = ASTExprNode.build(tl) code = (yield from ASTBlockNode.build(tl)) return cls(condition, code, lineno=lineno, offset=offset) #def validate(self, ns): # Signature.build(self.condition, ns).modifiers.volatile = True # super().validate(ns) def optimize(self, ns): self.code.validate(ns) super().optimize(ns) class ASTElseClauseNode(ASTFinalNode): code: ... def __init__(self, code, **kwargs): super().__init__(**kwargs) self.code = code def __repr__(self): return f"" def __str__(self): return f"else {self.code}" @classmethod def build(cls, tl): super().build(tl) lineno, offset = tl[0].lineno, tl[0].offset else_ = ASTKeywordNode.build(tl) if (else_.keyword != 'else'): raise SlSyntaxExpectedError("'else'", else_) code = (yield from ASTBlockNode.build(tl)) return cls(code, lineno=lineno, offset=offset) def build_ast(code, name=None, *, interactive=False): code = copy.deepcopy(code) root = ASTRootNode.build(name) code_stack = [(root, next(root))] next(root) final_nodes = ASTFinalNode.__subclasses__() if (interactive): final_nodes += (ASTExprNode,) for ii, tl in enumerate(code): if (not tl): continue lineno, offset = tl[0].lineno, tl[0].offset err = set() for i in final_nodes: try: c = tl.copy() r = i.build(c) if (inspect.isgenerator(r)): code_stack.append((r, next(r))) try: r = next(r) except StopIteration as ex: code_stack.pop(); r = ex.value else: assert (r is None) if (c): if (c[-1].typename == 'SPECIAL' and c[-1].token == '}'): code.insert(ii+1, [c.pop()]) code.insert(ii+1, c) err.clear() break assert (r is not None) if (c): raise SlSyntaxExpectedNothingError(c[0]) except SlSyntaxEmpty: err.clear(); break except SlSyntaxNoToken: err.add(SlSyntaxExpectedMoreTokensError(i.__name__[3:-4], lineno=lineno, offset=-1)) except SlSyntaxMultiExpectedError as ex: pass#err.add(ex) # TODO FIXME except SlSyntaxExpectedError as ex: ex.usage = i.__name__[3:-4]; err.add(ex) else: code_stack[-1][0].send(r) err.clear() break else: if (len(code_stack) > 1 and tl and tl[0].typename == 'SPECIAL' and tl[0].token == '}'): if (tl[1:]): code.insert(ii+1, tl[1:]) try: next(code_stack.pop()[0]) except StopIteration as ex: code_stack[-1][0].send(ex.value); err.clear() else: raise WTFException() elif (not err): raise SlSyntaxError("Unknown structure", lineno=lineno, offset=offset, length=0, scope='.'.join(i[1] for i in code_stack if i[1])) if (err): raise SlSyntaxMultiExpectedError.from_list(err, scope='.'.join(i[1] for i in code_stack if i[1]) if (code_stack[0][1] is not None) else None) if (len(code_stack) > 1): raise SlSyntaxExpectedMoreTokensError(code_stack[-1][1], lineno=lineno) assert (len(code_stack) == 1) try: next(code_stack.pop()[0]) except StopIteration as ex: return ex.value def walk_ast_nodes(node): if (isiterable(node) and not isinstance(node, str)): for i in node: yield from walk_ast_nodes(i) if (not isinstance(node, ASTNode)): return yield node for i in allslots(node): yield from walk_ast_nodes(getattr(node, i)) class _SignatureBase(ABCSlots): pass class Signature(_SignatureBase): operators = {} typename: ... modifiers: paramset flags: paramset @init(typename=..., modifiers=..., flags=...) def __init__(self): super().__init__() @property def __reprname__(self): return self.__class__.__name__ def __repr__(self): return f"<{self.__reprname__} `{self.name}'>" def __str__(self): return self.name def __eq__(self, x): return (x is not None and self.typename == x.typename) def __hash__(self): return hash(tuple(getattr(self, i) for i in allslots(self))) @property def name(self): return self.typename @staticitemget def itemget(x): raise KeyError() @staticitemget def attrops(optype, attr): raise KeyError() @classmethod @dispatch def build(cls, x: ASTArgdefNode, ns): # TODO: modifiers return cls.build(x.type, ns) @classmethod @dispatch def build(cls, x: ASTAssignvalNode, ns): r = cls.build(x.type, ns) #ns.signatures[x.name.identifier] = r #if (x.value is not None): ns.values[x.name] = x.value if (False and not r.modifiers.volatile and r.modifiers.const) else ... # XXX TODO (see False) return r @classmethod @dispatch def build(cls, x: ASTTypedefNode, ns): r = cls.build(x.type, ns) r.modifiers.update(x.modifiers) return r @classmethod @dispatch def build(cls, x: ASTValueNode, ns): return cls.build(x.value, ns) @classmethod @dispatch def build(cls, x: ASTLiteralNode, ns): return builtin_names[literal_type(x.literal).__name__]() @classmethod @dispatch def build(cls, x: ASTIdentifierNode, ns): if (x.identifier in builtin_names): return builtin_names[x.identifier]() if (x.identifier not in ns): raise SlValidationNotDefinedError(x, scope=ns.scope) return ns.signatures[x.identifier] @classmethod @dispatch def build(cls, x: ASTListNode, ns): #return Collection(keytype=stdlib.int(), valtype=Signature.build(x.type, ns)) return stdlib.list(valtype=Signature.build(x.type, ns)) @classmethod @dispatch def build(cls, x: ASTTupleNode, ns): #return MultiCollection(keytype=stdlib.int(), valtypes=tuple(Signature.build(t if (t is not None) else v, ns) for t, v in zip(x.types, x.values))) return stdlib.tuple(valtypes=tuple(Signature.build(t if (t is not None) else v, ns) for t, v in zip(x.types, x.values))) @classmethod @dispatch def build(cls, x: ASTFunccallNode, ns): callarguments = CallArguments.build(x, ns) return cls.build(x.callable, ns).compatible_call(callarguments, ns)[1] @classmethod @dispatch def build(cls, x: ASTLambdaNode, ns): return Lambda.build(x, ns) @classmethod @dispatch def build(cls, x: ASTFuncdefNode, ns): return Function.build(x, ns) @classmethod @dispatch def build(cls, x: ASTClassdefNode, ns): return Class.build(x, ns) @classmethod @dispatch def build(cls, x: ASTKeywordDefNode, ns): return KeywordDef.build(x, ns) @classmethod @dispatch def build(cls, x: ASTUnaryOperationNode, ns): return cls.build(x.name, ns) @classmethod @dispatch def build(cls, x: ASTItemgetNode, ns): return cls.build(x.value, ns).itemget[cls.build(x.key, ns), x.key] @classmethod @dispatch def build(cls, x: ASTAttrgetNode, ns): return cls.build(x.value, ns).attrops[x.optype.special, x.attr.identifier] @classmethod @dispatch def build(cls, x: ASTUnaryExprNode, ns): return cls.build(x.value, ns).operators[x.operator.operator] @classmethod @dispatch def build(cls, x: ASTBinaryExprNode, ns): return cls.build(x.lvalue, ns).operators[x.operator.operator, cls.build(x.rvalue, ns)] @classmethod @dispatch def build(cls, x: _SignatureBase, ns): return x class Callable(Signature): call: ... code: ... def __init__(self, *, code=None, **kwargs): super().__init__(**kwargs) self.code = code def compatible_call(self, callarguments, ns): try: return first((k, v) for k, v in self.call.items() if callarguments.compatible(k)) except StopIteration: return None @abc.abstractproperty def callargssigstr(self): pass class Function(Callable): typename = 'function' name: ... def __init__(self, *, name, code=None, **kwargs): super().__init__(**kwargs) self.name, self.code = name, code self.call = listmap() @property def callargssigstr(self): return '\n'.join(f"{ret.typename} {self.name}({S(', ').join(args)})" for args, ret in self.call.items()) @staticitemget def attrops(optype, attr): if (optype == '.'): if (attr == 'map'): return stdlib._map() raise KeyError() @classmethod @dispatch def build(cls, x: ASTFuncdefNode, ns, *, redefine=False): name = x.name.identifier if (name not in ns): fsig = ns.signatures[name] = cls(name=name, code=x.code) else: fsig = ns.signatures[name] argdefs = tuple(x.argdefs) if (not redefine and argdefs in fsig.call and name not in ns.weak): raise SlValidationRedefinedError(x.name, x.__fsig__(), scope=ns.scope) if (x.type.type.identifier == 'auto'): # XXX@ TODO FIXME code_ns = ns.derive(x.code.name) rettype = common_type((i.value for i in x.code.nodes if (isinstance(i, ASTKeywordExprNode) and i.keyword.keyword == 'return')), code_ns) or stdlib.void() else: rettype = Signature.build(x.type, ns) fsig.call[argdefs] = rettype dlog(fsig.call, x) return fsig class Lambda(Callable): typename = 'lambda' def __init__(self, **kwargs): super().__init__(**kwargs) self.call = listmap() @property def callargssigstr(self): return '\n'.join(f"({S(', ').join(args)}) -> {ret.typename}" for args, ret in self.call.items()) @staticitemget def attrops(optype, attr): if (optype == '.'): if (attr == 'map'): return stdlib._map() raise KeyError() @classmethod @dispatch def build(cls, x: ASTLambdaNode, ns, *, redefine=False): fsig = cls(code=x.code) argdefs = tuple(x.argdefs) if (x.type.type.identifier == 'auto'): code_ns = ns.derive(x.code.name) rettype = common_type((i.value for i in x.code.nodes if (isinstance(i, ASTKeywordExprNode) and i.keyword.keyword == 'return')), code_ns) or stdlib.void() else: rettype = Signature.build(x.type, ns) fsig.call[argdefs] = rettype return fsig class KeywordDef(Callable): typename = 'keyworddef' name: ... def __init__(self, *, name, **kwargs): super().__init__(**kwargs) self.name = name self.call = listmap() @property def callargssigstr(self): return '\n'.join(f"{self.name}({S(', ').join(args)})" for args, ret in self.call.items()) @classmethod @dispatch def build(cls, x: ASTKeywordDefNode, ns, *, redefine=False): name = x.name.identifier if (name not in ns): fsig = ns.signatures[name] = cls(name=name, code=x.code) else: fsig = ns.signatures[name] argdefs = tuple(x.argdefs) if (x.argdefs is not None) else () if (not redefine and argdefs in fsig.call and name not in ns.weak): raise SlValidationRedefinedError(x.name, fsig.call[argdefs], scope=ns.scope) fsig.call[argdefs] = stdlib.void return fsig class Object(Signature): typename = 'Object' class Collection(Object): typename = 'collection' keytype: ... valtype: ... def __init__(self, *, keytype, valtype, **kwargs): super().__init__(**kwargs) self.keytype, self.valtype = keytype, valtype def __repr__(self): return f"" @property def typename(self): return self.valtype.typename @typename.setter def typename(self, x): pass @itemget @instantiate def itemget(self, keysig, key): if (keysig == self.keytype): return self.valtype raise KeyError() class MultiCollection(Collection): typename = 'MultiCollection' valtypes: ... def __init__(self, *, keytype, valtypes): super().__init__(keytype=keytype, valtype=stdlib.Any) self.valtypes = valtypes @itemget @instantiate def itemget(self, keysig, key): if (keysig == self.keytype): return self.valtypes[int(key)] raise KeyError() class Class(Object, Callable): typename = 'class' name: ... scope: ... constructor: ... def __init__(self, *, name, scope, **kwargs): super().__init__(**kwargs) self.name, self.scope = name, scope self.constructor = listmap() def __str__(self): return self.name def compatible_call(self, callarguments, ns): try: return first((k, v) for k, v in self.constructor.items() if callarguments.compatible(k)) except StopIteration: return None @property def callargssigstr(self): return '\n'.join(f"{self.name}({S(', ').join(args)})" for args, ret in self.constructor.items()) @itemget def call(self, argdefs): return self.constructor[argdefs] @itemget def attrops(self, optype, attr): if (optype == '.'): return self.scope.signatures[attr] raise KeyError() @classmethod @dispatch def build(cls, x: ASTClassdefNode, ns, *, redefine=False): name = x.name.identifier #if (not redefine and name in ns and name not in ns.weak): raise SlValidationRedefinedError(x.name, ns.signatures[name], scope=ns.scope) fsig = ns.signatures[name] = cls(name=name, scope=ns.derive(x.code.name), code=x.code) for i in x.code.nodes: if (isinstance(i, ASTKeywordDefNode) and i.keyword.keyword == 'constr'): argdefs = tuple(i.argdefs) if (i.argdefs is not None) else () if (not redefine and argdefs in fsig.constructor and name not in ns.weak): raise SlValidationRedefinedError(x.name, fsig.constructor[argdefs], scope=ns.scope) fsig.constructor[argdefs] = fsig return fsig class CallArguments(Slots): args: ... starargs: ... kwargs: ... starkwargs: ... ns: ... @init_defaults @autocast def __init__(self, *, args: tuple, starargs: tuple, kwargs: tuple, starkwargs: tuple, ns): self.args, self.starargs, self.kwargs, self.starkwargs, self.ns = args, starargs, kwargs, starkwargs, ns def __str__(self): return S(', ').join((*self.args, *('*'+i for i in self.starargs), *(f"{v} {k}" for k, v in self.kwargs), *('**'+i for i in self.starkwargs))) def __eq__(self, x): return all(getattr(self, i) == getattr(x, i) for i in allslots(self)) @dispatch def compatible(self, x: typing.Iterable[ASTArgdefNode]): # type(x[i]) = ASTArgdefNode # type(args[i]) = ASTExprNode # type(starargs[i]) = ASTExprNode # type(kwargs[i]) = tuple(ASTIdentifierNode, ASTExprNode) # type(starkwargs[i]) = ASTExprNode x = Slist(x) args, starargs, kwargs, starkwargs = list(self.args), list(self.starargs), dict(self.kwargs), list(self.starkwargs) has_mandatory_left = bool(x) optional_left = max(0, len(args) - len(tuple(i for i in x if i.mandatory))) to_fill_optionals = list() #dlog(1, x, args) while (x): x.discard() for ii, arg in enumerate(x): # type, name, modifier, defvalue if (not arg.mandatory): if (has_mandatory_left): if (len(args) > optional_left): to_fill_optionals.append(args.pop(0)) continue elif (to_fill_optionals): args += to_fill_optionals to_fill_optionals.clear() if (args): posarg = args.pop(0) sig = Signature.build(posarg, self.ns) if (common_type((arg.type, sig), self.ns) is None): return False x.to_discard(ii) continue if (starargs): stararg = starargs.pop(0) sig = Signature.build(stararg, self.ns) #if (common_type((arg.type, *stararg.type), self.ns) is not None): return False # TODO: typecheck! x.to_discard(ii) continue if (kwargs): continue if (starkwargs): continue # XXX! break else: has_mandatory_left = False; continue x.discard() break return not any((has_mandatory_left, args, starargs, kwargs, starkwargs)) #@property # XXX needed? | FIXME star[kw]args #def nargs(self): # return len(self.args) + sum(i.length for i in self.starargs) + len(self.kwargs) + sum(i.length for i in self.starkwargs) # #return sum(len(getattr(self, i)) for i in allslots(self)) @classmethod @dispatch def build(cls, x: ASTFunccallNode, ns): return cls(args=x.callargs.callargs, starargs=x.callargs.starargs, kwargs=x.callkwargs.callkwargs, starkwargs=x.callkwargs.starkwargs, ns=ns) class Namespace(Slots): class _Signatures(Slots): signatures: dict parent: None @init(signatures=..., parent=...) def __init__(self): super().__init__() assert (self.parent not in (self, self.signatures)) if (isinstance(self.parent, Namespace._Signatures)): assert (self.parent.parent not in (self, self.signatures)) def __iter__(self): return iter(self.keys()) @dispatch def __contains__(self, x: str): return x in self.signatures or (self.parent is not None and x in self.parent) @dispatch def __getitem__(self, x: str): try: return self.signatures[x] except KeyError: if (self.parent is not None): try: return self.parent[x] except KeyError: pass raise @dispatch def __setitem__(self, k: str, v: Signature): self.signatures[k] = v @dispatch def __delitem__(self, x: ASTIdentifierNode): del self[x.identifier] @dispatch def __delitem__(self, x: str): del self.signatures[x] def items(self): return self.signatures.items() if (self.parent is None) else {**self.parent, **self.signatures}.items() def keys(self): return self.signatures.keys() if (self.parent is None) else (*self.signatures.keys(), *self.parent.keys()) def copy(self): return self.__class__(signatures=self.signatures.copy(), parent=self.parent) class _Values(Slots): values: dict parent: None @init(values=..., parent=...) def __init__(self): super().__init__() assert (self.parent not in (self, self.values)) if (isinstance(self.parent, Namespace._Values)): assert (self.parent.parent not in (self, self.values)) @dispatch def __getitem__(self, x: ASTLiteralNode): return eval_literal(x) @dispatch def __getitem__(self, x: ASTValueNode): return self[x.value] @dispatch def __getitem__(self, x: ASTIdentifierNode): return self[x.identifier] @dispatch def __getitem__(self, x: str): try: return self.values[x] except KeyError: if (self.parent is not None): try: return self.parent[x] except KeyError: pass #except RecursionError: pass # TODO FIXME XXX! ??? (tests/fib.sl) raise @dispatch def __setitem__(self, k, v: ASTValueNode): self[k] = v.value @dispatch def __setitem__(self, x: ASTIdentifierNode, v: ASTLiteralNode): self[x.identifier] = eval_literal(v) @dispatch def __setitem__(self, x: ASTIdentifierNode, v): self[x.identifier] = v @dispatch def __setitem__(self, k: str, v): if (self.parent is None or k in self.values): self.values[k] = v else: self.parent[k] = v @dispatch def __delitem__(self, x: ASTValueNode): del self[x.value] @dispatch def __delitem__(self, x: ASTIdentifierNode): del self[x.identifier] @dispatch def __delitem__(self, x: str): del self.values[x] def get(self, x): try: return self[x] except KeyError: return None def items(self): return self.values.items() if (self.parent is None) else {**self.parent, **self.values}.items() def copy(self): return self.__class__(values=self.values.copy(), parent=self.parent) scope: ... signatures: lambda: Namespace._Signatures(parent=builtin_names) values: lambda: Namespace._Values(parent={i: ... for i in builtin_names}) weak: set refcount: lambda: Sdict(int) warnclasses: paramset flags: lambda: Sdict(paramset) olevel: int @init(signatures=..., values=..., weak=..., refcount=..., warnclasses=..., flags=..., olevel=...) def __init__(self, scope): self.scope = scope def __repr__(self): return f"" def __contains__(self, x): return x in self.signatures @lrucachedfunction def derive(self, scope, *, append=True): assert (type(scope) is str) #return Namespace(signatures=self.signatures.copy(), values=self._Values(parent=self.values), weak=self.weak, scope=self.scope+'.'+scope if (append) else scope) return Namespace(signatures=self.signatures, values=self._Values(parent=self.values), weak=self.weak | set(self.signatures), scope=self.scope+'.'+scope if (append) else scope) # XXX. #return Namespace(signatures=self._Signatures(parent=self.signatures), values=self._Values(parent=self.values), weak=self.weak | set(self.signatures), scope=self.scope+'.'+scope if (append) else scope) @dispatch def define(self, x: ASTFuncdefNode): self.define(x, redefine=True) self.values[x.name] = ... @dispatch def define(self, x: lambda x: hasattr(x, 'name'), sig=None, *, redefine=False): if (redefine): try: del self.signatures[x.name] except KeyError: pass try: del self.values[x.name] except KeyError: pass self.define(x.name, sig if (sig is not None) else Signature.build(x, self), redefine=redefine) @dispatch def define(self, x: ASTIdentifierNode, sig, *, redefine=False): if (not redefine and x.identifier in self and x.identifier not in self.weak): raise SlValidationRedefinedError(x, self.signatures[x.identifier], scope=self.scope) self.signatures[x.identifier] = sig self.values.values[x.identifier] = None self.weak.discard(x.identifier) @dispatch def weaken(self, x: ASTIdentifierNode): self.weak.add(x.identifier) @dispatch def delete(self, x: ASTIdentifierNode): ok = bool() try: del self.values[x] except KeyError: pass else: ok = True try: del self.signatures[x.identifier] except KeyError: pass else: ok = True self.weak.discard(x.identifier) if (not ok): raise SlValidationNotDefinedError(x, scope=self.scope) from . import stdlib from .stdlib import builtin_names def validate_ast(ast, ns=None): return ast.validate(ns) class SlNodeException(Exception, ABCSlots): node: ... ctxnode: ... srclines: ... scope: ... def __init__(self, node, ctxnode=None, *, srclines=(), scope=None): self.node, self.ctxnode, self.srclines, self.scope = node, ctxnode if (ctxnode is not None) else node, srclines, scope def __str__(self): line = self.srclines[self.lineno-1].partition('\n')[0].rstrip() if (self.srclines) else '' l = lstripcount(line)[0] ctx = self.ctxnode minlineno = min((getattr(ctx, i).lineno for i in allslots(ctx) if getattr(getattr(ctx, i), 'lineno', 0) > 0), default=min(ctx.lineno, self.lineno)) maxlineno = max((getattr(ctx, i).lineno for i in allslots(ctx) if getattr(getattr(ctx, i), 'lineno', 0) > 0), default=max(ctx.lineno, self.lineno)) minoffset = min((getattr(ctx, i).offset for i in allslots(ctx) if getattr(getattr(ctx, i), 'offset', -1) >= 0 and getattr(getattr(ctx, i), 'lineno') == self.lineno), default=self.node.offset) maxoffsetlength, maxoffset = max(((getattr(ctx, i).length, getattr(ctx, i).offset) for i in allslots(ctx) if getattr(getattr(ctx, i), 'offset', -1) >= 0 and getattr(getattr(ctx, i), 'lineno') == self.lineno), default=(self.node.length, self.node.offset)) loff = min((lstripcount(i)[0] for i in self.srclines[minlineno-1:maxlineno]), default=0) srclines = tuple(i[loff:] for i in self.srclines) line, srclines = line[loff:].expandtabs(TAB_SIZE), tuple(i.expandtabs(TAB_SIZE) for i in srclines) loff = lstripcount(line)[0] return (f'\033[2m(in {self.scope})\033[0m ' if (self.scope is not None) else '')+\ f"{self.__exline__()} {self.at}"+(':\n'+\ '\033[1m'+(' '+'\n '.join(srclines[minlineno-1:self.lineno-1])+'\n' if (minlineno < self.lineno) else '')+\ ' '+line[:minoffset-l]+'\033[91m'*(self.node.offset >= 0)+line[minoffset-l:maxoffset+maxoffsetlength]+'\033[0m'+line[maxoffset+maxoffsetlength:]+'\033[0m\n'+\ '\033[95m'+' '*(2+loff+minoffset-l)+'~'*(self.node.offset-minoffset)+'^'+'~'*(maxoffset+maxoffsetlength-(2+loff+minoffset-l)-(self.node.offset-minoffset)+1)+\ ('\n\033[0;91m '+'\n '.join(srclines[self.lineno:maxlineno]) if (maxlineno > self.lineno and len(srclines) > self.lineno) else '')+\ '\033[0m' if (srclines) else '')+\ self.__exsubline__()+\ (f"\n\n\033[1;95mCaused by:\033[0m\n{self.__cause__ if (isinstance(self.__cause__, (SlSyntaxException, SlNodeException))) else ' '+str().join(traceback.format_exception(type(self.__cause__), self.__cause__, self.__cause__.__traceback__))}" if (self.__cause__ is not None) else '') @abc.abstractmethod def __exline__(self): return '' def __exsubline__(self): return '' @property def at(self): return f"at line {self.node.lineno}, offset {self.node.offset}" @property def lineno(self): return self.node.lineno class SlValidationException(SlNodeException): pass class SlValidationError(SlValidationException): desc: ... def __init__(self, desc, *args, **kwargs): super().__init__(*args, **kwargs) self.desc = desc def __exline__(self): return f"Validation error: {self.desc}" class SlValidationNotDefinedError(SlValidationError): def __init__(self, identifier, *args, **kwargs): super().__init__(f"`{identifier.identifier}' is not defined", identifier, *args, **kwargs) class SlValidationRedefinedError(SlValidationError): def __init__(self, identifier, definition, *args, **kwargs): super().__init__(f"`{identifier}' redefined (defined as `{definition}')", identifier, *args, **kwargs)# at lineno {definition.lineno} def optimize_ast(ast, ns, level=DEFAULT_OLEVEL): return ast.optimize(ns) # by Sdore, 2021