conditional gotos

This commit is contained in:
Irmen de Jong 2017-12-27 19:01:14 +01:00
parent c07698dfda
commit 44065597ff
8 changed files with 416 additions and 83 deletions

View File

@ -69,24 +69,6 @@ def parse_arguments(text: str, sourceref: SourceRef) -> List[Tuple[str, Primitiv
except SyntaxError as x:
raise src.to_error(str(x))
def astnode_to_repr(node: ast.AST) -> str:
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.Num):
return repr(node.n)
if isinstance(node, ast.Str):
return repr(node.s)
if isinstance(node, ast.BinOp):
if node.left.id == "__ptr" and isinstance(node.op, ast.MatMult): # type: ignore
return '#' + astnode_to_repr(node.right)
else:
print("error", ast.dump(node))
raise TypeError("invalid arg ast node type", node)
if isinstance(node, ast.Attribute):
return astnode_to_repr(node.value) + "." + node.attr
print("error", ast.dump(node))
raise TypeError("invalid arg ast node type", node)
args = [] # type: List[Tuple[str, Any]]
if isinstance(nodes, ast.Expression):
for arg in nodes.body.args:
@ -100,14 +82,37 @@ def parse_arguments(text: str, sourceref: SourceRef) -> List[Tuple[str, Primitiv
raise TypeError("ast.Expression expected")
def parse_expr_as_comparison(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef) -> None:
def parse_expr_as_comparison(text: str, sourceref: SourceRef) -> Tuple[str, str, str]:
src = SourceLine(text, sourceref)
text = src.preprocess()
try:
node = ast.parse(text, sourceref.file, mode="eval")
except SyntaxError as x:
raise src.to_error(str(x))
print("AST NODE", node)
if not isinstance(node, ast.Expression):
raise TypeError("ast.Expression expected")
if isinstance(node.body, ast.Compare):
if len(node.body.ops) != 1:
raise src.to_error("only one comparison operator at a time is supported")
operator = {
"Eq": "==",
"NotEq": "!=",
"Lt": "<",
"LtE": "<=",
"Gt": ">",
"GtE": ">=",
"Is": None,
"IsNot": None,
"In": None,
"NotIn": None
}[node.body.ops[0].__class__.__name__]
if not operator:
raise src.to_error("unsupported comparison operator")
left = text[node.body.left.col_offset:node.body.comparators[0].col_offset-len(operator)]
right = text[node.body.comparators[0].col_offset:]
return left.strip(), operator, right.strip()
left = astnode_to_repr(node.body)
return left, "", ""
def parse_expr_as_int(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef, *,
@ -240,3 +245,22 @@ class ExpressionTransformer(EvaluatingTransformer):
else:
raise self.error("invalid MatMult/Pointer node in AST")
return node
def astnode_to_repr(node: ast.AST) -> str:
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.Num):
return repr(node.n)
if isinstance(node, ast.Str):
return repr(node.s)
if isinstance(node, ast.BinOp):
if node.left.id == "__ptr" and isinstance(node.op, ast.MatMult): # type: ignore
return '#' + astnode_to_repr(node.right)
else:
print("error", ast.dump(node))
raise TypeError("invalid arg ast node type", node)
if isinstance(node, ast.Attribute):
return astnode_to_repr(node.value) + "." + node.attr
print("error", ast.dump(node))
raise TypeError("invalid arg ast node type", node)

View File

@ -160,7 +160,7 @@ class CodeGenerator:
self.p("\t\tsta {:s}".format(vname))
self.p("\t\tstx {:s}+1".format(vname))
elif variable.type == DataType.FLOAT:
raise TypeError("floats cannot be stored in the zp")
raise CodeError("floats cannot be stored in the zp")
self.p("; end init zp vars")
else:
self.p("\t\t; there are no zp vars to initialize")
@ -213,8 +213,9 @@ class CodeGenerator:
asmlines = [
"\t\tcld\t\t\t; clear decimal flag",
"\t\tclc\t\t\t; clear carry flag"
"\t\tclv\t\t\t; clear overflow flag"
]
statements.insert(index+1, ParseResult.InlineAsm(0, asmlines))
statements.insert(index+1, ParseResult.InlineAsm(asmlines, 0))
break
block.statements = statements
# generate
@ -286,7 +287,7 @@ class CodeGenerator:
self.p("\t\t{:s} = {:s}\t; matrix {:d} by {:d} = {:d} bytes"
.format(vardef.name, Parser.to_hex(vardef.address), vardef.matrixsize[0], vardef.matrixsize[1], vardef.length))
else:
raise ValueError("invalid var type")
raise CodeError("invalid var type")
non_mem_vars = [vi for vi in block.symbols.iter_variables() if vi.allocate]
if non_mem_vars:
self.p("; normal variables")
@ -305,7 +306,7 @@ class CodeGenerator:
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}"
.format(vardef.name, *self.to_mflpt5(float(vardef.value))))
else:
raise TypeError("weird datatype")
raise CodeError("weird datatype")
elif vardef.type in (DataType.BYTEARRAY, DataType.WORDARRAY):
if vardef.address:
raise CodeError("array or wordarray vars must not have address; will be allocated by assembler")
@ -316,7 +317,7 @@ class CodeGenerator:
self.p("{:s}\t\t.fill {:d}, [${:02x}, ${:02x}]\t; {:d} words of ${:04x}"
.format(vardef.name, vardef.length * 2, f_lo, f_hi, vardef.length, vardef.value or 0))
else:
raise TypeError("invalid datatype", vardef.type)
raise CodeError("invalid datatype", vardef.type)
elif vardef.type == DataType.MATRIX:
if vardef.address:
raise CodeError("matrix vars must not have address; will be allocated by assembler")
@ -434,6 +435,164 @@ class CodeGenerator:
raise NotImplementedError("decr by > 1") # XXX
def generate_call(self, stmt: ParseResult.CallStmt) -> None:
self.p("\t\t\t\t\t; src l. {:d}".format(stmt.lineno))
if stmt.condition:
assert stmt.is_goto
line_after_goto = ""
if stmt.condition.lvalue:
if stmt.condition.comparison_op:
# the condition is lvalue operator rvalue
assert stmt.condition.ifstatus in ("true", "not")
assert stmt.condition.lvalue != stmt.condition.rvalue # so we know we actually have to compare different things
lv, op, rv = stmt.condition.lvalue, stmt.condition.comparison_op, stmt.condition.rvalue
if lv.constant and not rv.constant:
# if lv is a constant, swap the whole thing around so the constant is on the right
lv, op, rv = stmt.condition.swap()
if isinstance(rv, ParseResult.RegisterValue):
# if rv is a register, make sure it comes first instead
lv, op, rv = stmt.condition.swap()
if lv.datatype != DataType.BYTE or rv.datatype != DataType.BYTE:
raise CodeError("can only generate comparison code for byte values for now") # @todo compare non-bytes
if isinstance(lv, ParseResult.RegisterValue):
if isinstance(rv, ParseResult.RegisterValue):
self.p("\t\tst{:s} {:s}".format(rv.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
if lv.register == "A":
self.p("\t\tcmp " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lv.register == "X":
self.p("\t\tcpx " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lv.register == "Y":
self.p("\t\tcpy " + Parser.to_hex(Zeropage.SCRATCH_B1))
else:
raise CodeError("wrong lvalue register")
elif isinstance(rv, ParseResult.IntegerValue):
rvstr = rv.name or Parser.to_hex(rv.value)
if lv.register == "A":
self.p("\t\tcmp #" + rvstr)
elif lv.register == "X":
self.p("\t\tcpx #" + rvstr)
elif lv.register == "Y":
self.p("\t\tcpy #" + rvstr)
else:
raise CodeError("wrong lvalue register")
elif isinstance(rv, ParseResult.MemMappedValue):
rvstr = rv.name or Parser.to_hex(rv.address)
if lv.register == "A":
self.p("\t\tcmp " + rvstr)
elif lv.register == "X":
self.p("\t\tcpx #" + rvstr)
elif lv.register == "Y":
self.p("\t\tcpy #" + rvstr)
else:
raise CodeError("wrong lvalue register")
else:
raise CodeError("invalid rvalue type in comparison", rv)
elif isinstance(lv, ParseResult.MemMappedValue):
assert not isinstance(rv, ParseResult.RegisterValue), "registers as rvalue should have been swapped with lvalue"
if isinstance(rv, ParseResult.IntegerValue):
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
self.p("\t\tcmp #" + (rv.name or Parser.to_hex(rv.value)))
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
elif isinstance(rv, ParseResult.MemMappedValue):
rvstr = rv.name or Parser.to_hex(rv.address)
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
self.p("\t\tcmp " + rvstr)
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
else:
raise CodeError("invalid rvalue type in comparison", rv)
else:
raise CodeError("invalid lvalue type in comparison", lv)
else:
# the condition is just the 'truth value' of the single value, or rather bool(value)
# this is translated into assembly by comparing the argument to zero.
cv = stmt.condition.lvalue
inverse_status = stmt.condition.inverse_ifstatus()
if isinstance(cv, ParseResult.RegisterValue):
if cv.register == 'A':
self.p("\t\tcmp #0")
elif cv.register == 'X':
self.p("\t\tcpx #0")
elif cv.register == 'Y':
self.p("\t\tcpy #0")
else:
# @todo combined register, not all if statuses are supported yet
opcode = self._branchopcode(inverse_status)
if opcode not in ("beq", "bne"):
raise CodeError("cannot yet generate code for register pair that is not a true/false/eq/ne comparison",
self.cur_block.sourceref.file, stmt.lineno) # @todo
if cv.register == 'AX':
line_after_goto = "+"
self.p("\t\tcmp #0")
self.p("\t\t{:s} {:s}".format(opcode, line_after_goto))
self.p("\t\tcpx #0")
elif cv.register == 'AY':
line_after_goto = "+"
self.p("\t\tcmp #0")
self.p("\t\t{:s} {:s}".format(opcode, line_after_goto))
self.p("\t\tcpy #0")
elif cv.register == 'XY':
line_after_goto = "+"
self.p("\t\tcpx #0")
self.p("\t\t{:s} {:s}".format(opcode, line_after_goto))
self.p("\t\tcpy #0")
else:
raise CodeError("invalid register", cv.register)
elif isinstance(cv, ParseResult.MemMappedValue):
if cv.datatype == DataType.BYTE:
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (cv.name or Parser.to_hex(cv.address)))
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
elif cv.datatype == DataType.WORD:
# @todo word value, not all if statuses are supported yet
opcode = self._branchopcode(inverse_status)
if opcode not in ("beq", "bne"):
raise CodeError("cannot yet generate code for word value that is not a true/false/eq/ne comparison",
self.cur_block.sourceref.file, stmt.lineno) # @todo
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (cv.name or Parser.to_hex(cv.address)))
self.p("\t\t{:s} +".format(opcode))
self.p("\t\tlda " + (cv.name or Parser.to_hex(cv.address)))
line_after_goto = "+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
else:
raise CodeError("conditions cannot yet use other types than byte or word", # XXX
str(cv), self.cur_block.sourceref.file, stmt.lineno)
else:
raise CodeError("need register or memmapped value")
status = stmt.condition.ifstatus
self._generate_call(stmt, conditional_goto_opcode=self._branchopcode(status), line_after_goto=line_after_goto)
else:
self._generate_call(stmt)
def _branchopcode(self, status: str) -> str:
if status == "true":
status = "ne"
elif status in ("not", "zero"):
status = "eq"
elif status == "lt":
status = "cc"
elif status == "gt":
status = "eq + cs" # @todo
elif status == "le":
status = "cc + eq" # @todo
elif status == "ge":
status = "cs"
opcodes = {"cc": "bcc",
"cs": "bcs",
"vc": "bvc",
"vs": "bvs",
"eq": "beq",
"ne": "bne",
"pos": "bpl", # @todo correct?
"neg": "bmi"} # @todo correct?
return opcodes[status]
def _generate_call(self, stmt: ParseResult.CallStmt, conditional_goto_opcode: str=None, line_after_goto: str=None) -> None:
if stmt.arguments and conditional_goto_opcode:
raise CodeError("cannot use a conditional goto when the target requires parameters")
goto_opcode = conditional_goto_opcode or "jmp"
def generate_param_assignments() -> None:
for assign_stmt in stmt.desugared_call_arguments:
self.generate_assignment(assign_stmt)
@ -469,10 +628,12 @@ class CodeGenerator:
if isinstance(stmt.target, ParseResult.MemMappedValue):
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
else:
raise TypeError("call sub target should be mmapped")
raise CodeError("call sub target should be mmapped")
if stmt.is_goto:
generate_param_assignments()
self.p("\t\tjmp " + targetstr)
self.p("\t\t{:s} {:s}".format(goto_opcode, targetstr))
if line_after_goto:
self.p(line_after_goto)
# no result assignments because it's a goto
return
clobbered = set() # type: Set[str]
@ -504,10 +665,12 @@ class CodeGenerator:
if targetstr in REGISTER_WORDS:
self.p("\t\tst{:s} {:s}".format(targetstr[0].lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tst{:s} {:s}".format(targetstr[1].lower(), Parser.to_hex(Zeropage.SCRATCH_B2)))
self.p("\t\tjmp ({:s})".format(Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\t{:s} ({:s})".format(goto_opcode, Parser.to_hex(Zeropage.SCRATCH_B1)))
else:
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("\t\t{:s} ({:s})".format(goto_opcode, targetstr))
# no result assignments because it's a goto
if line_after_goto:
self.p(line_after_goto)
else:
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
@ -551,8 +714,10 @@ class CodeGenerator:
if stmt.is_goto:
# no need to preserve registers for a goto
generate_param_assignments()
self.p("\t\tjmp " + targetstr)
self.p("\t\t{:s} {:s}".format(goto_opcode, targetstr))
# no result assignments because it's a goto
if line_after_goto:
self.p(line_after_goto)
else:
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
@ -813,7 +978,7 @@ class CodeGenerator:
if lv.name:
symblock, sym = self.cur_block.lookup(lv.name)
if not isinstance(sym, VariableDef):
raise TypeError("invalid lvalue type " + str(sym))
raise CodeError("invalid lvalue type " + str(sym))
assign_target = symblock.label + '.' + sym.name if symblock is not self.cur_block else lv.name
lvdatatype = sym.type
else:
@ -836,10 +1001,10 @@ class CodeGenerator:
self.p("\t\tsta {}+1".format(assign_target))
elif lvdatatype == DataType.FLOAT:
if rvalue.value is not None and not DataType.FLOAT.assignable_from_value(rvalue.value):
raise ValueError("value cannot be assigned to a float")
raise CodeError("value cannot be assigned to a float")
self.generate_assign_float_to_mem(lv, rvalue, False)
else:
raise TypeError("invalid lvalue type " + str(lvdatatype))
raise CodeError("invalid lvalue type " + str(lvdatatype))
def generate_assign_mem_to_reg(self, l_register: str, rvalue: ParseResult.MemMappedValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address)
@ -934,9 +1099,9 @@ class CodeGenerator:
self.p("\t\tlda #0")
self.p("\t\tsta {}+1".format(assign_target))
else:
raise TypeError("invalid lvalue type " + str(sym))
raise CodeError("invalid lvalue type " + str(sym))
else:
raise TypeError("invalid lvalue type " + str(sym))
raise CodeError("invalid lvalue type " + str(sym))
def generate_assign_integer_to_reg(self, l_register: str, rvalue: ParseResult.IntegerValue) -> None:
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)

View File

@ -13,9 +13,9 @@ import sys
import shutil
import enum
from collections import defaultdict
from typing import Set, List, Tuple, Optional, Any, Dict, Union, Sequence
from typing import Set, List, Tuple, Optional, Any, Dict, Union
from .astparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\
parse_expr_as_string, parse_arguments
parse_expr_as_string, parse_arguments, parse_expr_as_comparison
from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \
zeropage, check_value_in_range, char_to_bytevalue, \
PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \
@ -254,8 +254,11 @@ class ParseResult:
return False, "can only assign an integer constant value of 0 or 1 to SC and SI"
if self.constant:
return False, "cannot assign to a constant"
if isinstance(other, ParseResult.RegisterValue) and len(self.register) < len(other.register):
return False, "register size mismatch"
if isinstance(other, ParseResult.RegisterValue):
if other.register in {"SI", "SC", "SZ"}:
return False, "cannot explicitly assign from a status bit register alias"
if len(self.register) < len(other.register):
return False, "register size mismatch"
if isinstance(other, ParseResult.StringValue) and self.register in REGISTER_BYTES:
return False, "string address requires 16 bits combined register"
if isinstance(other, ParseResult.IntegerValue):
@ -345,22 +348,24 @@ class ParseResult:
return False, "incompatible value for assignment"
class _AstNode: # @todo merge Value with this?
pass
def __init__(self, lineno: int) -> None:
self.lineno = lineno
class Comment(_AstNode):
def __init__(self, text: str) -> None:
def __init__(self, text: str, lineno: int) -> None:
super().__init__(lineno)
self.text = text
class Label(_AstNode):
def __init__(self, name: str, lineno: int) -> None:
super().__init__(lineno)
self.name = name
self.lineno = lineno
class AssignmentStmt(_AstNode):
def __init__(self, leftvalues: List['ParseResult.Value'], right: 'ParseResult.Value', lineno: int) -> None:
super().__init__(lineno)
self.leftvalues = leftvalues
self.right = right
self.lineno = lineno
def __str__(self):
return "<Assign {:s} to {:s}>".format(str(self.right), ",".join(str(lv) for lv in self.leftvalues))
@ -395,28 +400,34 @@ class ParseResult:
return all(lv == self.right for lv in self.leftvalues)
class ReturnStmt(_AstNode):
def __init__(self, a: Optional['ParseResult.Value']=None,
def __init__(self, lineno: int, a: Optional['ParseResult.Value']=None,
x: Optional['ParseResult.Value']=None,
y: Optional['ParseResult.Value']=None) -> None:
super().__init__(lineno)
self.a = a
self.x = x
self.y = y
class IncrDecrStmt(_AstNode):
def __init__(self, what: 'ParseResult.Value', howmuch: int) -> None:
def __init__(self, what: 'ParseResult.Value', howmuch: int, lineno: int) -> None:
super().__init__(lineno)
self.what = what
self.howmuch = howmuch
class CallStmt(_AstNode):
def __init__(self, lineno: int, target: Optional['ParseResult.Value']=None, *,
address: Optional[int]=None, arguments: List[Tuple[str, Any]]=None,
outputs: List[Tuple[str, 'ParseResult.Value']]=None, is_goto: bool=False, preserve_regs: bool=True) -> None:
self.lineno = lineno
outputs: List[Tuple[str, 'ParseResult.Value']]=None, is_goto: bool=False,
preserve_regs: bool=True, condition: 'ParseResult.IfCondition'=None) -> None:
if not is_goto:
assert condition is None
super().__init__(lineno)
self.target = target
self.address = address
self.arguments = arguments
self.outputvars = outputs
self.is_goto = is_goto
self.condition = condition
self.preserve_regs = preserve_regs
self.desugared_call_arguments = [] # type: List[ParseResult.AssignmentStmt]
self.desugared_output_assignments = [] # type: List[ParseResult.AssignmentStmt]
@ -438,10 +449,56 @@ class ParseResult:
self.desugared_output_assignments.append(assignment)
class InlineAsm(_AstNode):
def __init__(self, lineno: int, asmlines: List[str]) -> None:
self.lineno = lineno
def __init__(self, asmlines: List[str], lineno: int) -> None:
super().__init__(lineno)
self.asmlines = asmlines
class IfCondition(_AstNode):
SWAPPED_OPERATOR = {"==": "==",
"!=": "!=",
"<=": ">=",
">=": "<=",
"<": ">",
">": "<"}
INVERSE_IFSTATUS = {"cc": "cs",
"cs": "cc",
"vc": "vs",
"vs": "vc",
"eq": "ne",
"ne": "eq",
"pos": "neg",
"neg": "pos",
"true": "not",
"not": "true",
"zero": "true",
"lt": "ge",
"gt": "le",
"le": "gt",
"ge": "lt"}
def __init__(self, ifstatus: str, leftvalue: Optional['ParseResult.Value'],
operator: str, rightvalue: Optional['ParseResult.Value'], lineno: int) -> None:
assert ifstatus in self.INVERSE_IFSTATUS
assert operator in (None, "") or operator in self.SWAPPED_OPERATOR
if operator:
assert ifstatus in ("true", "not")
super().__init__(lineno)
self.ifstatus = ifstatus
self.lvalue = leftvalue
self.comparison_op = operator
self.rvalue = rightvalue
def __str__(self):
return "<IfCondition {} {:s} {}>".format(self.lvalue, self.comparison_op, self.rvalue)
def inverse_ifstatus(self) -> str:
# to be able to generate a branch when the status does NOT apply
return self.INVERSE_IFSTATUS[self.ifstatus]
def swap(self) -> Tuple['ParseResult.Value', str, 'ParseResult.Value']:
self.lvalue, self.comparison_op, self.rvalue = self.rvalue, self.SWAPPED_OPERATOR[self.comparison_op], self.lvalue
return self.lvalue, self.comparison_op, self.rvalue
def add_block(self, block: 'ParseResult.Block', position: Optional[int]=None) -> None:
if position is not None:
self.blocks.insert(position, block)
@ -550,7 +607,7 @@ class Parser:
while True:
line = self.next_line().lstrip()
if line.startswith(';'):
self.cur_block.statements.append(ParseResult.Comment(line))
self.cur_block.statements.append(ParseResult.Comment(line, self.sourceref.line))
continue
self.prev_line()
break
@ -1040,6 +1097,17 @@ class Parser:
return varname, datatype, length, matrix_dimensions, valuetext
def parse_statement(self, line: str) -> ParseResult._AstNode:
match = re.fullmatch(r"(?P<if>if(_[a-z]+)?)\s+(?P<cond>.+)?goto\s+(?P<subname>[\S]+?)\s*(\((?P<arguments>.*)\))?\s*", line)
if match:
# conditional goto
groups = match.groupdict()
subname = groups["subname"]
if '!' in subname:
raise self.PError("goto is always without register preservation, should not have exclamation mark")
if groups["if"] == "if" and not groups["cond"]:
raise self.PError("need explicit if status when a condition is not present")
condition = self.parse_if_condition(groups["if"], groups["cond"])
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True, condition=condition)
match = re.fullmatch(r"goto\s+(?P<subname>[\S]+?)\s*(\((?P<arguments>.*)\))?\s*", line)
if match:
# goto
@ -1047,8 +1115,7 @@ class Parser:
subname = groups["subname"]
if '!' in subname:
raise self.PError("goto is always without register preservation, should not have exclamation mark")
arguments = groups["arguments"]
return self.parse_call_or_goto(subname, arguments, None, False, True)
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True)
match = re.fullmatch(r"(?P<outputs>[^\(]*\s*=)?\s*(?P<subname>[\S]+?)\s*(?P<fcall>[!]?)\s*(\((?P<arguments>.*)\))?\s*", line)
if match:
# subroutine call (not a goto) with possible output param assignment
@ -1070,7 +1137,7 @@ class Parser:
what = self.parse_expression(line[:-2].rstrip())
if isinstance(what, ParseResult.IntegerValue):
raise self.PError("cannot in/decrement a constant value")
return ParseResult.IncrDecrStmt(what, 1 if incr else -1)
return ParseResult.IncrDecrStmt(what, 1 if incr else -1, self.sourceref.line)
else:
# perhaps it is an assignment statment
lhs, sep, rhs = line.partition("=")
@ -1079,7 +1146,9 @@ class Parser:
raise self.PError("invalid statement")
def parse_call_or_goto(self, targetstr: str, argumentstr: str, outputstr: str,
preserve_regs=True, is_goto=False) -> ParseResult.CallStmt:
preserve_regs=True, is_goto=False, condition: ParseResult.IfCondition=None) -> ParseResult.CallStmt:
if not is_goto:
assert condition is None
argumentstr = argumentstr.strip() if argumentstr else ""
outputstr = outputstr.strip() if outputstr else ""
arguments = None
@ -1106,6 +1175,8 @@ class Parser:
except ParseError:
symbol = None # it's probably a number or a register then
if isinstance(symbol, SubroutineDef):
if condition and symbol.parameters:
raise self.PError("cannot use a subroutine that requires parameters as a target for conditional goto")
# verify subroutine arguments
if len(arguments or []) != len(symbol.parameters):
raise self.PError("invalid number of arguments ({:d}, expected {:d})"
@ -1144,7 +1215,7 @@ class Parser:
if isinstance(target, (type(None), ParseResult.Value)):
if is_goto:
return ParseResult.CallStmt(self.sourceref.line, target, address=address,
arguments=arguments, outputs=outputvars, is_goto=True)
arguments=arguments, outputs=outputvars, is_goto=True, condition=condition)
else:
return ParseResult.CallStmt(self.sourceref.line, target, address=address,
arguments=arguments, outputs=outputvars, preserve_regs=preserve_regs)
@ -1190,7 +1261,7 @@ class Parser:
if len(parts) > 1:
values = parts[1].split(",")
if len(values) == 0:
return ParseResult.ReturnStmt()
return ParseResult.ReturnStmt(self.sourceref.line)
else:
a = self.parse_expression(values[0]) if values[0] else None
if len(values) > 1:
@ -1199,7 +1270,7 @@ class Parser:
y = self.parse_expression(values[2]) if values[2] else None
if len(values) > 3:
raise self.PError("too many returnvalues")
return ParseResult.ReturnStmt(a, x, y)
return ParseResult.ReturnStmt(self.sourceref.line, a, x, y)
def parse_asm(self) -> ParseResult.InlineAsm:
line = self.next_line()
@ -1211,7 +1282,7 @@ class Parser:
while True:
line = self.next_line()
if line.strip() == "}":
return ParseResult.InlineAsm(lineno, asmlines)
return ParseResult.InlineAsm(asmlines, lineno)
# asm can refer to other symbols as well, track subroutine usage
splits = line.split(maxsplit=1)
if len(splits) == 2:
@ -1252,7 +1323,7 @@ class Parser:
lines = ['{:s}\t.binclude "{:s}"'.format(scopename, filename)]
else:
raise self.PError("invalid asminclude statement")
return ParseResult.InlineAsm(self.sourceref.line, lines)
return ParseResult.InlineAsm(lines, self.sourceref.line)
elif aline[0] == "asmbinary":
if len(aline) == 4:
offset = parse_expr_as_int(aline[2], None, None, self.sourceref)
@ -1265,7 +1336,7 @@ class Parser:
lines = ['\t.binary "{:s}"'.format(filename)]
else:
raise self.PError("invalid asmbinary statement")
return ParseResult.InlineAsm(self.sourceref.line, lines)
return ParseResult.InlineAsm(lines, self.sourceref.line)
else:
raise self.PError("invalid statement")
@ -1462,6 +1533,33 @@ class Parser:
result = [sentence[i:j].strip(separators) for i, j in zip(indices, indices[1:])]
return list(filter(None, result)) # remove empty strings
def parse_if_condition(self, ifpart: str, conditionpart: str) -> ParseResult.IfCondition:
if ifpart == "if":
ifstatus = "true"
else:
ifstatus = ifpart[3:]
if ifstatus not in ParseResult.IfCondition.INVERSE_IFSTATUS:
raise self.PError("invalid if form")
if conditionpart:
left, operator, right = parse_expr_as_comparison(conditionpart, self.sourceref)
leftv = self.parse_expression(left)
if not operator and isinstance(leftv, (ParseResult.IntegerValue, ParseResult.FloatValue, ParseResult.StringValue)):
raise self.PError("condition is a constant value")
if isinstance(leftv, ParseResult.RegisterValue):
if leftv.register in {"SC", "SZ", "SI"}:
raise self.PError("cannot use a status bit register explicitly in a condition")
if operator:
rightv = self.parse_expression(right)
if ifstatus not in ("true", "not"):
raise self.PError("can only use if[_true] or if_not with a comparison expression")
else:
rightv = None
if leftv == rightv:
raise self.PError("left and right values in comparison are identical")
return ParseResult.IfCondition(ifstatus, leftv, operator, rightv, self.sourceref.line)
else:
return ParseResult.IfCondition(ifstatus, None, "", None, self.sourceref.line)
class Optimizer:
def __init__(self, parseresult: ParseResult) -> None:
@ -1474,12 +1572,23 @@ class Optimizer:
self.combine_assignments_into_multi(block)
self.optimize_multiassigns(block)
self.remove_unused_subroutines(block)
self.optimize_compare_with_zero(block)
for sub in block.symbols.iter_subroutines(True):
self.remove_identity_assigns(sub.sub_block)
self.combine_assignments_into_multi(sub.sub_block)
self.optimize_multiassigns(sub.sub_block)
self.optimize_compare_with_zero(sub.sub_block)
return self.parsed
def optimize_compare_with_zero(self, block: ParseResult.Block) -> None:
# a conditional goto that compares a value to zero will be simplified
# the comparison operator and rvalue (0) will be removed and the if-status changed accordingly
for stmt in block.statements:
if isinstance(stmt, ParseResult.CallStmt):
if stmt.condition and isinstance(stmt.condition.rvalue, (ParseResult.IntegerValue, ParseResult.FloatValue)):
if stmt.condition.rvalue.value == 0:
print("ZOMG COMPARE WITH ZERO", stmt.lineno) # XXX
def combine_assignments_into_multi(self, block: ParseResult.Block) -> None:
# fold multiple consecutive assignments with the same rvalue into one multi-assignment
statements = [] # type: List[ParseResult._AstNode]

View File

@ -41,7 +41,7 @@ class PreprocessingParser(Parser):
return self.result
def parse_asminclude(self, line: str) -> ParseResult.InlineAsm:
return ParseResult.InlineAsm(self.sourceref.line, [])
return ParseResult.InlineAsm([], self.sourceref.line)
def parse_statement(self, line: str) -> ParseResult._AstNode:
return None # type: ignore

View File

@ -243,6 +243,7 @@ sub init_state () -> (A?, X?, Y?) {
tax
tay
clc
clv
cli
rts
}

View File

@ -311,22 +311,18 @@ TODOS
### Flow Control
Required building blocks: additional forms of 'go' statement: including an if clause, comparison statement.
Required building blocks: additional forms of 'goto' statement: including an if clause, comparison statement.
- a primitive conditional branch instruction (special case of 'go'): directly translates to a branch instruction:
if[_XX] goto <label>
XX is one of: (cc, cs, vc, vs, eq, ne, pos, min,
lt==cc, lts==min, gt==eq+cs, gts==eq+pos, le==cc+eq, les==neg+eq, ge==cs, ges==pos)
and when left out, defaults to ne (not-zero, i.e. true)
- a conditional goto instruction: directly translates to a branch instruction:
if[_XX] [<expression>] goto <label>
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, pos, neg, true==ne, not==eq, zero==eq,
lt==cc, gt==eq+cs, le==cc+eq, ge==cs]
(@todo signed: lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?)
and defaults to true (ne, not-zero) if omitted.
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] if-statuses
can be used when a *comparison expression* (such as "A < 10") is used.
NOTE: some combination branches such as cc+eq an be peephole optimized see http://www.6502.org/tutorials/compare_beyond.html#2.2
- conditional goto with expression: where the if[_XX] is followed by a <expression>
in that case, evaluate the <expression> first (whatever it is) and then emit the primitive if[_XX] go
if[_XX] <expression> goto <label>
eventually translates to:
<expression-code>
bXX <label>
- comparison statement: compares left with right: compare <first_value>, <second_value>
(and keeps the comparison result in the status register.)
this translates into a lda first_value, cmp second_value sequence after which a conditional branch is possible.

View File

@ -0,0 +1,44 @@
output raw
~ main {
var .text name = "?"*80
var bytevar = 22
var bytevar2 = 23
var .word wordvar = 22345
start
return
label1
if_true 22<=Y goto label1
if_true A<=22 goto label1
if_true A<X goto label1
if_true X>Y goto label1
if_true X>A goto label1
if_true A<=0 goto label1
if_true A==0 goto label1
if_true A!=0 goto label1
if A<=22 goto label1
if_cc A goto label1
if_vc X goto label1
if_pos Y goto label1
if_true XY goto label1
if_not XY goto label1
if A goto label1
if_not goto label1
if_true bytevar<=A goto label2
if_true bytevar<=22 goto label2
if bytevar<=22 goto label2
if bytevar<=22 goto label2
if bytevar goto label2
if bytevar>bytevar2 goto label2
if_pos bytevar goto label2
if_not wordvar goto label2
if_zero wordvar goto label2
if_true wordvar goto label2
label2
return
}

View File

@ -6,6 +6,8 @@ import "c64lib"
var .text name = "?"*80
start
c64util.init_state()
XY = c64.CINV
SI = 1
c64.CINV = #irq_handler
@ -18,21 +20,13 @@ start
c64util.print_string(name)
c64.CHROUT('\n')
;if_cc goto label
;if_cc label
;if_cc dsdaf + 33 < 22 label
;if_cc dsdaf + 33 < 22 goto label!
goto label
goto label()
label
SI = 1
c64.CINV = XY
SI = 0
return
irq_handler
asm {
lda $cb