mirror of
https://github.com/irmen/prog8.git
synced 2024-12-24 01:29:28 +00:00
conditional gotos
This commit is contained in:
parent
c07698dfda
commit
44065597ff
@ -69,24 +69,6 @@ def parse_arguments(text: str, sourceref: SourceRef) -> List[Tuple[str, Primitiv
|
||||
except SyntaxError as x:
|
||||
raise src.to_error(str(x))
|
||||
|
||||
def astnode_to_repr(node: ast.AST) -> str:
|
||||
if isinstance(node, ast.Name):
|
||||
return node.id
|
||||
if isinstance(node, ast.Num):
|
||||
return repr(node.n)
|
||||
if isinstance(node, ast.Str):
|
||||
return repr(node.s)
|
||||
if isinstance(node, ast.BinOp):
|
||||
if node.left.id == "__ptr" and isinstance(node.op, ast.MatMult): # type: ignore
|
||||
return '#' + astnode_to_repr(node.right)
|
||||
else:
|
||||
print("error", ast.dump(node))
|
||||
raise TypeError("invalid arg ast node type", node)
|
||||
if isinstance(node, ast.Attribute):
|
||||
return astnode_to_repr(node.value) + "." + node.attr
|
||||
print("error", ast.dump(node))
|
||||
raise TypeError("invalid arg ast node type", node)
|
||||
|
||||
args = [] # type: List[Tuple[str, Any]]
|
||||
if isinstance(nodes, ast.Expression):
|
||||
for arg in nodes.body.args:
|
||||
@ -100,14 +82,37 @@ def parse_arguments(text: str, sourceref: SourceRef) -> List[Tuple[str, Primitiv
|
||||
raise TypeError("ast.Expression expected")
|
||||
|
||||
|
||||
def parse_expr_as_comparison(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef) -> None:
|
||||
def parse_expr_as_comparison(text: str, sourceref: SourceRef) -> Tuple[str, str, str]:
|
||||
src = SourceLine(text, sourceref)
|
||||
text = src.preprocess()
|
||||
try:
|
||||
node = ast.parse(text, sourceref.file, mode="eval")
|
||||
except SyntaxError as x:
|
||||
raise src.to_error(str(x))
|
||||
print("AST NODE", node)
|
||||
if not isinstance(node, ast.Expression):
|
||||
raise TypeError("ast.Expression expected")
|
||||
if isinstance(node.body, ast.Compare):
|
||||
if len(node.body.ops) != 1:
|
||||
raise src.to_error("only one comparison operator at a time is supported")
|
||||
operator = {
|
||||
"Eq": "==",
|
||||
"NotEq": "!=",
|
||||
"Lt": "<",
|
||||
"LtE": "<=",
|
||||
"Gt": ">",
|
||||
"GtE": ">=",
|
||||
"Is": None,
|
||||
"IsNot": None,
|
||||
"In": None,
|
||||
"NotIn": None
|
||||
}[node.body.ops[0].__class__.__name__]
|
||||
if not operator:
|
||||
raise src.to_error("unsupported comparison operator")
|
||||
left = text[node.body.left.col_offset:node.body.comparators[0].col_offset-len(operator)]
|
||||
right = text[node.body.comparators[0].col_offset:]
|
||||
return left.strip(), operator, right.strip()
|
||||
left = astnode_to_repr(node.body)
|
||||
return left, "", ""
|
||||
|
||||
|
||||
def parse_expr_as_int(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef, *,
|
||||
@ -240,3 +245,22 @@ class ExpressionTransformer(EvaluatingTransformer):
|
||||
else:
|
||||
raise self.error("invalid MatMult/Pointer node in AST")
|
||||
return node
|
||||
|
||||
|
||||
def astnode_to_repr(node: ast.AST) -> str:
|
||||
if isinstance(node, ast.Name):
|
||||
return node.id
|
||||
if isinstance(node, ast.Num):
|
||||
return repr(node.n)
|
||||
if isinstance(node, ast.Str):
|
||||
return repr(node.s)
|
||||
if isinstance(node, ast.BinOp):
|
||||
if node.left.id == "__ptr" and isinstance(node.op, ast.MatMult): # type: ignore
|
||||
return '#' + astnode_to_repr(node.right)
|
||||
else:
|
||||
print("error", ast.dump(node))
|
||||
raise TypeError("invalid arg ast node type", node)
|
||||
if isinstance(node, ast.Attribute):
|
||||
return astnode_to_repr(node.value) + "." + node.attr
|
||||
print("error", ast.dump(node))
|
||||
raise TypeError("invalid arg ast node type", node)
|
||||
|
195
il65/codegen.py
195
il65/codegen.py
@ -160,7 +160,7 @@ class CodeGenerator:
|
||||
self.p("\t\tsta {:s}".format(vname))
|
||||
self.p("\t\tstx {:s}+1".format(vname))
|
||||
elif variable.type == DataType.FLOAT:
|
||||
raise TypeError("floats cannot be stored in the zp")
|
||||
raise CodeError("floats cannot be stored in the zp")
|
||||
self.p("; end init zp vars")
|
||||
else:
|
||||
self.p("\t\t; there are no zp vars to initialize")
|
||||
@ -213,8 +213,9 @@ class CodeGenerator:
|
||||
asmlines = [
|
||||
"\t\tcld\t\t\t; clear decimal flag",
|
||||
"\t\tclc\t\t\t; clear carry flag"
|
||||
"\t\tclv\t\t\t; clear overflow flag"
|
||||
]
|
||||
statements.insert(index+1, ParseResult.InlineAsm(0, asmlines))
|
||||
statements.insert(index+1, ParseResult.InlineAsm(asmlines, 0))
|
||||
break
|
||||
block.statements = statements
|
||||
# generate
|
||||
@ -286,7 +287,7 @@ class CodeGenerator:
|
||||
self.p("\t\t{:s} = {:s}\t; matrix {:d} by {:d} = {:d} bytes"
|
||||
.format(vardef.name, Parser.to_hex(vardef.address), vardef.matrixsize[0], vardef.matrixsize[1], vardef.length))
|
||||
else:
|
||||
raise ValueError("invalid var type")
|
||||
raise CodeError("invalid var type")
|
||||
non_mem_vars = [vi for vi in block.symbols.iter_variables() if vi.allocate]
|
||||
if non_mem_vars:
|
||||
self.p("; normal variables")
|
||||
@ -305,7 +306,7 @@ class CodeGenerator:
|
||||
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}"
|
||||
.format(vardef.name, *self.to_mflpt5(float(vardef.value))))
|
||||
else:
|
||||
raise TypeError("weird datatype")
|
||||
raise CodeError("weird datatype")
|
||||
elif vardef.type in (DataType.BYTEARRAY, DataType.WORDARRAY):
|
||||
if vardef.address:
|
||||
raise CodeError("array or wordarray vars must not have address; will be allocated by assembler")
|
||||
@ -316,7 +317,7 @@ class CodeGenerator:
|
||||
self.p("{:s}\t\t.fill {:d}, [${:02x}, ${:02x}]\t; {:d} words of ${:04x}"
|
||||
.format(vardef.name, vardef.length * 2, f_lo, f_hi, vardef.length, vardef.value or 0))
|
||||
else:
|
||||
raise TypeError("invalid datatype", vardef.type)
|
||||
raise CodeError("invalid datatype", vardef.type)
|
||||
elif vardef.type == DataType.MATRIX:
|
||||
if vardef.address:
|
||||
raise CodeError("matrix vars must not have address; will be allocated by assembler")
|
||||
@ -434,6 +435,164 @@ class CodeGenerator:
|
||||
raise NotImplementedError("decr by > 1") # XXX
|
||||
|
||||
def generate_call(self, stmt: ParseResult.CallStmt) -> None:
|
||||
self.p("\t\t\t\t\t; src l. {:d}".format(stmt.lineno))
|
||||
if stmt.condition:
|
||||
assert stmt.is_goto
|
||||
line_after_goto = ""
|
||||
if stmt.condition.lvalue:
|
||||
if stmt.condition.comparison_op:
|
||||
# the condition is lvalue operator rvalue
|
||||
assert stmt.condition.ifstatus in ("true", "not")
|
||||
assert stmt.condition.lvalue != stmt.condition.rvalue # so we know we actually have to compare different things
|
||||
lv, op, rv = stmt.condition.lvalue, stmt.condition.comparison_op, stmt.condition.rvalue
|
||||
if lv.constant and not rv.constant:
|
||||
# if lv is a constant, swap the whole thing around so the constant is on the right
|
||||
lv, op, rv = stmt.condition.swap()
|
||||
if isinstance(rv, ParseResult.RegisterValue):
|
||||
# if rv is a register, make sure it comes first instead
|
||||
lv, op, rv = stmt.condition.swap()
|
||||
if lv.datatype != DataType.BYTE or rv.datatype != DataType.BYTE:
|
||||
raise CodeError("can only generate comparison code for byte values for now") # @todo compare non-bytes
|
||||
if isinstance(lv, ParseResult.RegisterValue):
|
||||
if isinstance(rv, ParseResult.RegisterValue):
|
||||
self.p("\t\tst{:s} {:s}".format(rv.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
|
||||
if lv.register == "A":
|
||||
self.p("\t\tcmp " + Parser.to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lv.register == "X":
|
||||
self.p("\t\tcpx " + Parser.to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lv.register == "Y":
|
||||
self.p("\t\tcpy " + Parser.to_hex(Zeropage.SCRATCH_B1))
|
||||
else:
|
||||
raise CodeError("wrong lvalue register")
|
||||
elif isinstance(rv, ParseResult.IntegerValue):
|
||||
rvstr = rv.name or Parser.to_hex(rv.value)
|
||||
if lv.register == "A":
|
||||
self.p("\t\tcmp #" + rvstr)
|
||||
elif lv.register == "X":
|
||||
self.p("\t\tcpx #" + rvstr)
|
||||
elif lv.register == "Y":
|
||||
self.p("\t\tcpy #" + rvstr)
|
||||
else:
|
||||
raise CodeError("wrong lvalue register")
|
||||
elif isinstance(rv, ParseResult.MemMappedValue):
|
||||
rvstr = rv.name or Parser.to_hex(rv.address)
|
||||
if lv.register == "A":
|
||||
self.p("\t\tcmp " + rvstr)
|
||||
elif lv.register == "X":
|
||||
self.p("\t\tcpx #" + rvstr)
|
||||
elif lv.register == "Y":
|
||||
self.p("\t\tcpy #" + rvstr)
|
||||
else:
|
||||
raise CodeError("wrong lvalue register")
|
||||
else:
|
||||
raise CodeError("invalid rvalue type in comparison", rv)
|
||||
elif isinstance(lv, ParseResult.MemMappedValue):
|
||||
assert not isinstance(rv, ParseResult.RegisterValue), "registers as rvalue should have been swapped with lvalue"
|
||||
if isinstance(rv, ParseResult.IntegerValue):
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
|
||||
self.p("\t\tcmp #" + (rv.name or Parser.to_hex(rv.value)))
|
||||
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
|
||||
elif isinstance(rv, ParseResult.MemMappedValue):
|
||||
rvstr = rv.name or Parser.to_hex(rv.address)
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
|
||||
self.p("\t\tcmp " + rvstr)
|
||||
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
|
||||
else:
|
||||
raise CodeError("invalid rvalue type in comparison", rv)
|
||||
else:
|
||||
raise CodeError("invalid lvalue type in comparison", lv)
|
||||
else:
|
||||
# the condition is just the 'truth value' of the single value, or rather bool(value)
|
||||
# this is translated into assembly by comparing the argument to zero.
|
||||
cv = stmt.condition.lvalue
|
||||
inverse_status = stmt.condition.inverse_ifstatus()
|
||||
if isinstance(cv, ParseResult.RegisterValue):
|
||||
if cv.register == 'A':
|
||||
self.p("\t\tcmp #0")
|
||||
elif cv.register == 'X':
|
||||
self.p("\t\tcpx #0")
|
||||
elif cv.register == 'Y':
|
||||
self.p("\t\tcpy #0")
|
||||
else:
|
||||
# @todo combined register, not all if statuses are supported yet
|
||||
opcode = self._branchopcode(inverse_status)
|
||||
if opcode not in ("beq", "bne"):
|
||||
raise CodeError("cannot yet generate code for register pair that is not a true/false/eq/ne comparison",
|
||||
self.cur_block.sourceref.file, stmt.lineno) # @todo
|
||||
if cv.register == 'AX':
|
||||
line_after_goto = "+"
|
||||
self.p("\t\tcmp #0")
|
||||
self.p("\t\t{:s} {:s}".format(opcode, line_after_goto))
|
||||
self.p("\t\tcpx #0")
|
||||
elif cv.register == 'AY':
|
||||
line_after_goto = "+"
|
||||
self.p("\t\tcmp #0")
|
||||
self.p("\t\t{:s} {:s}".format(opcode, line_after_goto))
|
||||
self.p("\t\tcpy #0")
|
||||
elif cv.register == 'XY':
|
||||
line_after_goto = "+"
|
||||
self.p("\t\tcpx #0")
|
||||
self.p("\t\t{:s} {:s}".format(opcode, line_after_goto))
|
||||
self.p("\t\tcpy #0")
|
||||
else:
|
||||
raise CodeError("invalid register", cv.register)
|
||||
elif isinstance(cv, ParseResult.MemMappedValue):
|
||||
if cv.datatype == DataType.BYTE:
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + (cv.name or Parser.to_hex(cv.address)))
|
||||
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
|
||||
elif cv.datatype == DataType.WORD:
|
||||
# @todo word value, not all if statuses are supported yet
|
||||
opcode = self._branchopcode(inverse_status)
|
||||
if opcode not in ("beq", "bne"):
|
||||
raise CodeError("cannot yet generate code for word value that is not a true/false/eq/ne comparison",
|
||||
self.cur_block.sourceref.file, stmt.lineno) # @todo
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + (cv.name or Parser.to_hex(cv.address)))
|
||||
self.p("\t\t{:s} +".format(opcode))
|
||||
self.p("\t\tlda " + (cv.name or Parser.to_hex(cv.address)))
|
||||
line_after_goto = "+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
|
||||
else:
|
||||
raise CodeError("conditions cannot yet use other types than byte or word", # XXX
|
||||
str(cv), self.cur_block.sourceref.file, stmt.lineno)
|
||||
else:
|
||||
raise CodeError("need register or memmapped value")
|
||||
status = stmt.condition.ifstatus
|
||||
|
||||
self._generate_call(stmt, conditional_goto_opcode=self._branchopcode(status), line_after_goto=line_after_goto)
|
||||
else:
|
||||
self._generate_call(stmt)
|
||||
|
||||
def _branchopcode(self, status: str) -> str:
|
||||
if status == "true":
|
||||
status = "ne"
|
||||
elif status in ("not", "zero"):
|
||||
status = "eq"
|
||||
elif status == "lt":
|
||||
status = "cc"
|
||||
elif status == "gt":
|
||||
status = "eq + cs" # @todo
|
||||
elif status == "le":
|
||||
status = "cc + eq" # @todo
|
||||
elif status == "ge":
|
||||
status = "cs"
|
||||
opcodes = {"cc": "bcc",
|
||||
"cs": "bcs",
|
||||
"vc": "bvc",
|
||||
"vs": "bvs",
|
||||
"eq": "beq",
|
||||
"ne": "bne",
|
||||
"pos": "bpl", # @todo correct?
|
||||
"neg": "bmi"} # @todo correct?
|
||||
return opcodes[status]
|
||||
|
||||
def _generate_call(self, stmt: ParseResult.CallStmt, conditional_goto_opcode: str=None, line_after_goto: str=None) -> None:
|
||||
if stmt.arguments and conditional_goto_opcode:
|
||||
raise CodeError("cannot use a conditional goto when the target requires parameters")
|
||||
goto_opcode = conditional_goto_opcode or "jmp"
|
||||
|
||||
def generate_param_assignments() -> None:
|
||||
for assign_stmt in stmt.desugared_call_arguments:
|
||||
self.generate_assignment(assign_stmt)
|
||||
@ -469,10 +628,12 @@ class CodeGenerator:
|
||||
if isinstance(stmt.target, ParseResult.MemMappedValue):
|
||||
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
|
||||
else:
|
||||
raise TypeError("call sub target should be mmapped")
|
||||
raise CodeError("call sub target should be mmapped")
|
||||
if stmt.is_goto:
|
||||
generate_param_assignments()
|
||||
self.p("\t\tjmp " + targetstr)
|
||||
self.p("\t\t{:s} {:s}".format(goto_opcode, targetstr))
|
||||
if line_after_goto:
|
||||
self.p(line_after_goto)
|
||||
# no result assignments because it's a goto
|
||||
return
|
||||
clobbered = set() # type: Set[str]
|
||||
@ -504,10 +665,12 @@ class CodeGenerator:
|
||||
if targetstr in REGISTER_WORDS:
|
||||
self.p("\t\tst{:s} {:s}".format(targetstr[0].lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
|
||||
self.p("\t\tst{:s} {:s}".format(targetstr[1].lower(), Parser.to_hex(Zeropage.SCRATCH_B2)))
|
||||
self.p("\t\tjmp ({:s})".format(Parser.to_hex(Zeropage.SCRATCH_B1)))
|
||||
self.p("\t\t{:s} ({:s})".format(goto_opcode, Parser.to_hex(Zeropage.SCRATCH_B1)))
|
||||
else:
|
||||
self.p("\t\tjmp ({:s})".format(targetstr))
|
||||
self.p("\t\t{:s} ({:s})".format(goto_opcode, targetstr))
|
||||
# no result assignments because it's a goto
|
||||
if line_after_goto:
|
||||
self.p(line_after_goto)
|
||||
else:
|
||||
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
|
||||
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
|
||||
@ -551,8 +714,10 @@ class CodeGenerator:
|
||||
if stmt.is_goto:
|
||||
# no need to preserve registers for a goto
|
||||
generate_param_assignments()
|
||||
self.p("\t\tjmp " + targetstr)
|
||||
self.p("\t\t{:s} {:s}".format(goto_opcode, targetstr))
|
||||
# no result assignments because it's a goto
|
||||
if line_after_goto:
|
||||
self.p(line_after_goto)
|
||||
else:
|
||||
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
|
||||
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
|
||||
@ -813,7 +978,7 @@ class CodeGenerator:
|
||||
if lv.name:
|
||||
symblock, sym = self.cur_block.lookup(lv.name)
|
||||
if not isinstance(sym, VariableDef):
|
||||
raise TypeError("invalid lvalue type " + str(sym))
|
||||
raise CodeError("invalid lvalue type " + str(sym))
|
||||
assign_target = symblock.label + '.' + sym.name if symblock is not self.cur_block else lv.name
|
||||
lvdatatype = sym.type
|
||||
else:
|
||||
@ -836,10 +1001,10 @@ class CodeGenerator:
|
||||
self.p("\t\tsta {}+1".format(assign_target))
|
||||
elif lvdatatype == DataType.FLOAT:
|
||||
if rvalue.value is not None and not DataType.FLOAT.assignable_from_value(rvalue.value):
|
||||
raise ValueError("value cannot be assigned to a float")
|
||||
raise CodeError("value cannot be assigned to a float")
|
||||
self.generate_assign_float_to_mem(lv, rvalue, False)
|
||||
else:
|
||||
raise TypeError("invalid lvalue type " + str(lvdatatype))
|
||||
raise CodeError("invalid lvalue type " + str(lvdatatype))
|
||||
|
||||
def generate_assign_mem_to_reg(self, l_register: str, rvalue: ParseResult.MemMappedValue) -> None:
|
||||
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address)
|
||||
@ -934,9 +1099,9 @@ class CodeGenerator:
|
||||
self.p("\t\tlda #0")
|
||||
self.p("\t\tsta {}+1".format(assign_target))
|
||||
else:
|
||||
raise TypeError("invalid lvalue type " + str(sym))
|
||||
raise CodeError("invalid lvalue type " + str(sym))
|
||||
else:
|
||||
raise TypeError("invalid lvalue type " + str(sym))
|
||||
raise CodeError("invalid lvalue type " + str(sym))
|
||||
|
||||
def generate_assign_integer_to_reg(self, l_register: str, rvalue: ParseResult.IntegerValue) -> None:
|
||||
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
|
||||
|
159
il65/parse.py
159
il65/parse.py
@ -13,9 +13,9 @@ import sys
|
||||
import shutil
|
||||
import enum
|
||||
from collections import defaultdict
|
||||
from typing import Set, List, Tuple, Optional, Any, Dict, Union, Sequence
|
||||
from typing import Set, List, Tuple, Optional, Any, Dict, Union
|
||||
from .astparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\
|
||||
parse_expr_as_string, parse_arguments
|
||||
parse_expr_as_string, parse_arguments, parse_expr_as_comparison
|
||||
from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \
|
||||
zeropage, check_value_in_range, char_to_bytevalue, \
|
||||
PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \
|
||||
@ -254,8 +254,11 @@ class ParseResult:
|
||||
return False, "can only assign an integer constant value of 0 or 1 to SC and SI"
|
||||
if self.constant:
|
||||
return False, "cannot assign to a constant"
|
||||
if isinstance(other, ParseResult.RegisterValue) and len(self.register) < len(other.register):
|
||||
return False, "register size mismatch"
|
||||
if isinstance(other, ParseResult.RegisterValue):
|
||||
if other.register in {"SI", "SC", "SZ"}:
|
||||
return False, "cannot explicitly assign from a status bit register alias"
|
||||
if len(self.register) < len(other.register):
|
||||
return False, "register size mismatch"
|
||||
if isinstance(other, ParseResult.StringValue) and self.register in REGISTER_BYTES:
|
||||
return False, "string address requires 16 bits combined register"
|
||||
if isinstance(other, ParseResult.IntegerValue):
|
||||
@ -345,22 +348,24 @@ class ParseResult:
|
||||
return False, "incompatible value for assignment"
|
||||
|
||||
class _AstNode: # @todo merge Value with this?
|
||||
pass
|
||||
def __init__(self, lineno: int) -> None:
|
||||
self.lineno = lineno
|
||||
|
||||
class Comment(_AstNode):
|
||||
def __init__(self, text: str) -> None:
|
||||
def __init__(self, text: str, lineno: int) -> None:
|
||||
super().__init__(lineno)
|
||||
self.text = text
|
||||
|
||||
class Label(_AstNode):
|
||||
def __init__(self, name: str, lineno: int) -> None:
|
||||
super().__init__(lineno)
|
||||
self.name = name
|
||||
self.lineno = lineno
|
||||
|
||||
class AssignmentStmt(_AstNode):
|
||||
def __init__(self, leftvalues: List['ParseResult.Value'], right: 'ParseResult.Value', lineno: int) -> None:
|
||||
super().__init__(lineno)
|
||||
self.leftvalues = leftvalues
|
||||
self.right = right
|
||||
self.lineno = lineno
|
||||
|
||||
def __str__(self):
|
||||
return "<Assign {:s} to {:s}>".format(str(self.right), ",".join(str(lv) for lv in self.leftvalues))
|
||||
@ -395,28 +400,34 @@ class ParseResult:
|
||||
return all(lv == self.right for lv in self.leftvalues)
|
||||
|
||||
class ReturnStmt(_AstNode):
|
||||
def __init__(self, a: Optional['ParseResult.Value']=None,
|
||||
def __init__(self, lineno: int, a: Optional['ParseResult.Value']=None,
|
||||
x: Optional['ParseResult.Value']=None,
|
||||
y: Optional['ParseResult.Value']=None) -> None:
|
||||
super().__init__(lineno)
|
||||
self.a = a
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
class IncrDecrStmt(_AstNode):
|
||||
def __init__(self, what: 'ParseResult.Value', howmuch: int) -> None:
|
||||
def __init__(self, what: 'ParseResult.Value', howmuch: int, lineno: int) -> None:
|
||||
super().__init__(lineno)
|
||||
self.what = what
|
||||
self.howmuch = howmuch
|
||||
|
||||
class CallStmt(_AstNode):
|
||||
def __init__(self, lineno: int, target: Optional['ParseResult.Value']=None, *,
|
||||
address: Optional[int]=None, arguments: List[Tuple[str, Any]]=None,
|
||||
outputs: List[Tuple[str, 'ParseResult.Value']]=None, is_goto: bool=False, preserve_regs: bool=True) -> None:
|
||||
self.lineno = lineno
|
||||
outputs: List[Tuple[str, 'ParseResult.Value']]=None, is_goto: bool=False,
|
||||
preserve_regs: bool=True, condition: 'ParseResult.IfCondition'=None) -> None:
|
||||
if not is_goto:
|
||||
assert condition is None
|
||||
super().__init__(lineno)
|
||||
self.target = target
|
||||
self.address = address
|
||||
self.arguments = arguments
|
||||
self.outputvars = outputs
|
||||
self.is_goto = is_goto
|
||||
self.condition = condition
|
||||
self.preserve_regs = preserve_regs
|
||||
self.desugared_call_arguments = [] # type: List[ParseResult.AssignmentStmt]
|
||||
self.desugared_output_assignments = [] # type: List[ParseResult.AssignmentStmt]
|
||||
@ -438,10 +449,56 @@ class ParseResult:
|
||||
self.desugared_output_assignments.append(assignment)
|
||||
|
||||
class InlineAsm(_AstNode):
|
||||
def __init__(self, lineno: int, asmlines: List[str]) -> None:
|
||||
self.lineno = lineno
|
||||
def __init__(self, asmlines: List[str], lineno: int) -> None:
|
||||
super().__init__(lineno)
|
||||
self.asmlines = asmlines
|
||||
|
||||
class IfCondition(_AstNode):
|
||||
SWAPPED_OPERATOR = {"==": "==",
|
||||
"!=": "!=",
|
||||
"<=": ">=",
|
||||
">=": "<=",
|
||||
"<": ">",
|
||||
">": "<"}
|
||||
INVERSE_IFSTATUS = {"cc": "cs",
|
||||
"cs": "cc",
|
||||
"vc": "vs",
|
||||
"vs": "vc",
|
||||
"eq": "ne",
|
||||
"ne": "eq",
|
||||
"pos": "neg",
|
||||
"neg": "pos",
|
||||
"true": "not",
|
||||
"not": "true",
|
||||
"zero": "true",
|
||||
"lt": "ge",
|
||||
"gt": "le",
|
||||
"le": "gt",
|
||||
"ge": "lt"}
|
||||
|
||||
def __init__(self, ifstatus: str, leftvalue: Optional['ParseResult.Value'],
|
||||
operator: str, rightvalue: Optional['ParseResult.Value'], lineno: int) -> None:
|
||||
assert ifstatus in self.INVERSE_IFSTATUS
|
||||
assert operator in (None, "") or operator in self.SWAPPED_OPERATOR
|
||||
if operator:
|
||||
assert ifstatus in ("true", "not")
|
||||
super().__init__(lineno)
|
||||
self.ifstatus = ifstatus
|
||||
self.lvalue = leftvalue
|
||||
self.comparison_op = operator
|
||||
self.rvalue = rightvalue
|
||||
|
||||
def __str__(self):
|
||||
return "<IfCondition {} {:s} {}>".format(self.lvalue, self.comparison_op, self.rvalue)
|
||||
|
||||
def inverse_ifstatus(self) -> str:
|
||||
# to be able to generate a branch when the status does NOT apply
|
||||
return self.INVERSE_IFSTATUS[self.ifstatus]
|
||||
|
||||
def swap(self) -> Tuple['ParseResult.Value', str, 'ParseResult.Value']:
|
||||
self.lvalue, self.comparison_op, self.rvalue = self.rvalue, self.SWAPPED_OPERATOR[self.comparison_op], self.lvalue
|
||||
return self.lvalue, self.comparison_op, self.rvalue
|
||||
|
||||
def add_block(self, block: 'ParseResult.Block', position: Optional[int]=None) -> None:
|
||||
if position is not None:
|
||||
self.blocks.insert(position, block)
|
||||
@ -550,7 +607,7 @@ class Parser:
|
||||
while True:
|
||||
line = self.next_line().lstrip()
|
||||
if line.startswith(';'):
|
||||
self.cur_block.statements.append(ParseResult.Comment(line))
|
||||
self.cur_block.statements.append(ParseResult.Comment(line, self.sourceref.line))
|
||||
continue
|
||||
self.prev_line()
|
||||
break
|
||||
@ -1040,6 +1097,17 @@ class Parser:
|
||||
return varname, datatype, length, matrix_dimensions, valuetext
|
||||
|
||||
def parse_statement(self, line: str) -> ParseResult._AstNode:
|
||||
match = re.fullmatch(r"(?P<if>if(_[a-z]+)?)\s+(?P<cond>.+)?goto\s+(?P<subname>[\S]+?)\s*(\((?P<arguments>.*)\))?\s*", line)
|
||||
if match:
|
||||
# conditional goto
|
||||
groups = match.groupdict()
|
||||
subname = groups["subname"]
|
||||
if '!' in subname:
|
||||
raise self.PError("goto is always without register preservation, should not have exclamation mark")
|
||||
if groups["if"] == "if" and not groups["cond"]:
|
||||
raise self.PError("need explicit if status when a condition is not present")
|
||||
condition = self.parse_if_condition(groups["if"], groups["cond"])
|
||||
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True, condition=condition)
|
||||
match = re.fullmatch(r"goto\s+(?P<subname>[\S]+?)\s*(\((?P<arguments>.*)\))?\s*", line)
|
||||
if match:
|
||||
# goto
|
||||
@ -1047,8 +1115,7 @@ class Parser:
|
||||
subname = groups["subname"]
|
||||
if '!' in subname:
|
||||
raise self.PError("goto is always without register preservation, should not have exclamation mark")
|
||||
arguments = groups["arguments"]
|
||||
return self.parse_call_or_goto(subname, arguments, None, False, True)
|
||||
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True)
|
||||
match = re.fullmatch(r"(?P<outputs>[^\(]*\s*=)?\s*(?P<subname>[\S]+?)\s*(?P<fcall>[!]?)\s*(\((?P<arguments>.*)\))?\s*", line)
|
||||
if match:
|
||||
# subroutine call (not a goto) with possible output param assignment
|
||||
@ -1070,7 +1137,7 @@ class Parser:
|
||||
what = self.parse_expression(line[:-2].rstrip())
|
||||
if isinstance(what, ParseResult.IntegerValue):
|
||||
raise self.PError("cannot in/decrement a constant value")
|
||||
return ParseResult.IncrDecrStmt(what, 1 if incr else -1)
|
||||
return ParseResult.IncrDecrStmt(what, 1 if incr else -1, self.sourceref.line)
|
||||
else:
|
||||
# perhaps it is an assignment statment
|
||||
lhs, sep, rhs = line.partition("=")
|
||||
@ -1079,7 +1146,9 @@ class Parser:
|
||||
raise self.PError("invalid statement")
|
||||
|
||||
def parse_call_or_goto(self, targetstr: str, argumentstr: str, outputstr: str,
|
||||
preserve_regs=True, is_goto=False) -> ParseResult.CallStmt:
|
||||
preserve_regs=True, is_goto=False, condition: ParseResult.IfCondition=None) -> ParseResult.CallStmt:
|
||||
if not is_goto:
|
||||
assert condition is None
|
||||
argumentstr = argumentstr.strip() if argumentstr else ""
|
||||
outputstr = outputstr.strip() if outputstr else ""
|
||||
arguments = None
|
||||
@ -1106,6 +1175,8 @@ class Parser:
|
||||
except ParseError:
|
||||
symbol = None # it's probably a number or a register then
|
||||
if isinstance(symbol, SubroutineDef):
|
||||
if condition and symbol.parameters:
|
||||
raise self.PError("cannot use a subroutine that requires parameters as a target for conditional goto")
|
||||
# verify subroutine arguments
|
||||
if len(arguments or []) != len(symbol.parameters):
|
||||
raise self.PError("invalid number of arguments ({:d}, expected {:d})"
|
||||
@ -1144,7 +1215,7 @@ class Parser:
|
||||
if isinstance(target, (type(None), ParseResult.Value)):
|
||||
if is_goto:
|
||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address,
|
||||
arguments=arguments, outputs=outputvars, is_goto=True)
|
||||
arguments=arguments, outputs=outputvars, is_goto=True, condition=condition)
|
||||
else:
|
||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address,
|
||||
arguments=arguments, outputs=outputvars, preserve_regs=preserve_regs)
|
||||
@ -1190,7 +1261,7 @@ class Parser:
|
||||
if len(parts) > 1:
|
||||
values = parts[1].split(",")
|
||||
if len(values) == 0:
|
||||
return ParseResult.ReturnStmt()
|
||||
return ParseResult.ReturnStmt(self.sourceref.line)
|
||||
else:
|
||||
a = self.parse_expression(values[0]) if values[0] else None
|
||||
if len(values) > 1:
|
||||
@ -1199,7 +1270,7 @@ class Parser:
|
||||
y = self.parse_expression(values[2]) if values[2] else None
|
||||
if len(values) > 3:
|
||||
raise self.PError("too many returnvalues")
|
||||
return ParseResult.ReturnStmt(a, x, y)
|
||||
return ParseResult.ReturnStmt(self.sourceref.line, a, x, y)
|
||||
|
||||
def parse_asm(self) -> ParseResult.InlineAsm:
|
||||
line = self.next_line()
|
||||
@ -1211,7 +1282,7 @@ class Parser:
|
||||
while True:
|
||||
line = self.next_line()
|
||||
if line.strip() == "}":
|
||||
return ParseResult.InlineAsm(lineno, asmlines)
|
||||
return ParseResult.InlineAsm(asmlines, lineno)
|
||||
# asm can refer to other symbols as well, track subroutine usage
|
||||
splits = line.split(maxsplit=1)
|
||||
if len(splits) == 2:
|
||||
@ -1252,7 +1323,7 @@ class Parser:
|
||||
lines = ['{:s}\t.binclude "{:s}"'.format(scopename, filename)]
|
||||
else:
|
||||
raise self.PError("invalid asminclude statement")
|
||||
return ParseResult.InlineAsm(self.sourceref.line, lines)
|
||||
return ParseResult.InlineAsm(lines, self.sourceref.line)
|
||||
elif aline[0] == "asmbinary":
|
||||
if len(aline) == 4:
|
||||
offset = parse_expr_as_int(aline[2], None, None, self.sourceref)
|
||||
@ -1265,7 +1336,7 @@ class Parser:
|
||||
lines = ['\t.binary "{:s}"'.format(filename)]
|
||||
else:
|
||||
raise self.PError("invalid asmbinary statement")
|
||||
return ParseResult.InlineAsm(self.sourceref.line, lines)
|
||||
return ParseResult.InlineAsm(lines, self.sourceref.line)
|
||||
else:
|
||||
raise self.PError("invalid statement")
|
||||
|
||||
@ -1462,6 +1533,33 @@ class Parser:
|
||||
result = [sentence[i:j].strip(separators) for i, j in zip(indices, indices[1:])]
|
||||
return list(filter(None, result)) # remove empty strings
|
||||
|
||||
def parse_if_condition(self, ifpart: str, conditionpart: str) -> ParseResult.IfCondition:
|
||||
if ifpart == "if":
|
||||
ifstatus = "true"
|
||||
else:
|
||||
ifstatus = ifpart[3:]
|
||||
if ifstatus not in ParseResult.IfCondition.INVERSE_IFSTATUS:
|
||||
raise self.PError("invalid if form")
|
||||
if conditionpart:
|
||||
left, operator, right = parse_expr_as_comparison(conditionpart, self.sourceref)
|
||||
leftv = self.parse_expression(left)
|
||||
if not operator and isinstance(leftv, (ParseResult.IntegerValue, ParseResult.FloatValue, ParseResult.StringValue)):
|
||||
raise self.PError("condition is a constant value")
|
||||
if isinstance(leftv, ParseResult.RegisterValue):
|
||||
if leftv.register in {"SC", "SZ", "SI"}:
|
||||
raise self.PError("cannot use a status bit register explicitly in a condition")
|
||||
if operator:
|
||||
rightv = self.parse_expression(right)
|
||||
if ifstatus not in ("true", "not"):
|
||||
raise self.PError("can only use if[_true] or if_not with a comparison expression")
|
||||
else:
|
||||
rightv = None
|
||||
if leftv == rightv:
|
||||
raise self.PError("left and right values in comparison are identical")
|
||||
return ParseResult.IfCondition(ifstatus, leftv, operator, rightv, self.sourceref.line)
|
||||
else:
|
||||
return ParseResult.IfCondition(ifstatus, None, "", None, self.sourceref.line)
|
||||
|
||||
|
||||
class Optimizer:
|
||||
def __init__(self, parseresult: ParseResult) -> None:
|
||||
@ -1474,12 +1572,23 @@ class Optimizer:
|
||||
self.combine_assignments_into_multi(block)
|
||||
self.optimize_multiassigns(block)
|
||||
self.remove_unused_subroutines(block)
|
||||
self.optimize_compare_with_zero(block)
|
||||
for sub in block.symbols.iter_subroutines(True):
|
||||
self.remove_identity_assigns(sub.sub_block)
|
||||
self.combine_assignments_into_multi(sub.sub_block)
|
||||
self.optimize_multiassigns(sub.sub_block)
|
||||
self.optimize_compare_with_zero(sub.sub_block)
|
||||
return self.parsed
|
||||
|
||||
def optimize_compare_with_zero(self, block: ParseResult.Block) -> None:
|
||||
# a conditional goto that compares a value to zero will be simplified
|
||||
# the comparison operator and rvalue (0) will be removed and the if-status changed accordingly
|
||||
for stmt in block.statements:
|
||||
if isinstance(stmt, ParseResult.CallStmt):
|
||||
if stmt.condition and isinstance(stmt.condition.rvalue, (ParseResult.IntegerValue, ParseResult.FloatValue)):
|
||||
if stmt.condition.rvalue.value == 0:
|
||||
print("ZOMG COMPARE WITH ZERO", stmt.lineno) # XXX
|
||||
|
||||
def combine_assignments_into_multi(self, block: ParseResult.Block) -> None:
|
||||
# fold multiple consecutive assignments with the same rvalue into one multi-assignment
|
||||
statements = [] # type: List[ParseResult._AstNode]
|
||||
|
@ -41,7 +41,7 @@ class PreprocessingParser(Parser):
|
||||
return self.result
|
||||
|
||||
def parse_asminclude(self, line: str) -> ParseResult.InlineAsm:
|
||||
return ParseResult.InlineAsm(self.sourceref.line, [])
|
||||
return ParseResult.InlineAsm([], self.sourceref.line)
|
||||
|
||||
def parse_statement(self, line: str) -> ParseResult._AstNode:
|
||||
return None # type: ignore
|
||||
|
@ -243,6 +243,7 @@ sub init_state () -> (A?, X?, Y?) {
|
||||
tax
|
||||
tay
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}
|
||||
|
22
reference.md
22
reference.md
@ -311,22 +311,18 @@ TODOS
|
||||
|
||||
### Flow Control
|
||||
|
||||
Required building blocks: additional forms of 'go' statement: including an if clause, comparison statement.
|
||||
Required building blocks: additional forms of 'goto' statement: including an if clause, comparison statement.
|
||||
|
||||
- a primitive conditional branch instruction (special case of 'go'): directly translates to a branch instruction:
|
||||
if[_XX] goto <label>
|
||||
XX is one of: (cc, cs, vc, vs, eq, ne, pos, min,
|
||||
lt==cc, lts==min, gt==eq+cs, gts==eq+pos, le==cc+eq, les==neg+eq, ge==cs, ges==pos)
|
||||
and when left out, defaults to ne (not-zero, i.e. true)
|
||||
- a conditional goto instruction: directly translates to a branch instruction:
|
||||
if[_XX] [<expression>] goto <label>
|
||||
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, pos, neg, true==ne, not==eq, zero==eq,
|
||||
lt==cc, gt==eq+cs, le==cc+eq, ge==cs]
|
||||
(@todo signed: lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?)
|
||||
and defaults to true (ne, not-zero) if omitted.
|
||||
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] if-statuses
|
||||
can be used when a *comparison expression* (such as "A < 10") is used.
|
||||
NOTE: some combination branches such as cc+eq an be peephole optimized see http://www.6502.org/tutorials/compare_beyond.html#2.2
|
||||
|
||||
- conditional goto with expression: where the if[_XX] is followed by a <expression>
|
||||
in that case, evaluate the <expression> first (whatever it is) and then emit the primitive if[_XX] go
|
||||
if[_XX] <expression> goto <label>
|
||||
eventually translates to:
|
||||
<expression-code>
|
||||
bXX <label>
|
||||
|
||||
- comparison statement: compares left with right: compare <first_value>, <second_value>
|
||||
(and keeps the comparison result in the status register.)
|
||||
this translates into a lda first_value, cmp second_value sequence after which a conditional branch is possible.
|
||||
|
44
testsource/conditionals.ill
Normal file
44
testsource/conditionals.ill
Normal file
@ -0,0 +1,44 @@
|
||||
output raw
|
||||
|
||||
~ main {
|
||||
var .text name = "?"*80
|
||||
var bytevar = 22
|
||||
var bytevar2 = 23
|
||||
var .word wordvar = 22345
|
||||
|
||||
|
||||
start
|
||||
return
|
||||
label1
|
||||
if_true 22<=Y goto label1
|
||||
if_true A<=22 goto label1
|
||||
if_true A<X goto label1
|
||||
if_true X>Y goto label1
|
||||
if_true X>A goto label1
|
||||
if_true A<=0 goto label1
|
||||
if_true A==0 goto label1
|
||||
if_true A!=0 goto label1
|
||||
if A<=22 goto label1
|
||||
if_cc A goto label1
|
||||
if_vc X goto label1
|
||||
if_pos Y goto label1
|
||||
if_true XY goto label1
|
||||
if_not XY goto label1
|
||||
if A goto label1
|
||||
if_not goto label1
|
||||
|
||||
if_true bytevar<=A goto label2
|
||||
if_true bytevar<=22 goto label2
|
||||
if bytevar<=22 goto label2
|
||||
if bytevar<=22 goto label2
|
||||
if bytevar goto label2
|
||||
if bytevar>bytevar2 goto label2
|
||||
if_pos bytevar goto label2
|
||||
if_not wordvar goto label2
|
||||
if_zero wordvar goto label2
|
||||
if_true wordvar goto label2
|
||||
|
||||
|
||||
label2
|
||||
return
|
||||
}
|
@ -6,6 +6,8 @@ import "c64lib"
|
||||
var .text name = "?"*80
|
||||
|
||||
start
|
||||
c64util.init_state()
|
||||
|
||||
XY = c64.CINV
|
||||
SI = 1
|
||||
c64.CINV = #irq_handler
|
||||
@ -18,21 +20,13 @@ start
|
||||
c64util.print_string(name)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
;if_cc goto label
|
||||
;if_cc label
|
||||
;if_cc dsdaf + 33 < 22 label
|
||||
;if_cc dsdaf + 33 < 22 goto label!
|
||||
goto label
|
||||
goto label()
|
||||
|
||||
label
|
||||
|
||||
SI = 1
|
||||
c64.CINV = XY
|
||||
SI = 0
|
||||
|
||||
return
|
||||
|
||||
|
||||
irq_handler
|
||||
asm {
|
||||
lda $cb
|
||||
|
Loading…
Reference in New Issue
Block a user