mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
conditional gotos
This commit is contained in:
parent
c07698dfda
commit
44065597ff
@ -69,24 +69,6 @@ def parse_arguments(text: str, sourceref: SourceRef) -> List[Tuple[str, Primitiv
|
|||||||
except SyntaxError as x:
|
except SyntaxError as x:
|
||||||
raise src.to_error(str(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]]
|
args = [] # type: List[Tuple[str, Any]]
|
||||||
if isinstance(nodes, ast.Expression):
|
if isinstance(nodes, ast.Expression):
|
||||||
for arg in nodes.body.args:
|
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")
|
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)
|
src = SourceLine(text, sourceref)
|
||||||
text = src.preprocess()
|
text = src.preprocess()
|
||||||
try:
|
try:
|
||||||
node = ast.parse(text, sourceref.file, mode="eval")
|
node = ast.parse(text, sourceref.file, mode="eval")
|
||||||
except SyntaxError as x:
|
except SyntaxError as x:
|
||||||
raise src.to_error(str(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, *,
|
def parse_expr_as_int(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef, *,
|
||||||
@ -240,3 +245,22 @@ class ExpressionTransformer(EvaluatingTransformer):
|
|||||||
else:
|
else:
|
||||||
raise self.error("invalid MatMult/Pointer node in AST")
|
raise self.error("invalid MatMult/Pointer node in AST")
|
||||||
return node
|
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)
|
||||||
|
195
il65/codegen.py
195
il65/codegen.py
@ -160,7 +160,7 @@ class CodeGenerator:
|
|||||||
self.p("\t\tsta {:s}".format(vname))
|
self.p("\t\tsta {:s}".format(vname))
|
||||||
self.p("\t\tstx {:s}+1".format(vname))
|
self.p("\t\tstx {:s}+1".format(vname))
|
||||||
elif variable.type == DataType.FLOAT:
|
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")
|
self.p("; end init zp vars")
|
||||||
else:
|
else:
|
||||||
self.p("\t\t; there are no zp vars to initialize")
|
self.p("\t\t; there are no zp vars to initialize")
|
||||||
@ -213,8 +213,9 @@ class CodeGenerator:
|
|||||||
asmlines = [
|
asmlines = [
|
||||||
"\t\tcld\t\t\t; clear decimal flag",
|
"\t\tcld\t\t\t; clear decimal flag",
|
||||||
"\t\tclc\t\t\t; clear carry 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
|
break
|
||||||
block.statements = statements
|
block.statements = statements
|
||||||
# generate
|
# generate
|
||||||
@ -286,7 +287,7 @@ class CodeGenerator:
|
|||||||
self.p("\t\t{:s} = {:s}\t; matrix {:d} by {:d} = {:d} bytes"
|
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))
|
.format(vardef.name, Parser.to_hex(vardef.address), vardef.matrixsize[0], vardef.matrixsize[1], vardef.length))
|
||||||
else:
|
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]
|
non_mem_vars = [vi for vi in block.symbols.iter_variables() if vi.allocate]
|
||||||
if non_mem_vars:
|
if non_mem_vars:
|
||||||
self.p("; normal variables")
|
self.p("; normal variables")
|
||||||
@ -305,7 +306,7 @@ class CodeGenerator:
|
|||||||
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}"
|
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}"
|
||||||
.format(vardef.name, *self.to_mflpt5(float(vardef.value))))
|
.format(vardef.name, *self.to_mflpt5(float(vardef.value))))
|
||||||
else:
|
else:
|
||||||
raise TypeError("weird datatype")
|
raise CodeError("weird datatype")
|
||||||
elif vardef.type in (DataType.BYTEARRAY, DataType.WORDARRAY):
|
elif vardef.type in (DataType.BYTEARRAY, DataType.WORDARRAY):
|
||||||
if vardef.address:
|
if vardef.address:
|
||||||
raise CodeError("array or wordarray vars must not have address; will be allocated by assembler")
|
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}"
|
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))
|
.format(vardef.name, vardef.length * 2, f_lo, f_hi, vardef.length, vardef.value or 0))
|
||||||
else:
|
else:
|
||||||
raise TypeError("invalid datatype", vardef.type)
|
raise CodeError("invalid datatype", vardef.type)
|
||||||
elif vardef.type == DataType.MATRIX:
|
elif vardef.type == DataType.MATRIX:
|
||||||
if vardef.address:
|
if vardef.address:
|
||||||
raise CodeError("matrix vars must not have address; will be allocated by assembler")
|
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
|
raise NotImplementedError("decr by > 1") # XXX
|
||||||
|
|
||||||
def generate_call(self, stmt: ParseResult.CallStmt) -> None:
|
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:
|
def generate_param_assignments() -> None:
|
||||||
for assign_stmt in stmt.desugared_call_arguments:
|
for assign_stmt in stmt.desugared_call_arguments:
|
||||||
self.generate_assignment(assign_stmt)
|
self.generate_assignment(assign_stmt)
|
||||||
@ -469,10 +628,12 @@ class CodeGenerator:
|
|||||||
if isinstance(stmt.target, ParseResult.MemMappedValue):
|
if isinstance(stmt.target, ParseResult.MemMappedValue):
|
||||||
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
|
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
|
||||||
else:
|
else:
|
||||||
raise TypeError("call sub target should be mmapped")
|
raise CodeError("call sub target should be mmapped")
|
||||||
if stmt.is_goto:
|
if stmt.is_goto:
|
||||||
generate_param_assignments()
|
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
|
# no result assignments because it's a goto
|
||||||
return
|
return
|
||||||
clobbered = set() # type: Set[str]
|
clobbered = set() # type: Set[str]
|
||||||
@ -504,10 +665,12 @@ class CodeGenerator:
|
|||||||
if targetstr in REGISTER_WORDS:
|
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[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\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:
|
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
|
# no result assignments because it's a goto
|
||||||
|
if line_after_goto:
|
||||||
|
self.p(line_after_goto)
|
||||||
else:
|
else:
|
||||||
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
|
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
|
||||||
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
|
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
|
||||||
@ -551,8 +714,10 @@ class CodeGenerator:
|
|||||||
if stmt.is_goto:
|
if stmt.is_goto:
|
||||||
# no need to preserve registers for a goto
|
# no need to preserve registers for a goto
|
||||||
generate_param_assignments()
|
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
|
# no result assignments because it's a goto
|
||||||
|
if line_after_goto:
|
||||||
|
self.p(line_after_goto)
|
||||||
else:
|
else:
|
||||||
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
|
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
|
||||||
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
|
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
|
||||||
@ -813,7 +978,7 @@ class CodeGenerator:
|
|||||||
if lv.name:
|
if lv.name:
|
||||||
symblock, sym = self.cur_block.lookup(lv.name)
|
symblock, sym = self.cur_block.lookup(lv.name)
|
||||||
if not isinstance(sym, VariableDef):
|
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
|
assign_target = symblock.label + '.' + sym.name if symblock is not self.cur_block else lv.name
|
||||||
lvdatatype = sym.type
|
lvdatatype = sym.type
|
||||||
else:
|
else:
|
||||||
@ -836,10 +1001,10 @@ class CodeGenerator:
|
|||||||
self.p("\t\tsta {}+1".format(assign_target))
|
self.p("\t\tsta {}+1".format(assign_target))
|
||||||
elif lvdatatype == DataType.FLOAT:
|
elif lvdatatype == DataType.FLOAT:
|
||||||
if rvalue.value is not None and not DataType.FLOAT.assignable_from_value(rvalue.value):
|
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)
|
self.generate_assign_float_to_mem(lv, rvalue, False)
|
||||||
else:
|
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:
|
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)
|
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\tlda #0")
|
||||||
self.p("\t\tsta {}+1".format(assign_target))
|
self.p("\t\tsta {}+1".format(assign_target))
|
||||||
else:
|
else:
|
||||||
raise TypeError("invalid lvalue type " + str(sym))
|
raise CodeError("invalid lvalue type " + str(sym))
|
||||||
else:
|
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:
|
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)
|
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
|
||||||
|
157
il65/parse.py
157
il65/parse.py
@ -13,9 +13,9 @@ import sys
|
|||||||
import shutil
|
import shutil
|
||||||
import enum
|
import enum
|
||||||
from collections import defaultdict
|
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,\
|
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, \
|
from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \
|
||||||
zeropage, check_value_in_range, char_to_bytevalue, \
|
zeropage, check_value_in_range, char_to_bytevalue, \
|
||||||
PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \
|
PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \
|
||||||
@ -254,7 +254,10 @@ class ParseResult:
|
|||||||
return False, "can only assign an integer constant value of 0 or 1 to SC and SI"
|
return False, "can only assign an integer constant value of 0 or 1 to SC and SI"
|
||||||
if self.constant:
|
if self.constant:
|
||||||
return False, "cannot assign to a constant"
|
return False, "cannot assign to a constant"
|
||||||
if isinstance(other, ParseResult.RegisterValue) and len(self.register) < len(other.register):
|
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"
|
return False, "register size mismatch"
|
||||||
if isinstance(other, ParseResult.StringValue) and self.register in REGISTER_BYTES:
|
if isinstance(other, ParseResult.StringValue) and self.register in REGISTER_BYTES:
|
||||||
return False, "string address requires 16 bits combined register"
|
return False, "string address requires 16 bits combined register"
|
||||||
@ -345,22 +348,24 @@ class ParseResult:
|
|||||||
return False, "incompatible value for assignment"
|
return False, "incompatible value for assignment"
|
||||||
|
|
||||||
class _AstNode: # @todo merge Value with this?
|
class _AstNode: # @todo merge Value with this?
|
||||||
pass
|
def __init__(self, lineno: int) -> None:
|
||||||
|
self.lineno = lineno
|
||||||
|
|
||||||
class Comment(_AstNode):
|
class Comment(_AstNode):
|
||||||
def __init__(self, text: str) -> None:
|
def __init__(self, text: str, lineno: int) -> None:
|
||||||
|
super().__init__(lineno)
|
||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
class Label(_AstNode):
|
class Label(_AstNode):
|
||||||
def __init__(self, name: str, lineno: int) -> None:
|
def __init__(self, name: str, lineno: int) -> None:
|
||||||
|
super().__init__(lineno)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.lineno = lineno
|
|
||||||
|
|
||||||
class AssignmentStmt(_AstNode):
|
class AssignmentStmt(_AstNode):
|
||||||
def __init__(self, leftvalues: List['ParseResult.Value'], right: 'ParseResult.Value', lineno: int) -> None:
|
def __init__(self, leftvalues: List['ParseResult.Value'], right: 'ParseResult.Value', lineno: int) -> None:
|
||||||
|
super().__init__(lineno)
|
||||||
self.leftvalues = leftvalues
|
self.leftvalues = leftvalues
|
||||||
self.right = right
|
self.right = right
|
||||||
self.lineno = lineno
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<Assign {:s} to {:s}>".format(str(self.right), ",".join(str(lv) for lv in self.leftvalues))
|
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)
|
return all(lv == self.right for lv in self.leftvalues)
|
||||||
|
|
||||||
class ReturnStmt(_AstNode):
|
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,
|
x: Optional['ParseResult.Value']=None,
|
||||||
y: Optional['ParseResult.Value']=None) -> None:
|
y: Optional['ParseResult.Value']=None) -> None:
|
||||||
|
super().__init__(lineno)
|
||||||
self.a = a
|
self.a = a
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
|
|
||||||
class IncrDecrStmt(_AstNode):
|
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.what = what
|
||||||
self.howmuch = howmuch
|
self.howmuch = howmuch
|
||||||
|
|
||||||
class CallStmt(_AstNode):
|
class CallStmt(_AstNode):
|
||||||
def __init__(self, lineno: int, target: Optional['ParseResult.Value']=None, *,
|
def __init__(self, lineno: int, target: Optional['ParseResult.Value']=None, *,
|
||||||
address: Optional[int]=None, arguments: List[Tuple[str, Any]]=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:
|
outputs: List[Tuple[str, 'ParseResult.Value']]=None, is_goto: bool=False,
|
||||||
self.lineno = lineno
|
preserve_regs: bool=True, condition: 'ParseResult.IfCondition'=None) -> None:
|
||||||
|
if not is_goto:
|
||||||
|
assert condition is None
|
||||||
|
super().__init__(lineno)
|
||||||
self.target = target
|
self.target = target
|
||||||
self.address = address
|
self.address = address
|
||||||
self.arguments = arguments
|
self.arguments = arguments
|
||||||
self.outputvars = outputs
|
self.outputvars = outputs
|
||||||
self.is_goto = is_goto
|
self.is_goto = is_goto
|
||||||
|
self.condition = condition
|
||||||
self.preserve_regs = preserve_regs
|
self.preserve_regs = preserve_regs
|
||||||
self.desugared_call_arguments = [] # type: List[ParseResult.AssignmentStmt]
|
self.desugared_call_arguments = [] # type: List[ParseResult.AssignmentStmt]
|
||||||
self.desugared_output_assignments = [] # type: List[ParseResult.AssignmentStmt]
|
self.desugared_output_assignments = [] # type: List[ParseResult.AssignmentStmt]
|
||||||
@ -438,10 +449,56 @@ class ParseResult:
|
|||||||
self.desugared_output_assignments.append(assignment)
|
self.desugared_output_assignments.append(assignment)
|
||||||
|
|
||||||
class InlineAsm(_AstNode):
|
class InlineAsm(_AstNode):
|
||||||
def __init__(self, lineno: int, asmlines: List[str]) -> None:
|
def __init__(self, asmlines: List[str], lineno: int) -> None:
|
||||||
self.lineno = lineno
|
super().__init__(lineno)
|
||||||
self.asmlines = asmlines
|
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:
|
def add_block(self, block: 'ParseResult.Block', position: Optional[int]=None) -> None:
|
||||||
if position is not None:
|
if position is not None:
|
||||||
self.blocks.insert(position, block)
|
self.blocks.insert(position, block)
|
||||||
@ -550,7 +607,7 @@ class Parser:
|
|||||||
while True:
|
while True:
|
||||||
line = self.next_line().lstrip()
|
line = self.next_line().lstrip()
|
||||||
if line.startswith(';'):
|
if line.startswith(';'):
|
||||||
self.cur_block.statements.append(ParseResult.Comment(line))
|
self.cur_block.statements.append(ParseResult.Comment(line, self.sourceref.line))
|
||||||
continue
|
continue
|
||||||
self.prev_line()
|
self.prev_line()
|
||||||
break
|
break
|
||||||
@ -1040,6 +1097,17 @@ class Parser:
|
|||||||
return varname, datatype, length, matrix_dimensions, valuetext
|
return varname, datatype, length, matrix_dimensions, valuetext
|
||||||
|
|
||||||
def parse_statement(self, line: str) -> ParseResult._AstNode:
|
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)
|
match = re.fullmatch(r"goto\s+(?P<subname>[\S]+?)\s*(\((?P<arguments>.*)\))?\s*", line)
|
||||||
if match:
|
if match:
|
||||||
# goto
|
# goto
|
||||||
@ -1047,8 +1115,7 @@ class Parser:
|
|||||||
subname = groups["subname"]
|
subname = groups["subname"]
|
||||||
if '!' in subname:
|
if '!' in subname:
|
||||||
raise self.PError("goto is always without register preservation, should not have exclamation mark")
|
raise self.PError("goto is always without register preservation, should not have exclamation mark")
|
||||||
arguments = groups["arguments"]
|
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True)
|
||||||
return self.parse_call_or_goto(subname, arguments, None, False, True)
|
|
||||||
match = re.fullmatch(r"(?P<outputs>[^\(]*\s*=)?\s*(?P<subname>[\S]+?)\s*(?P<fcall>[!]?)\s*(\((?P<arguments>.*)\))?\s*", line)
|
match = re.fullmatch(r"(?P<outputs>[^\(]*\s*=)?\s*(?P<subname>[\S]+?)\s*(?P<fcall>[!]?)\s*(\((?P<arguments>.*)\))?\s*", line)
|
||||||
if match:
|
if match:
|
||||||
# subroutine call (not a goto) with possible output param assignment
|
# subroutine call (not a goto) with possible output param assignment
|
||||||
@ -1070,7 +1137,7 @@ class Parser:
|
|||||||
what = self.parse_expression(line[:-2].rstrip())
|
what = self.parse_expression(line[:-2].rstrip())
|
||||||
if isinstance(what, ParseResult.IntegerValue):
|
if isinstance(what, ParseResult.IntegerValue):
|
||||||
raise self.PError("cannot in/decrement a constant value")
|
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:
|
else:
|
||||||
# perhaps it is an assignment statment
|
# perhaps it is an assignment statment
|
||||||
lhs, sep, rhs = line.partition("=")
|
lhs, sep, rhs = line.partition("=")
|
||||||
@ -1079,7 +1146,9 @@ class Parser:
|
|||||||
raise self.PError("invalid statement")
|
raise self.PError("invalid statement")
|
||||||
|
|
||||||
def parse_call_or_goto(self, targetstr: str, argumentstr: str, outputstr: str,
|
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 ""
|
argumentstr = argumentstr.strip() if argumentstr else ""
|
||||||
outputstr = outputstr.strip() if outputstr else ""
|
outputstr = outputstr.strip() if outputstr else ""
|
||||||
arguments = None
|
arguments = None
|
||||||
@ -1106,6 +1175,8 @@ class Parser:
|
|||||||
except ParseError:
|
except ParseError:
|
||||||
symbol = None # it's probably a number or a register then
|
symbol = None # it's probably a number or a register then
|
||||||
if isinstance(symbol, SubroutineDef):
|
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
|
# verify subroutine arguments
|
||||||
if len(arguments or []) != len(symbol.parameters):
|
if len(arguments or []) != len(symbol.parameters):
|
||||||
raise self.PError("invalid number of arguments ({:d}, expected {:d})"
|
raise self.PError("invalid number of arguments ({:d}, expected {:d})"
|
||||||
@ -1144,7 +1215,7 @@ class Parser:
|
|||||||
if isinstance(target, (type(None), ParseResult.Value)):
|
if isinstance(target, (type(None), ParseResult.Value)):
|
||||||
if is_goto:
|
if is_goto:
|
||||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address,
|
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:
|
else:
|
||||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address,
|
return ParseResult.CallStmt(self.sourceref.line, target, address=address,
|
||||||
arguments=arguments, outputs=outputvars, preserve_regs=preserve_regs)
|
arguments=arguments, outputs=outputvars, preserve_regs=preserve_regs)
|
||||||
@ -1190,7 +1261,7 @@ class Parser:
|
|||||||
if len(parts) > 1:
|
if len(parts) > 1:
|
||||||
values = parts[1].split(",")
|
values = parts[1].split(",")
|
||||||
if len(values) == 0:
|
if len(values) == 0:
|
||||||
return ParseResult.ReturnStmt()
|
return ParseResult.ReturnStmt(self.sourceref.line)
|
||||||
else:
|
else:
|
||||||
a = self.parse_expression(values[0]) if values[0] else None
|
a = self.parse_expression(values[0]) if values[0] else None
|
||||||
if len(values) > 1:
|
if len(values) > 1:
|
||||||
@ -1199,7 +1270,7 @@ class Parser:
|
|||||||
y = self.parse_expression(values[2]) if values[2] else None
|
y = self.parse_expression(values[2]) if values[2] else None
|
||||||
if len(values) > 3:
|
if len(values) > 3:
|
||||||
raise self.PError("too many returnvalues")
|
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:
|
def parse_asm(self) -> ParseResult.InlineAsm:
|
||||||
line = self.next_line()
|
line = self.next_line()
|
||||||
@ -1211,7 +1282,7 @@ class Parser:
|
|||||||
while True:
|
while True:
|
||||||
line = self.next_line()
|
line = self.next_line()
|
||||||
if line.strip() == "}":
|
if line.strip() == "}":
|
||||||
return ParseResult.InlineAsm(lineno, asmlines)
|
return ParseResult.InlineAsm(asmlines, lineno)
|
||||||
# asm can refer to other symbols as well, track subroutine usage
|
# asm can refer to other symbols as well, track subroutine usage
|
||||||
splits = line.split(maxsplit=1)
|
splits = line.split(maxsplit=1)
|
||||||
if len(splits) == 2:
|
if len(splits) == 2:
|
||||||
@ -1252,7 +1323,7 @@ class Parser:
|
|||||||
lines = ['{:s}\t.binclude "{:s}"'.format(scopename, filename)]
|
lines = ['{:s}\t.binclude "{:s}"'.format(scopename, filename)]
|
||||||
else:
|
else:
|
||||||
raise self.PError("invalid asminclude statement")
|
raise self.PError("invalid asminclude statement")
|
||||||
return ParseResult.InlineAsm(self.sourceref.line, lines)
|
return ParseResult.InlineAsm(lines, self.sourceref.line)
|
||||||
elif aline[0] == "asmbinary":
|
elif aline[0] == "asmbinary":
|
||||||
if len(aline) == 4:
|
if len(aline) == 4:
|
||||||
offset = parse_expr_as_int(aline[2], None, None, self.sourceref)
|
offset = parse_expr_as_int(aline[2], None, None, self.sourceref)
|
||||||
@ -1265,7 +1336,7 @@ class Parser:
|
|||||||
lines = ['\t.binary "{:s}"'.format(filename)]
|
lines = ['\t.binary "{:s}"'.format(filename)]
|
||||||
else:
|
else:
|
||||||
raise self.PError("invalid asmbinary statement")
|
raise self.PError("invalid asmbinary statement")
|
||||||
return ParseResult.InlineAsm(self.sourceref.line, lines)
|
return ParseResult.InlineAsm(lines, self.sourceref.line)
|
||||||
else:
|
else:
|
||||||
raise self.PError("invalid statement")
|
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:])]
|
result = [sentence[i:j].strip(separators) for i, j in zip(indices, indices[1:])]
|
||||||
return list(filter(None, result)) # remove empty strings
|
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:
|
class Optimizer:
|
||||||
def __init__(self, parseresult: ParseResult) -> None:
|
def __init__(self, parseresult: ParseResult) -> None:
|
||||||
@ -1474,12 +1572,23 @@ class Optimizer:
|
|||||||
self.combine_assignments_into_multi(block)
|
self.combine_assignments_into_multi(block)
|
||||||
self.optimize_multiassigns(block)
|
self.optimize_multiassigns(block)
|
||||||
self.remove_unused_subroutines(block)
|
self.remove_unused_subroutines(block)
|
||||||
|
self.optimize_compare_with_zero(block)
|
||||||
for sub in block.symbols.iter_subroutines(True):
|
for sub in block.symbols.iter_subroutines(True):
|
||||||
self.remove_identity_assigns(sub.sub_block)
|
self.remove_identity_assigns(sub.sub_block)
|
||||||
self.combine_assignments_into_multi(sub.sub_block)
|
self.combine_assignments_into_multi(sub.sub_block)
|
||||||
self.optimize_multiassigns(sub.sub_block)
|
self.optimize_multiassigns(sub.sub_block)
|
||||||
|
self.optimize_compare_with_zero(sub.sub_block)
|
||||||
return self.parsed
|
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:
|
def combine_assignments_into_multi(self, block: ParseResult.Block) -> None:
|
||||||
# fold multiple consecutive assignments with the same rvalue into one multi-assignment
|
# fold multiple consecutive assignments with the same rvalue into one multi-assignment
|
||||||
statements = [] # type: List[ParseResult._AstNode]
|
statements = [] # type: List[ParseResult._AstNode]
|
||||||
|
@ -41,7 +41,7 @@ class PreprocessingParser(Parser):
|
|||||||
return self.result
|
return self.result
|
||||||
|
|
||||||
def parse_asminclude(self, line: str) -> ParseResult.InlineAsm:
|
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:
|
def parse_statement(self, line: str) -> ParseResult._AstNode:
|
||||||
return None # type: ignore
|
return None # type: ignore
|
||||||
|
@ -243,6 +243,7 @@ sub init_state () -> (A?, X?, Y?) {
|
|||||||
tax
|
tax
|
||||||
tay
|
tay
|
||||||
clc
|
clc
|
||||||
|
clv
|
||||||
cli
|
cli
|
||||||
rts
|
rts
|
||||||
}
|
}
|
||||||
|
22
reference.md
22
reference.md
@ -311,22 +311,18 @@ TODOS
|
|||||||
|
|
||||||
### Flow Control
|
### 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:
|
- a conditional goto instruction: directly translates to a branch instruction:
|
||||||
if[_XX] goto <label>
|
if[_XX] [<expression>] goto <label>
|
||||||
XX is one of: (cc, cs, vc, vs, eq, ne, pos, min,
|
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, pos, neg, true==ne, not==eq, zero==eq,
|
||||||
lt==cc, lts==min, gt==eq+cs, gts==eq+pos, le==cc+eq, les==neg+eq, ge==cs, ges==pos)
|
lt==cc, gt==eq+cs, le==cc+eq, ge==cs]
|
||||||
and when left out, defaults to ne (not-zero, i.e. true)
|
(@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
|
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>
|
- comparison statement: compares left with right: compare <first_value>, <second_value>
|
||||||
(and keeps the comparison result in the status register.)
|
(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.
|
this translates into a lda first_value, cmp second_value sequence after which a conditional branch is possible.
|
||||||
|
44
testsource/conditionals.ill
Normal file
44
testsource/conditionals.ill
Normal 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
|
||||||
|
}
|
@ -6,6 +6,8 @@ import "c64lib"
|
|||||||
var .text name = "?"*80
|
var .text name = "?"*80
|
||||||
|
|
||||||
start
|
start
|
||||||
|
c64util.init_state()
|
||||||
|
|
||||||
XY = c64.CINV
|
XY = c64.CINV
|
||||||
SI = 1
|
SI = 1
|
||||||
c64.CINV = #irq_handler
|
c64.CINV = #irq_handler
|
||||||
@ -18,21 +20,13 @@ start
|
|||||||
c64util.print_string(name)
|
c64util.print_string(name)
|
||||||
c64.CHROUT('\n')
|
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
|
SI = 1
|
||||||
c64.CINV = XY
|
c64.CINV = XY
|
||||||
SI = 0
|
SI = 0
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
irq_handler
|
irq_handler
|
||||||
asm {
|
asm {
|
||||||
lda $cb
|
lda $cb
|
||||||
|
Loading…
x
Reference in New Issue
Block a user