mirror of
https://github.com/irmen/prog8.git
synced 2025-01-10 20:30:23 +00:00
fixing conditional calls
This commit is contained in:
parent
4e4baff9e0
commit
5e16b82418
@ -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)
|
||||
|
596
il65/codegen.py
596
il65/codegen.py
@ -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 = []
|
||||
|
@ -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...")
|
||||
|
104
il65/parse.py
104
il65/parse.py
@ -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:
|
||||
|
@ -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]]:
|
||||
|
@ -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,
|
||||
|
@ -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!!
|
||||
|
14
reference.md
14
reference.md
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
7115
testsource/large.ill
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user