fixing conditional calls

This commit is contained in:
Irmen de Jong 2017-12-28 19:08:33 +01:00
parent 4e4baff9e0
commit 5e16b82418
12 changed files with 7809 additions and 309 deletions

View File

@ -282,5 +282,7 @@ def astnode_to_repr(node: ast.AST) -> str:
return "~" + astnode_to_repr(node.operand)
if isinstance(node.op, ast.Not):
return "not " + astnode_to_repr(node.operand)
print("error", ast.dump(node))
if isinstance(node, ast.List):
# indirect values get turned into a list...
return "[" + ",".join(astnode_to_repr(elt) for elt in node.elts) + "]"
raise TypeError("invalid arg ast node type", node)

View File

@ -13,7 +13,7 @@ import datetime
import subprocess
import contextlib
from functools import partial
from typing import TextIO, Set, Union, List
from typing import TextIO, Set, Union, List, Tuple, Callable
from .parse import ProgramFormat, ParseResult, Parser
from .symbols import Zeropage, DataType, ConstantDef, VariableDef, SubroutineDef, \
STRING_DATATYPES, REGISTER_WORDS, REGISTER_BYTES, FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE
@ -481,168 +481,299 @@ class CodeGenerator:
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)
self._generate_goto_conditional_comparison(stmt)
else:
# the condition is just the 'truth value' of the single 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 more register pair comparisons
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 more word value comparisons
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", # @todo comparisons of other types
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)
self._generate_goto_conditional_truthvalue(stmt)
else:
self._generate_goto_conditional_if(stmt)
else:
self._generate_call(stmt)
# unconditional goto or subroutine call.
def branch_emitter(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
if is_goto:
if target_indirect:
self.p("\t\tjmp ({:s})".format(targetstr))
else:
self.p("\t\tjmp {:s}".format(targetstr))
else:
assert not target_indirect
self.p("\t\tjsr " + targetstr)
self._generate_call_or_goto(stmt, branch_emitter)
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 if_gt
elif status == "le":
status = "cc + eq" # @todo if_le
elif status == "ge":
status = "cs"
opcodes = {"cc": "bcc",
"cs": "bcs",
"vc": "bvc",
"vs": "bvs",
"eq": "beq",
"ne": "bne",
"pos": "bpl",
"neg": "bmi"}
return opcodes[status]
def _generate_goto_conditional_if(self, stmt):
# a goto with just an if-condition, no condition expression
def branch_emitter(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
ifs = stmt.condition.ifstatus
if target_indirect:
if ifs == "true":
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs in ("not", "zero"):
self.p("\t\tbne +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs in ("cc", "cs", "vc", "vs", "eq", "ne"):
if ifs == "cc":
self.p("\t\tbcs +")
elif ifs == "cs":
self.p("\t\tbcc +")
elif ifs == "vc":
self.p("\t\tbvs +")
elif ifs == "vs":
self.p("\t\tbvc +")
elif ifs == "eq":
self.p("\t\tbne +")
elif ifs == "ne":
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "lt":
self.p("\t\tbcs +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "gt":
self.p("\t\tbcc +")
self.p("\t\tbeq +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "ge":
self.p("\t\tbcc +")
self.p("\t\tjmp ({:s})".format(targetstr))
self.p("+")
elif ifs == "le":
self.p("\t\tbeq +")
self.p("\t\tbcs ++")
self.p("+\t\tjmp ({:s})".format(targetstr))
self.p("+")
else:
raise CodeError("invalid if status " + ifs)
else:
if ifs == "true":
self.p("\t\tbne " + targetstr)
elif ifs in ("not", "zero"):
self.p("\t\tbeq " + targetstr)
elif ifs in ("cc", "cs", "vc", "vs", "eq", "ne"):
self.p("\t\tb{:s} {:s}".format(ifs, targetstr))
elif ifs == "lt":
self.p("\t\tbcc " + targetstr)
elif ifs == "gt":
self.p("\t\tbeq +")
self.p("\t\tbcs " + targetstr)
self.p("+")
elif ifs == "ge":
self.p("\t\tbcs " + targetstr)
elif ifs == "le":
self.p("\t\tbcc " + targetstr)
self.p("\t\tbeq " + targetstr)
else:
raise CodeError("invalid if status " + ifs)
self._generate_call_or_goto(stmt, branch_emitter)
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_goto_conditional_truthvalue(self, stmt: ParseResult.CallStmt) -> None:
# the condition is just the 'truth value' of the single value,
# this is translated into assembly by comparing the argument to zero.
def branch_emitter_mmap(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert not target_indirect
assert stmt.condition.ifstatus in ("true", "not", "zero")
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
cv = stmt.condition.lvalue
assert isinstance(cv, ParseResult.MemMappedValue)
cv_str = cv.name or Parser.to_hex(cv.address)
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_str)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif cv.datatype == DataType.WORD:
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_str)
if stmt.condition.ifstatus == "true":
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
self.p("\t\t{:s} +".format(inverse_branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
cv.datatype, str(cv), self.cur_block.sourceref.file, stmt.lineno)
def branch_emitter_reg(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert not target_indirect
assert stmt.condition.ifstatus in ("true", "not", "zero")
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
line_after_branch = ""
cv = stmt.condition.lvalue
assert 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:
if cv.register == 'AX':
line_after_branch = "+"
self.p("\t\tcmp #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpx #0")
elif cv.register == 'AY':
line_after_branch = "+"
self.p("\t\tcmp #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpy #0")
elif cv.register == 'XY':
line_after_branch = "+"
self.p("\t\tcpx #0")
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
self.p("\t\tcpy #0")
else:
raise CodeError("invalid register", cv.register)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
if line_after_branch:
self.p(line_after_branch)
def branch_emitter_indirect_cond(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
print("EMIT", stmt.target, stmt.condition, is_goto, target_indirect)
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert stmt.condition.ifstatus in ("true", "not", "zero")
cv = stmt.condition.lvalue.value # type: ignore
if isinstance(cv, ParseResult.RegisterValue):
raise CodeError("indirect registers not yet supported") # @todo indirect reg
elif isinstance(cv, ParseResult.MemMappedValue):
raise CodeError("memmapped indirect should not occur, use the variable without indirection")
elif isinstance(cv, ParseResult.IntegerValue) and cv.constant:
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
cv_str = cv.name or Parser.to_hex(cv.value)
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_str)
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif cv.datatype == DataType.WORD:
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_str)
if stmt.condition.ifstatus == "true":
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
self.p("\t\t{:s} +".format(inverse_branch, targetstr))
self.p("\t\tlda {:s}+1".format(cv_str))
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
else:
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
cv.datatype, str(cv), self.cur_block.sourceref.file, stmt.lineno)
else:
raise CodeError("weird indirect type", str(cv))
cv = stmt.condition.lvalue
if isinstance(cv, ParseResult.RegisterValue):
self._generate_call_or_goto(stmt, branch_emitter_reg)
elif isinstance(cv, ParseResult.MemMappedValue):
self._generate_call_or_goto(stmt, branch_emitter_mmap)
elif isinstance(cv, ParseResult.IndirectValue):
if isinstance(cv.value, ParseResult.RegisterValue):
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
elif isinstance(cv.value, ParseResult.MemMappedValue):
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
elif isinstance(cv.value, ParseResult.IntegerValue) and cv.value.constant:
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
else:
raise CodeError("weird indirect type", str(cv))
else:
raise CodeError("need register, memmapped or indirect value", str(cv))
def _generate_goto_conditional_comparison(self, stmt: ParseResult.CallStmt) -> None:
# the condition is lvalue operator rvalue
raise NotImplementedError("no comparisons yet") # XXX comparisons
assert stmt.condition.ifstatus in ("true", "not", "zero")
assert stmt.condition.lvalue != stmt.condition.rvalue # so we know we actually have to compare different things
lv, compare_operator, 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, compare_operator, rv = stmt.condition.swap()
if isinstance(rv, ParseResult.RegisterValue):
# if rv is a register, make sure it comes first instead
lv, compare_operator, 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)
def _generate_call_or_goto(self, stmt: ParseResult.CallStmt, branch_emitter: Callable[[str, bool, bool], None]) -> None:
def generate_param_assignments() -> None:
self.p("; param assignment") # XXX
for assign_stmt in stmt.desugared_call_arguments:
self.generate_assignment(assign_stmt)
self.p("; param assignment done") # XXX
def generate_result_assignments() -> None:
self.p("; result assignment") # XXX
for assign_stmt in stmt.desugared_output_assignments:
self.generate_assignment(assign_stmt)
self.p("; result assignment done") # XXX
def params_load_a() -> bool:
for assign_stmt in stmt.desugared_call_arguments:
@ -657,10 +788,10 @@ class CodeGenerator:
for lv in a.leftvalues:
if isinstance(lv, ParseResult.RegisterValue):
if len(lv.register) == 1:
registers.remove(lv.register)
registers.discard(lv.register)
else:
for r in lv.register:
registers.remove(r)
registers.discard(r)
if stmt.target.name:
symblock, targetdef = self.cur_block.lookup(stmt.target.name)
@ -674,9 +805,7 @@ class CodeGenerator:
raise CodeError("call sub target should be mmapped")
if stmt.is_goto:
generate_param_assignments()
self.p("\t\t{:s} {:s}".format(goto_opcode, targetstr))
if line_after_goto:
self.p(line_after_goto)
branch_emitter(targetstr, True, False)
# no result assignments because it's a goto
return
clobbered = set() # type: Set[str]
@ -686,7 +815,7 @@ class CodeGenerator:
unclobber_result_registers(clobbered, stmt.desugared_output_assignments)
with self.preserving_registers(clobbered, loads_a_within=params_load_a()):
generate_param_assignments()
self.p("\t\tjsr " + targetstr)
branch_emitter(targetstr, False, False)
generate_result_assignments()
return
if isinstance(stmt.target, ParseResult.IndirectValue):
@ -708,13 +837,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\t{:s} ({:s})".format(goto_opcode, Parser.to_hex(Zeropage.SCRATCH_B1)))
branch_emitter(Parser.to_hex(Zeropage.SCRATCH_B1), True, True)
else:
self.p("\t\t{:s} ({:s})".format(goto_opcode, targetstr))
branch_emitter(targetstr, True, True)
# no result assignments because it's a goto
if line_after_goto:
self.p(line_after_goto)
else:
# indirect call to subroutine
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()):
@ -747,6 +875,7 @@ class CodeGenerator:
self.p("+")
generate_result_assignments()
else:
# call to a label or immediate address
if stmt.target.name:
targetstr = stmt.target.name
elif stmt.address is not None:
@ -758,16 +887,14 @@ class CodeGenerator:
if stmt.is_goto:
# no need to preserve registers for a goto
generate_param_assignments()
self.p("\t\t{:s} {:s}".format(goto_opcode, targetstr))
branch_emitter(targetstr, True, False)
# 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)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()):
generate_param_assignments()
self.p("\t\tjsr " + targetstr)
branch_emitter(targetstr, False, False)
generate_result_assignments()
def generate_augmented_assignment(self, stmt: ParseResult.AugmentedAssignmentStmt) -> None:
@ -780,10 +907,105 @@ class CodeGenerator:
self._generate_aug_reg_int(lvalue, stmt.operator, rvalue)
elif isinstance(rvalue, ParseResult.RegisterValue):
self._generate_aug_reg_reg(lvalue, stmt.operator, rvalue)
elif isinstance(rvalue, ParseResult.MemMappedValue):
self._generate_aug_reg_mem(lvalue, stmt.operator, rvalue)
else:
raise CodeError("incr/decr on register only takes int or register for now") # XXX
raise CodeError("invalid rvalue for augmented assignment on register", str(rvalue))
else:
raise CodeError("incr/decr only implemented for registers for now") # XXX
raise CodeError("augmented assignment only implemented for registers for now") # XXX
def _generate_aug_reg_mem(self, lvalue: ParseResult.RegisterValue, operator: str, rvalue: ParseResult.MemMappedValue) -> None:
r_str = rvalue.name or Parser.to_hex(rvalue.address)
if operator == "+=":
self.p("\t\tclc")
if lvalue.register == "A":
self.p("\t\tadc " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tadc " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tadc " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
if lvalue.register == "A":
self.p("\t\tsbc " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tsbc " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tsbc " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if lvalue.register == "A":
self.p("\t\tand " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tand " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tand " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if lvalue.register == "A":
self.p("\t\tora " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tora " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tora " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if lvalue.register == "A":
self.p("\t\teor " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\teor " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\teor " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
raise CodeError("can not yet shift a variable amount") # XXX
elif operator == "<<=":
raise CodeError("can not yet shift a variable amount") # XXX
def _generate_aug_reg_int(self, lvalue: ParseResult.RegisterValue, operator: str, rvalue: ParseResult.IntegerValue) -> None:
r_str = rvalue.name or Parser.to_hex(rvalue.value)
@ -1308,10 +1530,30 @@ class CodeGenerator:
if not loads_a_within:
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
yield
if 'Y' in registers:
self.p("\t\tpla\n\t\ttay")
if 'X' in registers:
self.p("\t\tpla\n\t\ttax")
if 'X' in registers and 'Y' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttay")
self.p("\t\tpla\n\t\ttax")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttay")
self.p("\t\tpla\n\t\ttax")
else:
if 'Y' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttay")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttay")
if 'X' in registers:
if 'A' not in registers:
self.p("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
self.p("\t\tpla\n\t\ttax")
self.p("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
else:
self.p("\t\tpla\n\t\ttax")
if 'A' in registers:
self.p("\t\tpla")
else:
@ -1514,15 +1756,15 @@ class CodeGenerator:
if screencodes:
result += '", {:d}, "'.format(ord(char))
else:
if char == "\f":
if char == '\f':
result += "{clear}"
elif char == "\b":
elif char == '\b':
result += "{delete}"
elif char == "\n":
result += "{lf}"
elif char == "\r":
elif char == '\n':
result += "{cr}"
elif char == "\t":
elif char == '\r':
result += "{down}"
elif char == '\t':
result += "{tab}"
else:
result += '", {:d}, "'.format(ord(char))
@ -1545,7 +1787,7 @@ class Assembler64Tass:
raise ValueError("don't know how to create format "+str(self.format))
try:
if self.format == ProgramFormat.PRG:
print("\ncreating C-64 .prg")
print("\ncreating C-64 prg")
elif self.format == ProgramFormat.RAW:
print("\ncreating raw binary")
try:
@ -1553,7 +1795,7 @@ class Assembler64Tass:
except FileNotFoundError as x:
raise SystemExit("ERROR: cannot run assembler program: "+str(x))
except subprocess.CalledProcessError as x:
print("assembler failed with returncode", x.returncode)
raise SystemExit("assembler failed with returncode " + str(x.returncode))
def generate_breakpoint_list(self, program_filename: str) -> str:
breakpoints = []

View File

@ -37,13 +37,13 @@ def main() -> None:
start = time.perf_counter()
pp = PreprocessingParser(args.sourcefile, )
sourcelines, symbols = pp.preprocess()
symbols.print_table(True)
# symbols.print_table()
p = Parser(args.sourcefile, args.output, sourcelines, ppsymbols=symbols, sub_usage=pp.result.subroutine_usage)
parsed = p.parse()
if parsed:
if args.nooptimize:
p.print_warning("not optimizing the parse tree!")
p.print_bold("not optimizing the parse tree!")
else:
opt = Optimizer(parsed)
parsed = opt.optimize()
@ -57,7 +57,7 @@ def main() -> None:
mon_command_file = assembler.generate_breakpoint_list(program_filename)
duration_total = time.perf_counter() - start
print("Compile duration: {:.2f} seconds".format(duration_total))
p.print_warning("Output file: " + program_filename)
p.print_bold("Output file: " + program_filename)
print()
if args.startvice:
print("Autostart vice emulator...")

View File

@ -48,6 +48,10 @@ class ParseResult:
self.statements = [] # type: List[ParseResult._AstNode]
self.symbols = SymbolTable(name, parent_scope, self)
@property
def ignore(self) -> bool:
return not self.name and not self.address
@property
def label_names(self) -> Set[str]:
return {symbol.name for symbol in self.symbols.iter_labels()}
@ -96,6 +100,7 @@ class ParseResult:
return False, "incompatible value for assignment"
class IndirectValue(Value):
# only constant integers, memmapped and register values are wrapped in this.
def __init__(self, value: 'ParseResult.Value', type_modifier: DataType) -> None:
assert type_modifier
super().__init__(type_modifier, value.name, False)
@ -480,28 +485,14 @@ class ParseResult:
">=": "<=",
"<": ">",
">": "<"}
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"}
IF_STATUSES = {"cc", "cs", "vc", "vs", "eq", "ne", "true", "not", "zero", "lt", "gt", "le", "ge"}
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 ifstatus in self.IF_STATUSES
assert operator in (None, "") or operator in self.SWAPPED_OPERATOR
if operator:
assert ifstatus in ("true", "not")
assert ifstatus in ("true", "not", "zero")
super().__init__(lineno)
self.ifstatus = ifstatus
self.lvalue = leftvalue
@ -509,11 +500,16 @@ class ParseResult:
self.rvalue = rightvalue
def __str__(self):
return "<IfCondition {} {:s} {}>".format(self.lvalue, self.comparison_op, self.rvalue)
return "<IfCondition if_{:s} {} {:s} {}>".format(self.ifstatus, 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 make_if_true(self) -> bool:
# makes a condition of the form if_not a < b into: if a > b (gets rid of the not)
# returns whether the change was made or not
if self.ifstatus == "not" and self.comparison_op:
self.ifstatus = "true"
self.comparison_op = self.SWAPPED_OPERATOR[self.comparison_op]
return True
return False
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
@ -621,7 +617,10 @@ class Parser:
self._parse_2()
return self.result
def print_warning(self, text: str) -> None:
def print_warning(self, text: str, sourceref: SourceRef=None) -> None:
self.print_bold("warning: {}: {:s}".format(sourceref or self.sourceref, text))
def print_bold(self, text: str) -> None:
if sys.stdout.isatty():
print("\x1b[1m" + text + "\x1b[0m")
else:
@ -664,12 +663,12 @@ class Parser:
if "start" not in block.label_names:
self.sourceref.line = block.sourceref.line
self.sourceref.column = 0
raise self.PError("The 'main' block should contain the program entry point 'start'")
raise self.PError("block 'main' should contain the program entry point 'start'")
self._check_return_statement(block, "'main' block")
for sub in block.symbols.iter_subroutines(True):
self._check_return_statement(sub.sub_block, "'{:s}' subroutine".format(sub.name))
self._check_return_statement(sub.sub_block, "subroutine '{:s}'".format(sub.name))
if not main_found:
raise self.PError("A block named 'main' should be defined for the program's entry point 'start'")
raise self.PError("a block 'main' should be defined and contain the program's entry point label 'start'")
def _check_return_statement(self, block: ParseResult.Block, message: str) -> None:
# find last statement that isn't a comment
@ -690,7 +689,7 @@ class Parser:
continue
break
break
self.print_warning("warning: {}: The {:s} doesn't end with a return statement".format(block.sourceref, message))
self.print_warning("{:s} doesn't end with a return statement".format(message), block.sourceref)
def _parse_2(self) -> None:
# parsing pass 2 (not done during preprocessing!)
@ -886,7 +885,7 @@ class Parser:
def create_import_parser(self, filename: str, outputdir: str) -> 'Parser':
return Parser(filename, outputdir, parsing_import=True, ppsymbols=self.ppsymbols, sub_usage=self.result.subroutine_usage)
def parse_block(self) -> ParseResult.Block:
def parse_block(self) -> Optional[ParseResult.Block]:
# first line contains block header "~ [name] [addr]" followed by a '{'
self._parse_comments()
line = self.next_line()
@ -944,6 +943,20 @@ class Parser:
print(" parsing block '{:s}' at ${:04x}".format(self.cur_block.name, self.cur_block.address))
else:
print(" parsing block '{:s}'".format(self.cur_block.name))
if self.cur_block.ignore:
# just skip the lines until we hit a '}' that closes the block
nesting_level = 1
while True:
line = self.next_line().strip()
if line.endswith("{"):
nesting_level += 1
elif line == "}":
nesting_level -= 1
if nesting_level == 0:
self.print_warning("ignoring block without name and address", self.cur_block.sourceref)
return None
else:
raise self.PError("invalid statement in block")
while True:
self._parse_comments()
line = self.next_line()
@ -952,8 +965,8 @@ class Parser:
if line == "}":
if is_zp_block and any(b.name == "ZP" for b in self.result.blocks):
return None # we already have the ZP block
if not self.cur_block.name and not self.cur_block.address:
self.print_warning("warning: {}: Ignoring block without name and address.".format(self.cur_block.sourceref))
if self.cur_block.ignore:
self.print_warning("ignoring block without name and address", self.cur_block.sourceref)
return None
return self.cur_block
if line.startswith(("var ", "var\t")):
@ -977,7 +990,7 @@ class Parser:
self.cur_block.statements.append(self.parse_asm())
elif line == "breakpoint":
self.cur_block.statements.append(ParseResult.BreakpointStmt(self.sourceref.line))
self.print_warning("warning: {}: breakpoint defined".format(self.sourceref))
self.print_warning("breakpoint defined")
elif unstripped_line.startswith((" ", "\t")):
if is_zp_block:
raise self.PError("ZP block cannot contain code statements")
@ -1174,7 +1187,7 @@ class Parser:
return self.parse_augmented_assignment(match.group("left"), match.group("assignment"), match.group("right"))
# a normal assignment perhaps?
splits = [s.strip() for s in line.split('=')]
if all(splits):
if len(splits) > 1 and all(splits):
return self.parse_assignment(*splits)
raise self.PError("invalid statement")
@ -1232,7 +1245,7 @@ class Parser:
.format(len(outputs), len(symbol.return_registers)))
outputvars = list(zip(symbol.return_registers, (self.parse_expression(out) for out in outputs)))
else:
self.print_warning("warning: {}: return values discarded".format(self.sourceref))
self.print_warning("return values discarded")
else:
if outputstr:
raise self.PError("this subroutine doesn't have output parameters")
@ -1303,14 +1316,14 @@ class Parser:
elif r_value.value < 0: # type: ignore
return ParseResult.InplaceDecrStmt(l_value, -r_value.value, self.sourceref.line) # type: ignore
else:
self.print_warning("warning: {}: incr with zero, ignored".format(self.sourceref))
self.print_warning("incr with zero, ignored")
else:
if r_value.value > 0: # type: ignore
return ParseResult.InplaceDecrStmt(l_value, r_value.value, self.sourceref.line) # type: ignore
elif r_value.value < 0: # type: ignore
return ParseResult.InplaceIncrStmt(l_value, -r_value.value, self.sourceref.line) # type: ignore
else:
self.print_warning("warning: {}: decr with zero, ignored".format(self.sourceref))
self.print_warning("decr with zero, ignored")
return ParseResult.AugmentedAssignmentStmt(l_value, operator, r_value, self.sourceref.line)
def parse_return(self, line: str) -> ParseResult.ReturnStmt:
@ -1557,7 +1570,7 @@ class Parser:
if datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX) and type(value) is float:
frac = math.modf(value) # type:ignore
if frac != 0:
self.print_warning("warning: {}: Float value truncated.".format(sourceref))
self.print_warning("float value truncated")
return True, int(value)
return False, value
@ -1599,9 +1612,11 @@ class Parser:
ifstatus = "true"
else:
ifstatus = ifpart[3:]
if ifstatus not in ParseResult.IfCondition.INVERSE_IFSTATUS:
if ifstatus not in ParseResult.IfCondition.IF_STATUSES:
raise self.PError("invalid if form")
if conditionpart:
if ifstatus not in ("true", "not", "zero"):
raise self.PError("can only have if[_true], if_not or if_zero when using a comparison expression")
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)):
@ -1611,15 +1626,16 @@ class Parser:
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)
result = ParseResult.IfCondition(ifstatus, leftv, operator, rightv, self.sourceref.line)
else:
return ParseResult.IfCondition(ifstatus, None, "", None, self.sourceref.line)
result = ParseResult.IfCondition(ifstatus, None, "", None, self.sourceref.line)
if result.make_if_true():
self.print_warning("if_not condition inverted to if")
return result
class Optimizer:
@ -1635,10 +1651,10 @@ class Optimizer:
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)
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:

View File

@ -8,6 +8,7 @@ License: GNU GPL 3.0, see LICENSE
from typing import List, Tuple
from .parse import Parser, ParseResult, SymbolTable, SymbolDefinition
from .symbols import SourceRef
class PreprocessingParser(Parser):
@ -27,7 +28,7 @@ class PreprocessingParser(Parser):
cleanup_table(self.root_scope)
return self.lines, self.root_scope
def print_warning(self, text: str) -> None:
def print_warning(self, text: str, sourceref: SourceRef=None) -> None:
pass
def load_source(self, filename: str) -> List[Tuple[int, str]]:

View File

@ -186,13 +186,21 @@ class SubroutineDef(SymbolDefinition):
raise SymbolError("invalid parameter spec: " + param)
for register in returnvalues:
if register in REGISTER_SYMBOLS_RETURNVALUES:
self.clobbered_registers.add(register)
self.return_registers.append(register)
if len(register) == 1:
self.clobbered_registers.add(register)
else:
self.clobbered_registers.add(register[0])
self.clobbered_registers.add(register[1])
elif register[-1] == "?":
for r in register[:-1]:
if r not in REGISTER_SYMBOLS_RETURNVALUES:
raise SymbolError("invalid return value spec: " + r)
self.clobbered_registers.add(r)
if len(r) == 1:
self.clobbered_registers.add(r)
else:
self.clobbered_registers.add(r[0])
self.clobbered_registers.add(r[1])
else:
raise SymbolError("invalid return value spec: " + register)
@ -260,6 +268,8 @@ class SymbolTable:
# such as 'sin' or 'max' are also resolved.
# Does NOT utilize a symbol table from a preprocessing parse phase, only looks in the current.
nameparts = dottedname.split('.')
if not nameparts[0]:
nameparts = nameparts[1:]
if len(nameparts) == 1:
try:
return self, self.symbols[nameparts[0]]
@ -432,38 +442,27 @@ class SymbolTable:
raise SymbolError("problematic symbol '{:s}' from {}; {:s}"
.format(thing.name, thing.owning_block.sourceref, str(x))) from None
def print_table(self, summary_only: bool=False) -> None:
if summary_only:
def count_symbols(symbols: 'SymbolTable') -> int:
count = 0
for s in symbols.symbols.values():
if isinstance(s, SymbolTable):
count += count_symbols(s)
else:
count += 1
return count
print("number of symbols:", count_symbols(self))
else:
def print_symbols(symbols: 'SymbolTable', level: int) -> None:
indent = '\t' * level
print("\n" + indent + "BLOCK:", symbols.name)
for name, s in sorted(symbols.symbols.items(), key=lambda x: str(getattr(x[1], "sourceref", ""))):
if isinstance(s, SymbolTable):
print_symbols(s, level + 1)
elif isinstance(s, SubroutineDef):
print(indent * 2 + "SUB: " + s.name, s.sourceref, sep="\t")
elif isinstance(s, LabelDef):
print(indent * 2 + "LABEL: " + s.name, s.sourceref, sep="\t")
elif isinstance(s, VariableDef):
print(indent * 2 + "VAR: " + s.name, s.sourceref, s.type, sep="\t")
elif isinstance(s, ConstantDef):
print(indent * 2 + "CONST: " + s.name, s.sourceref, s.type, sep="\t")
else:
raise TypeError("invalid symbol def type", s)
print("\nSymbols defined in the symbol table:")
print("------------------------------------")
print_symbols(self, 0)
print()
def print_table(self) -> None:
def print_symbols(symbols: 'SymbolTable', level: int) -> None:
indent = '\t' * level
print("\n" + indent + "BLOCK:", symbols.name)
for name, s in sorted(symbols.symbols.items(), key=lambda x: str(getattr(x[1], "sourceref", ""))):
if isinstance(s, SymbolTable):
print_symbols(s, level + 1)
elif isinstance(s, SubroutineDef):
print(indent * 2 + "SUB: " + s.name, s.sourceref, sep="\t")
elif isinstance(s, LabelDef):
print(indent * 2 + "LABEL: " + s.name, s.sourceref, sep="\t")
elif isinstance(s, VariableDef):
print(indent * 2 + "VAR: " + s.name, s.sourceref, s.type, sep="\t")
elif isinstance(s, ConstantDef):
print(indent * 2 + "CONST: " + s.name, s.sourceref, s.type, sep="\t")
else:
raise TypeError("invalid symbol def type", s)
print("\nSymbols defined in the symbol table:")
print("------------------------------------")
print_symbols(self, 0)
print()
class EvalSymbolDict(dict):
@ -557,9 +556,9 @@ def char_to_bytevalue(character: str, petscii: bool=True) -> int:
# ASCII/UNICODE-to-PETSCII translation table
# Unicode symbols supported that map to a PETSCII character: £ ↑ ← ♠ ♥ ♦ ♣ π ● ○ and various others
ascii_to_petscii_trans = str.maketrans({
'\f': 147, # form feed becomes ClearScreen
'\n': 13, # line feed becomes a RETURN
'\r': 17, # CR becomes CursorDown
'\f': 147, # form feed becomes ClearScreen "{clear}"
'\n': 13, # line feed becomes a RETURN "{cr}" (not a line feed)
'\r': 17, # CR becomes CursorDown "{down}"
'a': 65,
'b': 66,
'c': 67,

View File

@ -147,7 +147,8 @@ sub ABS () -> () = $bc58 ; fac1 = ABS(fac1)
sub SQR () -> (A?, X?, Y?) = $bf71 ; fac1 = SQRT(fac1)
sub EXP () -> (A?, X?, Y?) = $bfed ; fac1 = EXP(fac1) (e ** fac1)
sub NEGOP () -> (A?) = $bfb4 ; switch the sign of fac1
sub RND () -> (A?, X?, Y?) = $e097 ; fac1 = RND()
sub RND () -> (A?, X?, Y?) = $e097 ; fac1 = RND() (use RNDA instead)
sub RNDA (A) -> (A?, X?, Y?) = $e09a ; fac1 = RND(A)
sub COS () -> (A?, X?, Y?) = $e264 ; fac1 = COS(fac1)
sub SIN () -> (A?, X?, Y?) = $e26b ; fac1 = SIN(fac1)
sub TAN () -> (A?, X?, Y?) = $e2b4 ; fac1 = TAN(fac1)
@ -166,8 +167,8 @@ sub HOMECRSR () -> (A?, X?, Y?) = $E566 ; cursor to top left of screen
; ---- C64 kernal routines ----
sub IRQDFRT () -> (A?, X?, Y?) = $EA31 ; default IRQ routine
sub IRQDFEND () -> (A?, X?, Y?) = $EA81 ; default IRQ end/cleanup
sub IRQDFRT () -> (A?, X?, Y?) = $EA31 ; default IRQ routine
sub IRQDFEND () -> (A?, X?, Y?) = $EA81 ; default IRQ end/cleanup
sub CINT () -> (A?, X?, Y?) = $FF81 ; (alias: SCINIT) initialize screen editor and video chip
sub IOINIT () -> (A?, X?) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
sub RAMTAS () -> (A?, X?, Y?) = $FF87 ; initialize RAM, tape buffer, screen
@ -310,7 +311,7 @@ sub GIVAYFAY (sword: AY) -> (A?, X?, Y?) {
}
}
sub FTOSWRDAY () -> (A, Y, X?) {
sub FTOSWRDAY () -> (AY, X?) {
; ---- fac1 to signed word in A/Y
asm {
jsr c64.FTOSWORDYA ; note the inverse Y/A order
@ -321,7 +322,7 @@ sub FTOSWRDAY () -> (A, Y, X?) {
}
}
sub GETADRAY () -> (A, Y, X?) {
sub GETADRAY () -> (AY, X?) {
; ---- fac1 to unsigned word in A/Y
asm {
jsr c64.GETADR ; this uses the inverse order, Y/A
@ -598,6 +599,9 @@ _pr_decimal
}
; @todo string to 32 bit unsigned integer http://www.6502.org/source/strings/ascii-to-32bit.html
sub input_chars (buffer: AX) -> (A?, Y) {
; ---- Input a string (max. 80 chars) from the keyboard.
; It assumes the keyboard is selected as I/O channel!!

View File

@ -142,7 +142,7 @@ For most other types this prefix is not supported.
By default, if not otherwise known, a single byte is assumed. You can add the ``.byte`` or ``.word`` or ``.float``
type identifier suffix to make it clear what data type the address points to.
This addressing mode is only supported for constant (integer) addresses and not for variable types,
unless it is part of a subroutine call statement. For an indirect goto call, the 6502 CPU has a special opcode
unless it is part of a subroutine call statement. For an indirect goto call, the 6502 CPU has a special instruction
(``jmp`` indirect) and an indirect subroutine call (``jsr`` indirect) is synthesized using a couple of instructions.
@ -196,6 +196,8 @@ Block address must be >= $0200 (because $00-$fff is the ZP and $100-$200 is the
You can omit the blockname but then you can only refer to the contents of the block via its absolute address,
which is required in this case. If you omit both, the block is ignored altogether (and a warning is displayed).
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
want to work on later, because the contents of the ignored block are not syntactically parsed.
### Importing, Including and Binary-Including Files
@ -324,16 +326,14 @@ that is translated into a comparison (if needed) and then a conditional branch i
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]
It defaults to 'true' (=='ne', not-zero) if omitted. @todo signed: lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, true, not, zero, lt, gt, le, ge]
It defaults to 'true' (=='ne', not-zero) if omitted. @todo signed: pos, neg, lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] if-statuses
can be used when such a *comparison expression* is used. An example is:
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] and [zero]
if-statuses can be used when such a *comparison expression* is used. An example is:
``if_not A > 55 goto more_iterations``
NOTE: some combination branches such as cc+eq an be peephole optimized see http://www.6502.org/tutorials/compare_beyond.html#2.2
Debugging (with Vice)

View File

@ -22,6 +22,18 @@
bar
goto $c000
goto [$c000.word]
goto [var1]
goto [mem1]
goto var1 ; jumps to the address in var1
goto mem1 ; jumps to the address in mem1
goto #var1 ; strange, but jumps to the location in memory where var1 sits
goto #mem1 ; strange, but jumps to the location in mempory where mem1 sits (points to)
; ----
goto sub1
goto sub2 (1 )
goto sub3 (3)

View File

@ -2,8 +2,79 @@ output prg,sys
import "c64lib"
~ main {
var .word value
memory .word memvalue = $8000
start
A = 100
; conditional if, without conditional expression. needs explicit if status.
if_not goto label
if_true goto label
if_zero goto label
if_cc goto label
if_lt goto label
if_le goto label
if_ge goto label
if_gt goto label
if_cc goto value
if_cc goto memvalue
if_cc goto #value
if_cc goto #memvalue
if_cc goto [value]
if_cc goto [memvalue]
if_cc goto $c000
if_cc goto [$c000.word]
label
; conditional if with a single 'truth' value (register)
if_true A goto label2
if_not A goto label2
if_zero A goto label2
if A goto label2
if_true X goto label2
if_not X goto label2
if_zero X goto label2
if X goto label2
if_true Y goto label2
if_not Y goto label2
if_zero Y goto label2
if Y goto label2
if_true XY goto label2
if_not XY goto label2
if_zero XY goto label2
if XY goto label2
label2
; conditional if with a single 'truth' value (variable)
if_true value goto label3
if_not value goto label3
if_zero value goto label3
if value goto label3
if_true memvalue goto label3
if_not memvalue goto label3
if_zero memvalue goto label3
if memvalue goto label3
; conditional if with a single 'truth' value (indirect address)
if_true [$c000] goto label
if_not [$c000] goto label
if_zero [$c000] goto label
if [$c000] goto label
; if_true [XY] goto label ; @todo support indirect reg
label3
return
}
; @todo temporarily disabled until comparison operators are properly implemented:
~ {
var .text name = "?"*80
var bytevar = 22
var bytevar2 = 23
@ -41,9 +112,9 @@ label1
if_true X>A goto label1
if A<=22 goto label1
if A <= 22 goto label1
if_cc A goto label1
if_vc X goto label1
if_pos Y goto label1
if_zero A goto label1
if_zero X goto label1
if Y goto label1
if_true XY goto label1
if_not XY goto label1
if A goto label1
@ -55,7 +126,7 @@ label1
if bytevar<=22 goto label2
if bytevar goto label2
if bytevar>bytevar2 goto label2
if_pos bytevar goto label2
if_zero bytevar goto label2
if_not wordvar goto label2
if_zero wordvar goto label2
if_true wordvar goto label2

7115
testsource/large.ill Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +1,87 @@
output prg,sys
output prg,sys ; @todo basic
;reg_preserve off ; @todo global option
import "c64lib"
~ main {
var .text name = '?' * 80
var .float myfloat = 2.33
var .text guess = '?' * 80
var secretnumber
var attempts_left = 10
start
A += 1
A -= 1
A &= %10011100
A |= %10011100
A ^= %10011100
A <<= 1
A >>= 1
A += 2
A -= 2
X += 1
X -= 1
X += 2
X -= 2
Y += 1
Y -= 1
Y += 2
Y -= 2
c64util.init_system()
A = c64.VMCSB
;A |= 2 ; @todo A = A | 2 (complex expressions)
A |= 2 ; @todo c64.VMCSB |= 2
c64.VMCSB = A
;c64.VMCSB |= 2 ; @todo c64.VMCSB |= 2
; greeting
c64util.print_string("Enter your name: ")
c64util.input_chars(name)
Y = c64util.input_chars(name)
c64.CHROUT('\n')
c64.CHROUT('\n')
c64util.print_string("Hello, ")
c64util.print_string(name)
c64.CHROUT('.')
c64.CHROUT('\n')
A = 0
; create a secret random number from 1-100
c64.RNDA(0)
c64.MUL10()
c64.MUL10()
c64.FADDH()
c64.FADDH()
AY = c64util.GETADRAY()
secretnumber = A
c64util.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n")
printloop
c64util.print_byte_decimal(A)
c64util.print_string("\nYou have ")
c64util.print_byte_decimal(attempts_left)
c64util.print_string(" guesses left.\nWhat is your next guess? ")
A = c64util.input_chars(guess)
c64.CHROUT('\n')
A++
if A <20 goto printloop
[$22.word] = guess
c64.FREADSTR(A)
AY = c64util.GETADRAY()
A -= secretnumber
if_zero goto correct_guess
if_gt goto too_high
c64util.print_string("That is too ")
c64util.print_string("low!\n")
goto continue
correct_guess
c64util.print_string("\nImpressive!\n")
bye()
return
too_high
c64util.print_string("That is too ")
c64util.print_string("high!\n")
continue
attempts_left--
if_zero attempts_left goto game_over
goto printloop
game_over
c64util.print_string("\nToo bad! It was: ")
c64util.print_byte_decimal(secretnumber)
c64.CHROUT('\n')
bye()
return
sub bye ()->() {
;var x ; @todo vars in sub
;memory y = $c000 ; @todo vars in sub
;const q = 22 ; @todo const in sub
c64.CHROUT('\n')
;c64util.print_string("Thanks for playing. Bye!\n\n") ;@todo string values should work in sub too
return
}
}