def check_pending_labels(ast): """ Iteratively traverses the node looking for ID with no class set, marks them as labels, and check they've been declared. This way we avoid stack overflow for high line-numbered listings. """ result = True visited = set() pending = [ast] while pending: node = pending.pop() if node is None or node in visited: # Avoid recursive infinite-loop continue visited.add(node) for x in node.children: pending.append(x) if node.token != 'VAR' or (node.token == 'VAR' and node.class_ is not CLASS.unknown): continue tmp = global_.SYMBOL_TABLE.get_entry(node.name) if tmp is None or tmp.class_ is CLASS.unknown: errmsg.error(node.lineno, f'Undeclared identifier "{node.name}"') else: assert tmp.class_ == CLASS.label node.to_label(node) result = result and tmp is not None return result
def argval(self): """ Solve args values or raise errors if not defined yet """ if gl.has_errors: return [None] if self.asm in ('DEFB', 'DEFS', 'DEFW'): result = tuple( [x.eval() if isinstance(x, Expr) else x for x in self.arg]) if self.asm == 'DEFB' and any(x > 255 for x in result): errmsg.warning_value_will_be_truncated(self.lineno) return result self.arg = tuple( [x if not isinstance(x, Expr) else x.eval() for x in self.arg]) if gl.has_errors: return [None] if self.asm.split(' ')[0] in ('JR', 'DJNZ'): # A relative jump? if self.arg[0] < -128 or self.arg[0] > 127: error(self.lineno, 'Relative jump out of range') return [None] return super(Asm, self).argval()
def exit_proc(self, lineno: int): """ Exits current procedure. Local labels are transferred to global scope unless they have been marked as local ones. Raises an error if no current local context (stack underflow) """ __DEBUG__('Exiting current scope from lineno %i' % lineno) if len(self.local_labels) <= 1: error(lineno, 'ENDP in global scope (with no PROC)') return for label in self.local_labels[-1].values(): if label.local: if not label.defined: error(lineno, "Undefined LOCAL label '%s'" % label.name) return continue name = label.name _lineno = label.lineno value = label.value if name not in self.global_labels.keys(): self.global_labels[name] = label else: self.global_labels[name].define(value, _lineno) self.local_labels.pop() # Removes current context self.scopes.pop()
def __init__(self, lineno, asm, arg=None): self.lineno = lineno if asm not in ('DEFB', 'DEFS', 'DEFW'): try: super(Asm, self).__init__(asm, arg) except Error as v: error(lineno, v.msg) return self.pending = len([ x for x in self.arg if isinstance(x, Expr) and x.try_eval() is None ]) > 0 if not self.pending: self.arg = self.argval() else: self.asm = asm self.pending = True if isinstance(arg, str): self.arg = tuple( [Expr(Container(ord(x), lineno)) for x in arg]) else: self.arg = arg self.arg_num = len(self.arg)
def p_asm_ld8(p): """ asm : LD reg8 COMMA reg8_hl | LD reg8_hl COMMA reg8 | LD reg8 COMMA reg8 | LD SP COMMA HL | LD SP COMMA reg16i | LD A COMMA reg8 | LD reg8 COMMA A | LD reg8_hl COMMA A | LD A COMMA reg8_hl | LD A COMMA A | LD A COMMA I | LD I COMMA A | LD A COMMA R | LD R COMMA A | LD A COMMA reg8i | LD reg8i COMMA A | LD reg8 COMMA reg8i | LD reg8i COMMA regBCDE | LD reg8i COMMA reg8i """ if p[2] in ('H', 'L') and p[4] in ('IXH', 'IXL', 'IYH', 'IYL'): p[0] = None error(p.lineno(0), "Unexpected token '%s'" % p[4]) else: p[0] = Asm(p.lineno(1), 'LD %s,%s' % (p[2], p[4]))
def make_node(lower, upper, lineno): """ Creates an array bound """ if not check.is_static(lower, upper): error(lineno, 'Array bounds must be constants') return None if isinstance(lower, SymbolVAR): lower = lower.value if lower is None: # semantic error error(lineno, "Unknown lower bound for array dimension") return if isinstance(upper, SymbolVAR): upper = upper.value if upper is None: # semantic error error(lineno, "Unknown upper bound for array dimension") return lower.value = int(lower.value) upper.value = int(upper.value) if lower.value < 0: error(lineno, 'Array bounds must be greater than 0') return None if lower.value > upper.value: error(lineno, 'Lower array bound must be less or equal to upper one') return None return SymbolBOUND(lower.value, upper.value)
def t_MACROS(t): r'__[a-zA-Z]+__' if t.value in macros: t.type = t.value return t error(t.lexer.lineno, "unknown macro '%s'" % t.value)
def __set_byte(self, byte: int, lineno: int): """ Sets a byte at the current location, and increments org in one. Raises an error if org > MAX_MEMORY """ if byte < 0 or byte > 255: error(lineno, 'Invalid byte value %i' % byte) self.memory_bytes[self.org] = byte self.index += 1 # Increment current memory pointer
def p_im(p): """ asm : IM expr """ val = p[2].eval() if val not in (0, 1, 2): error(p.lineno(1), 'Invalid IM number %i' % val) p[0] = None return p[0] = Asm(p.lineno(1), 'IM %i' % val)
def resolve(self, lineno): """ Evaluates label value. Exits with error (unresolved) if value is none """ if not self.defined: error(lineno, "Undeclared label '%s'" % self.name) if isinstance(self.value, Expr): return self.value.eval() return self.value
def set_org(self, value: int, lineno: int): """ Sets a new ORG value """ if value < 0 or value > MAX_MEM: error( lineno, "Memory ORG out of range [0 .. 65535]. Current value: %i" % value) self.index = self.ORG = value
def p_DEFS(p): # Define bytes """ asm : DEFS number_list """ if len(p[2]) > 2: error(p.lineno(1), "too many arguments for DEFS") if len(p[2]) < 2: num = Expr.makenode(Container(0, p.lineno(1))) # Defaults to 0 p[2] = p[2] + (num, ) p[0] = Asm(p.lineno(1), 'DEFS', p[2])
def p_align(p): """ asm : ALIGN expr | ALIGN pexpr """ align = p[2].eval() if align < 2: error(p.lineno(1), "ALIGN value must be greater than 1") return MEMORY.set_org(MEMORY.org + (align - MEMORY.org % align) % align, p.lineno(1))
def p_BIT_ix(p): """ asm : bitop expr COMMA reg8_I | bitop pexpr COMMA reg8_I """ bit = p[2].eval() if bit < 0 or bit > 7: error(p.lineno(3), 'Invalid bit position %i. Must be in [0..7]' % bit) p[0] = None return p[0] = Asm(p.lineno(3), '%s %i,%s' % (p[1], bit, p[4][0]), p[4][1])
def define(self, value, lineno, namespace=None): """ Defines label value. It can be anything. Even an AST """ if self.defined: error( lineno, "label '%s' already defined at line %i" % (self.name, self.lineno)) self.value = value self.lineno = lineno self.namespace = NAMESPACE if namespace is None else namespace
def p_rst(p): """ asm : RST expr """ val = p[2].eval() if val not in (0, 8, 16, 24, 32, 40, 48, 56): error(p.lineno(1), 'Invalid RST number %i' % val) p[0] = None return p[0] = Asm(p.lineno(1), 'RST %XH' % val)
def p_pop_namespace(p): """ asm : POP NAMESPACE """ global NAMESPACE if not NAMESPACE_STACK: error( p.lineno(2), f"Stack underflow. No more Namespaces to pop. Current namespace is {NAMESPACE}" ) else: NAMESPACE = NAMESPACE_STACK.pop()
def p_error(p): if p is not None: if p.type != 'NEWLINE': error( p.lineno, "Syntax error. Unexpected token '%s' [%s]" % (p.value, p.type)) else: error(p.lineno, "Syntax error. Unexpected end of line [NEWLINE]") else: OPTIONS.stderr.write( "General syntax error at assembler (unexpected End of File?)") gl.has_errors += 1
def try_eval(self): """ Recursively evals the node. Returns None if it is still unresolved. """ item = self.symbol.item if isinstance(item, int): return item if isinstance(item, Label): if item.defined: if isinstance(item.value, Expr): return item.value.try_eval() else: return item.value else: if Expr.ignore: return None # Try to resolve into the global namespace error(self.symbol.lineno, "Undefined label '%s'" % item.name) return None try: if isinstance(item, tuple): return tuple([x.try_eval() for x in item]) if isinstance(item, list): return [x.try_eval() for x in item] if item == '-' and len(self.children) == 1: return -self.left.try_eval() if item == '+' and len(self.children) == 1: return self.left.try_eval() try: return self.funct[item](self.left.try_eval(), self.right.try_eval()) except ZeroDivisionError: error(self.symbol.lineno, 'Division by 0') except KeyError: pass except TypeError: pass return None
def p_incbin(p): """ asm : INCBIN STRING """ try: fname = zxbpp.search_filename(p[2], p.lineno(2), local_first=True) if not fname: p[0] = None return with src.api.utils.open_file(fname, 'rb') as f: filecontent = f.read() except IOError: error(p.lineno(2), "cannot read file '%s'" % p[2]) p[0] = None return p[0] = Asm(p.lineno(1), 'DEFB', filecontent)
def check_call_arguments(lineno: int, id_: str, args): """ Check arguments against function signature. Checks every argument in a function call against a function. Returns True on success. """ if not global_.SYMBOL_TABLE.check_is_declared(id_, lineno, 'function'): return False if not global_.SYMBOL_TABLE.check_class(id_, CLASS.function, lineno): return False entry = global_.SYMBOL_TABLE.get_entry(id_) if len(args) != len(entry.params): c = 's' if len(entry.params) != 1 else '' errmsg.error(lineno, "Function '%s' takes %i parameter%s, not %i" % (id_, len(entry.params), c, len(args))) return False for arg, param in zip(args, entry.params): if arg.class_ in (CLASS.var, CLASS.array) and param.class_ != arg.class_: errmsg.error(lineno, "Invalid argument '{}'".format(arg.value)) return None if not arg.typecast(param.type_): return False if param.byref: if not isinstance(arg.value, symbols.VAR): errmsg.error(lineno, "Expected a variable name, not an expression (parameter By Reference)") return False if arg.class_ not in (CLASS.var, CLASS.array): errmsg.error(lineno, "Expected a variable or array name (parameter By Reference)") return False arg.byref = True if arg.value is not None: arg.value.add_required_symbol(param) if entry.forwarded: # The function / sub was DECLARED but not implemented errmsg.error(lineno, "%s '%s' declared but not implemented" % (CLASS.to_string(entry.class_), entry.name)) return False return True
def check_and_make_label(lbl: Union[str, int, float], lineno): """ Checks if the given label (or line number) is valid and, if so, returns a label object. :param lbl: Line number of label (string) :param lineno: Line number in the basic source code for error reporting :return: Label object or None if error. """ if isinstance(lbl, float): if lbl == int(lbl): id_ = str(int(lbl)) else: errmsg.error(lineno, 'Line numbers must be integers.') return None else: id_ = lbl return global_.SYMBOL_TABLE.access_label(id_, lineno)
def check_type(lineno, type_list, arg): """ Check arg's type is one in type_list, otherwise, raises an error. """ if not isinstance(type_list, list): type_list = [type_list] if arg.type_ in type_list: return True if len(type_list) == 1: errmsg.error(lineno, "Wrong expression type '%s'. Expected '%s'" % (arg.type_, type_list[0])) else: errmsg.error(lineno, "Wrong expression type '%s'. Expected one of '%s'" % (arg.type_, tuple(type_list))) return False
def dump(self): """ Returns a tuple containing code ORG (origin address), and a list of bytes (OUTPUT) """ org = min(self.memory_bytes.keys()) # Org is the lowest one OUTPUT = [] align = [] for label in self.global_labels.values(): if not label.defined: error(label.lineno, "Undefined GLOBAL label '%s'" % label.name) for i in range(org, max(self.memory_bytes.keys()) + 1): if gl.has_errors: return org, OUTPUT try: try: a = [x for x in self.orgs[i] if isinstance(x, Asm)] # search for asm instructions if not a: align.append( 0) # Fill with ZEROes not used memory regions continue OUTPUT += align align = [] a = a[0] if a.pending: a.arg = a.argval() a.pending = False tmp = a.bytes() for r in range(len(tmp)): self.memory_bytes[i + r] = tmp[r] except KeyError: pass OUTPUT.append(self.memory_bytes[i]) except KeyError: OUTPUT.append(0) # Fill with ZEROes not used memory regions return org, OUTPUT
def assemble(input_): """ Assembles input string, and leave the result in the MEMORY global object """ global MEMORY if MEMORY is None: MEMORY = Memory() if OPTIONS.zxnext: parser_ = zxnext_parser else: parser_ = parser parser_.parse(input_, lexer=LEXER, debug=OPTIONS.debug_level > 1) if len(MEMORY.scopes): error(MEMORY.scopes[-1], 'Missing ENDP to close this scope') return gl.has_errors
def p_ind8_I(p): """ reg8_I : LP IX expr RP | LP IY expr RP | LP IX PLUS pexpr RP | LP IX MINUS pexpr RP | LP IY PLUS pexpr RP | LP IY MINUS pexpr RP | LB IX expr RB | LB IY expr RB | LB IX PLUS pexpr RB | LB IX MINUS pexpr RB | LB IY PLUS pexpr RB | LB IY MINUS pexpr RB """ if len(p) == 6: expr = p[4] sign = p[3] else: expr = p[3] gen_ = expr.inorder() first_expr = next(gen_, '') if first_expr and first_expr.parent: if len(first_expr.parent.children) == 2: first_token = first_expr.symbol.item else: first_token = first_expr.parent.symbol.item else: first_token = '<nothing>' if first_token not in ('-', '+'): error( p.lineno(2), "Unexpected token '{}'. Expected '+' or '-'".format( first_token)) sign = '+' if sign == '-': expr = Expr.makenode(Container(sign, p.lineno(2)), expr) p[0] = ('(%s+N)' % p[2], expr)
def make_node(cls, id_: str, arglist: SymbolARGLIST, lineno: int, filename: str) -> Optional['SymbolARRAYACCESS']: """ Creates an array access. A(x1, x2, ..., xn) """ assert isinstance(arglist, SymbolARGLIST) variable = gl.SYMBOL_TABLE.access_array(id_, lineno) if variable is None: return None if variable.scope != SCOPE.parameter: if len(variable.bounds) != len(arglist): errmsg.error(lineno, "Array '%s' has %i dimensions, not %i" % (variable.name, len(variable.bounds), len(arglist))) return None # Checks for array subscript range if the subscript is constant # e.g. A(1) is a constant subscript access btype = gl.SYMBOL_TABLE.basic_types[gl.BOUND_TYPE] for i, b in zip(arglist, variable.bounds): lower_bound = NUMBER(b.lower, type_=btype, lineno=lineno) if check.is_number(i.value) or check.is_const(i.value): val = i.value.value if val < b.lower or val > b.upper: errmsg.warning(lineno, "Array '%s' subscript out of range" % id_) i.value = BINARY.make_node('MINUS', TYPECAST.make_node(btype, i.value, lineno), lower_bound, lineno, func=lambda x, y: x - y, type_=btype) else: btype = gl.SYMBOL_TABLE.basic_types[gl.BOUND_TYPE] for arg in arglist: arg.value = TYPECAST.make_node(btype, arg.value, arg.value.lineno) # Returns the variable entry and the node return cls(variable, arglist, lineno, filename)
def make_node(cls, new_type, node, lineno): """ Creates a node containing the type cast of the given one. If new_type == node.type, then nothing is done, and the same node is returned. Returns None on failure (and calls syntax_error) """ assert isinstance(new_type, SymbolTYPE) # None (null) means the given AST node is empty (usually an error) if node is None: return None # Do nothing. Return None assert isinstance(node, Symbol), '<%s> is not a Symbol' % node # The source and dest types are the same if new_type == node.type_: return node # Do nothing. Return as is # TODO: Create a base scalar type if isinstance(node, SymbolVARARRAY): if new_type.size == node.type_.size and TYPE.string not in (new_type, node.type_): return node error(lineno, "Array {} type does not match parameter type".format(node.name)) return None STRTYPE = TYPE.string # Typecasting, at the moment, only for number if node.type_ == STRTYPE: error(lineno, 'Cannot convert string to a value. Use VAL() function') return None # Converting from string to number is done by STR if new_type == STRTYPE: error(lineno, 'Cannot convert value to string. Use STR() function') return None # If the given operand is a constant, perform a static typecast if check.is_CONST(node): node.expr = cls(new_type, node.expr, lineno) return node if not check.is_number(node) and not check.is_const(node): return cls(new_type, node, lineno) # It's a number. So let's convert it directly if check.is_const(node): node = SymbolNUMBER(node.value, node.lineno, node.type_) if new_type.is_basic and not TYPE.is_integral(new_type): # not an integer node.value = float(node.value) else: # It's an integer new_val = (int(node.value) & ((1 << (8 * new_type.size)) - 1)) # Mask it if node.value >= 0 and node.value != new_val: errmsg.warning_conversion_lose_digits(node.lineno) node.value = new_val elif node.value < 0 and (1 << (new_type.size * 8)) + \ node.value != new_val: # Test for positive to negative coercion errmsg.warning_conversion_lose_digits(node.lineno) node.value = new_val - (1 << (new_type.size * 8)) node.type_ = new_type return node
def t_INITIAL_preproc_error(self, t): # error handling rule error(t.lexer.lineno, "illegal character '%s'" % t.value[0])
def t_INITIAL_bin_string_asm_preproc_comment_error(t): error(t.lineno, "illegal character '%s'" % t.value[0])