mirror of
https://github.com/irmen/prog8.git
synced 2024-11-25 19:31:36 +00:00
changes to call
This commit is contained in:
parent
fbb4ba4bd8
commit
37f049ee54
@ -110,13 +110,6 @@ def parse_expr_as_primitive(text: str, context: Optional[SymbolTable], ppcontext
|
||||
raise src.to_error("int or float or string expected, not " + type(result).__name__)
|
||||
|
||||
|
||||
def parse_statement(text: str, sourceref: SourceRef) -> int: # @todo in progress...
|
||||
src = SourceLine(text, sourceref)
|
||||
text = src.preprocess()
|
||||
node = ast.parse(text, sourceref.file, mode="single")
|
||||
return node
|
||||
|
||||
|
||||
class EvaluatingTransformer(ast.NodeTransformer):
|
||||
def __init__(self, src: SourceLine, context: SymbolTable, ppcontext: SymbolTable) -> None:
|
||||
super().__init__()
|
||||
|
@ -14,7 +14,7 @@ import contextlib
|
||||
from functools import partial
|
||||
from typing import TextIO, Set, Union
|
||||
from .parse import ProgramFormat, ParseResult, Parser
|
||||
from .symbols import Zeropage, DataType, VariableDef, SubroutineDef, \
|
||||
from .symbols import Zeropage, DataType, ConstantDef, VariableDef, SubroutineDef, \
|
||||
STRING_DATATYPES, REGISTER_WORDS, FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE
|
||||
|
||||
|
||||
@ -246,10 +246,11 @@ class CodeGenerator:
|
||||
for constdef in consts:
|
||||
if constdef.type == DataType.FLOAT:
|
||||
self.p("\t\t{:s} = {}".format(constdef.name, constdef.value))
|
||||
elif constdef.type in STRING_DATATYPES:
|
||||
print("warning: {}: const string not defined in output yet".format(constdef.sourceref)) # XXX
|
||||
elif constdef.type in (DataType.BYTE, DataType.WORD):
|
||||
self.p("\t\t{:s} = {:s}".format(constdef.name, Parser.to_hex(constdef.value))) # type: ignore
|
||||
elif constdef.type in STRING_DATATYPES:
|
||||
# a const string is just a string variable in the generated assembly
|
||||
self._generate_string_var(constdef)
|
||||
else:
|
||||
raise CodeError("invalid const type", constdef)
|
||||
mem_vars = [vi for vi in block.symbols.iter_variables() if not vi.allocate and not vi.register]
|
||||
@ -306,7 +307,13 @@ class CodeGenerator:
|
||||
vardef.matrixsize[0] * vardef.matrixsize[1],
|
||||
vardef.value or 0,
|
||||
vardef.matrixsize[0], vardef.matrixsize[1]))
|
||||
elif vardef.type == DataType.STRING:
|
||||
elif vardef.type in STRING_DATATYPES:
|
||||
self._generate_string_var(vardef)
|
||||
else:
|
||||
raise CodeError("unknown variable type " + str(vardef.type))
|
||||
|
||||
def _generate_string_var(self, vardef: Union[ConstantDef, VariableDef]) -> None:
|
||||
if vardef.type == DataType.STRING:
|
||||
# 0-terminated string
|
||||
self.p("{:s}\n\t\t.null {:s}".format(vardef.name, self.output_string(str(vardef.value))))
|
||||
elif vardef.type == DataType.STRING_P:
|
||||
@ -322,8 +329,6 @@ class CodeGenerator:
|
||||
self.p(".enc 'screen'")
|
||||
self.p("{:s}\n\t\t.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
|
||||
self.p(".enc 'none'")
|
||||
else:
|
||||
raise CodeError("unknown variable type " + str(vardef.type))
|
||||
|
||||
def generate_statement(self, stmt: ParseResult._AstNode) -> None:
|
||||
if isinstance(stmt, ParseResult.ReturnStmt):
|
||||
@ -566,30 +571,31 @@ class CodeGenerator:
|
||||
else:
|
||||
raise CodeError("invalid assignment target (4)", str(stmt))
|
||||
elif isinstance(rvalue, ParseResult.FloatValue):
|
||||
mflpt = self.to_mflpt5(rvalue.value)
|
||||
for lv in stmt.leftvalues:
|
||||
if isinstance(lv, ParseResult.MemMappedValue) and lv.datatype == DataType.FLOAT:
|
||||
self.generate_store_immediate_float(lv, rvalue.value, mflpt)
|
||||
self.generate_assign_float_to_mem(lv, rvalue)
|
||||
elif isinstance(lv, ParseResult.IndirectValue):
|
||||
lv = unwrap_indirect(lv)
|
||||
assert lv.datatype == DataType.FLOAT
|
||||
self.generate_store_immediate_float(lv, rvalue.value, mflpt)
|
||||
self.generate_assign_float_to_mem(lv, rvalue)
|
||||
else:
|
||||
raise CodeError("cannot assign float to ", str(lv))
|
||||
else:
|
||||
raise CodeError("invalid assignment value type", str(stmt))
|
||||
|
||||
def generate_store_immediate_float(self, mmv: ParseResult.MemMappedValue, floatvalue: float,
|
||||
mflpt: bytearray, emit_pha: bool=True) -> None:
|
||||
def generate_assign_float_to_mem(self, mmv: ParseResult.MemMappedValue,
|
||||
rvalue: Union[ParseResult.FloatValue, ParseResult.IntegerValue], save_reg: bool=True) -> None:
|
||||
floatvalue = float(rvalue.value)
|
||||
mflpt = self.to_mflpt5(floatvalue)
|
||||
target = mmv.name or Parser.to_hex(mmv.address)
|
||||
if emit_pha:
|
||||
self.p("\t\tpha\t\t\t; {:s} = {}".format(target, floatvalue))
|
||||
if save_reg:
|
||||
self.p("\t\tpha\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
|
||||
else:
|
||||
self.p("\t\t\t\t\t; {:s} = {}".format(target, floatvalue))
|
||||
self.p("\t\t\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
|
||||
for num in range(5):
|
||||
self.p("\t\tlda #${:02x}".format(mflpt[num]))
|
||||
self.p("\t\tsta {:s}+{:d}".format(target, num))
|
||||
if emit_pha:
|
||||
if save_reg:
|
||||
self.p("\t\tpla")
|
||||
|
||||
def generate_assign_reg_to_memory(self, lv: ParseResult.MemMappedValue, r_register: str) -> None:
|
||||
@ -732,8 +738,7 @@ class CodeGenerator:
|
||||
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")
|
||||
floatvalue = float(rvalue.value)
|
||||
self.generate_store_immediate_float(lv, floatvalue, self.to_mflpt5(floatvalue), False)
|
||||
self.generate_assign_float_to_mem(lv, rvalue, False)
|
||||
else:
|
||||
raise TypeError("invalid lvalue type " + str(lvdatatype))
|
||||
|
||||
@ -744,15 +749,20 @@ class CodeGenerator:
|
||||
raise CodeError("can only assign a byte to a register")
|
||||
self.p("\t\tld{:s} {:s}".format(l_register.lower(), r_str))
|
||||
else:
|
||||
if rvalue.datatype != DataType.WORD:
|
||||
raise CodeError("can only assign a word to a register pair")
|
||||
raise NotImplementedError("some mmap type assignment") # @todo other mmapped types
|
||||
if rvalue.datatype == DataType.BYTE:
|
||||
self.p("\t\tld{:s} {:s}".format(l_register[0].lower(), r_str))
|
||||
self.p("\t\tld{:s} #0".format(l_register[1].lower()))
|
||||
elif rvalue.datatype == DataType.WORD:
|
||||
self.p("\t\tld{:s} {:s}".format(l_register[0].lower(), r_str))
|
||||
self.p("\t\tld{:s} {:s}+1".format(l_register[1].lower(), r_str))
|
||||
else:
|
||||
raise CodeError("can only assign a byte or word to a register pair")
|
||||
|
||||
def generate_assign_mem_to_mem(self, lv: ParseResult.MemMappedValue, rvalue: ParseResult.MemMappedValue) -> None:
|
||||
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address)
|
||||
if lv.datatype == DataType.BYTE:
|
||||
if rvalue.datatype != DataType.BYTE:
|
||||
raise CodeError("can only assign a byte to a byte")
|
||||
raise CodeError("can only assign a byte to a byte", str(rvalue))
|
||||
with self.preserving_registers({'A'}):
|
||||
self.p("\t\tlda " + r_str)
|
||||
self.p("\t\tsta " + (lv.name or Parser.to_hex(lv.address)))
|
||||
@ -760,9 +770,9 @@ class CodeGenerator:
|
||||
if rvalue.datatype == DataType.BYTE:
|
||||
with self.preserving_registers({'A'}):
|
||||
l_str = lv.name or Parser.to_hex(lv.address)
|
||||
self.p("\t\tlda #0")
|
||||
self.p("\t\tsta " + l_str)
|
||||
self.p("\t\tlda " + r_str)
|
||||
self.p("\t\tsta " + l_str)
|
||||
self.p("\t\tlda #0")
|
||||
self.p("\t\tsta {:s}+1".format(l_str))
|
||||
elif rvalue.datatype == DataType.WORD:
|
||||
with self.preserving_registers({'A'}):
|
||||
@ -772,10 +782,10 @@ class CodeGenerator:
|
||||
self.p("\t\tlda {:s}+1".format(r_str))
|
||||
self.p("\t\tsta {:s}+1".format(l_str))
|
||||
else:
|
||||
raise CodeError("can only assign a byte or word to a word")
|
||||
raise CodeError("can only assign a byte or word to a word", str(rvalue))
|
||||
else:
|
||||
raise CodeError("can only assign to a memory mapped byte or word value for now "
|
||||
"(if you need other types, can't you use a var?)")
|
||||
raise CodeError("can only assign memory to a memory mapped byte or word value for now "
|
||||
"(if you need other types, can't you use a var?)", str(rvalue))
|
||||
|
||||
def generate_assign_char_to_memory(self, lv: ParseResult.MemMappedValue, char_str: str) -> None:
|
||||
# Memory = Character
|
||||
|
142
il65/parse.py
142
il65/parse.py
@ -92,8 +92,7 @@ class ParseResult:
|
||||
|
||||
class IndirectValue(Value):
|
||||
def __init__(self, value: 'ParseResult.Value', type_modifier: DataType) -> None:
|
||||
if not type_modifier:
|
||||
type_modifier = value.datatype
|
||||
assert type_modifier
|
||||
super().__init__(type_modifier, value.name, False)
|
||||
self.value = value
|
||||
|
||||
@ -103,7 +102,19 @@ class ParseResult:
|
||||
def assignable_from(self, other: 'ParseResult.Value') -> Tuple[bool, str]:
|
||||
if self.constant:
|
||||
return False, "cannot assign to a constant"
|
||||
if other.datatype in {DataType.BYTE, DataType.WORD, DataType.FLOAT} | STRING_DATATYPES:
|
||||
if self.datatype == DataType.BYTE:
|
||||
if other.datatype == DataType.BYTE:
|
||||
return True, ""
|
||||
if self.datatype == DataType.WORD:
|
||||
if other.datatype in {DataType.BYTE, DataType.WORD} | STRING_DATATYPES:
|
||||
return True, ""
|
||||
if self.datatype == DataType.FLOAT:
|
||||
if other.datatype in {DataType.BYTE, DataType.WORD, DataType.FLOAT}:
|
||||
return True, ""
|
||||
if isinstance(other, (ParseResult.IntegerValue, ParseResult.FloatValue, ParseResult.StringValue)):
|
||||
rangefault = check_value_in_range(self.datatype, "", 1, other.value)
|
||||
if rangefault:
|
||||
return False, rangefault
|
||||
return True, ""
|
||||
return False, "incompatible value for indirect assignment (need byte, word, float or string)"
|
||||
|
||||
@ -209,10 +220,15 @@ class ParseResult:
|
||||
|
||||
def assignable_from(self, other: 'ParseResult.Value') -> Tuple[bool, str]:
|
||||
if isinstance(other, ParseResult.IndirectValue):
|
||||
other = other.value
|
||||
if other.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
|
||||
if self.datatype == DataType.BYTE:
|
||||
if other.datatype == DataType.BYTE:
|
||||
return True, ""
|
||||
return False, "incompatible value for register assignment"
|
||||
return False, "(unsigned) byte required"
|
||||
if self.datatype == DataType.WORD:
|
||||
if other.datatype in (DataType.BYTE, DataType.WORD):
|
||||
return True, ""
|
||||
return False, "(unsigned) byte required"
|
||||
return False, "incompatible indirect value for register assignment"
|
||||
if self.register == "SC":
|
||||
if isinstance(other, ParseResult.IntegerValue) and other.value in (0, 1):
|
||||
return True, ""
|
||||
@ -223,7 +239,16 @@ class ParseResult:
|
||||
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, ParseResult.FloatValue)):
|
||||
if isinstance(other, ParseResult.IntegerValue):
|
||||
if other.value is not None:
|
||||
range_error = check_value_in_range(self.datatype, self.register, 1, other.value)
|
||||
if range_error:
|
||||
return False, range_error
|
||||
return True, ""
|
||||
if self.datatype == DataType.WORD:
|
||||
return True, ""
|
||||
return False, "cannot assign address to single register"
|
||||
if isinstance(other, ParseResult.FloatValue):
|
||||
range_error = check_value_in_range(self.datatype, self.register, 1, other.value)
|
||||
if range_error:
|
||||
return False, range_error
|
||||
@ -369,7 +394,7 @@ class ParseResult:
|
||||
return [self]
|
||||
statements = [] # type: List[ParseResult._AstNode]
|
||||
for name, value in self.arguments:
|
||||
assert name is not None, "call argument should have a parameter name assigned"
|
||||
assert name is not None, "all call arguments should have a name or be matched on a named parameter"
|
||||
assignment = parser.parse_assignment("{:s}={:s}".format(name, value))
|
||||
assignment.lineno = self.lineno
|
||||
statements.append(assignment)
|
||||
@ -419,16 +444,18 @@ class Parser:
|
||||
with open(filename, "rU") as source:
|
||||
sourcelines = source.readlines()
|
||||
# store all lines that aren't empty
|
||||
# comments are kept (end-of-line comments are put on a separate line)
|
||||
# comments are kept (end-of-line comments are stripped though)
|
||||
lines = []
|
||||
for num, line in enumerate(sourcelines, start=1):
|
||||
line2 = line.strip()
|
||||
if line2:
|
||||
line, sep, comment = line.partition(";")
|
||||
if comment:
|
||||
lines.append((num, "; " + comment.strip()))
|
||||
if line.rstrip():
|
||||
lines.append((num, line.rstrip()))
|
||||
line = line.rstrip()
|
||||
if line.lstrip().startswith(';'):
|
||||
lines.append((num, line.lstrip()))
|
||||
else:
|
||||
line2, sep, comment = line.rpartition(';')
|
||||
if sep:
|
||||
line = line2.rstrip()
|
||||
if line:
|
||||
lines.append((num, line))
|
||||
return lines
|
||||
|
||||
def parse(self) -> Optional[ParseResult]:
|
||||
@ -759,21 +786,21 @@ class Parser:
|
||||
self.print_warning("warning: {}: Ignoring block without name and address.".format(self.cur_block.sourceref))
|
||||
return None
|
||||
return self.cur_block
|
||||
if line.startswith("var"):
|
||||
if line.startswith(("var ", "var\t")):
|
||||
self.parse_var_def(line)
|
||||
elif line.startswith("const"):
|
||||
elif line.startswith(("const ", "const\t")):
|
||||
self.parse_const_def(line)
|
||||
elif line.startswith("memory"):
|
||||
elif line.startswith(("memory ", "memory\t")):
|
||||
self.parse_memory_def(line, is_zp_block)
|
||||
elif line.startswith("subx"):
|
||||
elif line.startswith(("subx ", "subx\t")):
|
||||
if is_zp_block:
|
||||
raise self.PError("ZP block cannot contain subroutines")
|
||||
self.parse_subx_def(line)
|
||||
elif line.startswith(("asminclude", "asmbinary")):
|
||||
elif line.startswith(("asminclude ", "asminclude\t", "asmbinary ", "asmbinary\t")):
|
||||
if is_zp_block:
|
||||
raise self.PError("ZP block cannot contain assembler directives")
|
||||
self.cur_block.statements.append(self.parse_asminclude(line))
|
||||
elif line.startswith("asm"):
|
||||
elif line.startswith(("asm ", "asm\t")):
|
||||
if is_zp_block:
|
||||
raise self.PError("ZP block cannot contain code statements")
|
||||
self.prev_line()
|
||||
@ -896,15 +923,18 @@ class Parser:
|
||||
return varname, datatype, length, matrix_dimensions, valuetext
|
||||
|
||||
def parse_statement(self, line: str) -> ParseResult._AstNode:
|
||||
# check if we have a subroutine call using () syntax
|
||||
match = re.match(r"^(?P<subname>[\w\.]+)\s*(?P<fcall>[!]?)\s*\((?P<params>.*)\)\s*$", line)
|
||||
match = re.match(r"(?P<goto>goto\s+)?(?P<subname>[\S]+?)\s*(?P<fcall>[!]?)\s*(\((?P<arguments>.*)\))?\s*$", line)
|
||||
if match:
|
||||
# subroutine or goto call
|
||||
is_goto = bool(match.group("goto"))
|
||||
preserve = not bool(match.group("fcall"))
|
||||
subname = match.group("subname")
|
||||
fcall = "f" if match.group("fcall") else ""
|
||||
param_str = match.group("params")
|
||||
# turn this into "[f]call subname parameters" so it will be parsed below
|
||||
line = "{:s}call {:s} {:s}".format(fcall, subname, param_str)
|
||||
if line.startswith("return"):
|
||||
arguments = match.group("arguments")
|
||||
if is_goto:
|
||||
return self.parse_call_or_goto(subname, arguments, preserve, True)
|
||||
elif arguments or match.group(4):
|
||||
return self.parse_call_or_goto(subname, arguments, preserve, False)
|
||||
if line == "return" or line.startswith(("return ", "return\t")):
|
||||
return self.parse_return(line)
|
||||
elif line.endswith(("++", "--")):
|
||||
incr = line.endswith("++")
|
||||
@ -912,12 +942,6 @@ class Parser:
|
||||
if isinstance(what, ParseResult.IntegerValue):
|
||||
raise self.PError("cannot in/decrement a constant value")
|
||||
return ParseResult.IncrDecrStmt(what, 1 if incr else -1)
|
||||
elif line.startswith("call"):
|
||||
return self.parse_call_or_go(line, "call")
|
||||
elif line.startswith("fcall"):
|
||||
return self.parse_call_or_go(line, "fcall")
|
||||
elif line.startswith("go"):
|
||||
return self.parse_call_or_go(line, "go")
|
||||
else:
|
||||
# perhaps it is an assignment statment
|
||||
lhs, sep, rhs = line.partition("=")
|
||||
@ -925,13 +949,10 @@ class Parser:
|
||||
return self.parse_assignment(line)
|
||||
raise self.PError("invalid statement")
|
||||
|
||||
def parse_call_or_go(self, line: str, what: str) -> ParseResult.CallStmt:
|
||||
args = line.split(maxsplit=2)
|
||||
def parse_call_or_goto(self, targetstr: str, argumentstr: str, preserve_regs=True, is_goto=False) -> ParseResult.CallStmt:
|
||||
argumentstr = argumentstr.strip() if argumentstr else ""
|
||||
arguments = None
|
||||
if len(args) == 2:
|
||||
targetstr, argumentstr, = args[1], ""
|
||||
elif len(args) == 3:
|
||||
targetstr, argumentstr = args[1], args[2]
|
||||
if argumentstr:
|
||||
arguments = []
|
||||
for part in argumentstr.split(','):
|
||||
pname, sep, pvalue = part.partition('=')
|
||||
@ -941,8 +962,6 @@ class Parser:
|
||||
arguments.append((pname, pvalue))
|
||||
else:
|
||||
arguments.append((None, pname))
|
||||
else:
|
||||
raise self.PError("invalid call/go arguments")
|
||||
target = None # type: ParseResult.Value
|
||||
if targetstr[0] == '[' and targetstr[-1] == ']':
|
||||
# indirect call to address in register pair or memory location
|
||||
@ -961,9 +980,9 @@ class Parser:
|
||||
_, symbol = self.cur_block.lookup(targetstr)
|
||||
if isinstance(symbol, SubroutineDef):
|
||||
# verify subroutine arguments
|
||||
if arguments is not None and len(arguments) != len(symbol.parameters):
|
||||
if len(arguments or []) != len(symbol.parameters):
|
||||
raise self.PError("invalid number of arguments ({:d}, expected {:d})"
|
||||
.format(len(arguments), len(symbol.parameters)))
|
||||
.format(len(arguments or []), len(symbol.parameters)))
|
||||
args_with_pnames = []
|
||||
for i, (argname, value) in enumerate(arguments or []):
|
||||
pname, preg = symbol.parameters[i]
|
||||
@ -974,15 +993,18 @@ class Parser:
|
||||
argname = preg
|
||||
args_with_pnames.append((argname, value))
|
||||
arguments = args_with_pnames
|
||||
if isinstance(target, (type(None), ParseResult.Value)):
|
||||
if what == "go":
|
||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address, is_goto=True)
|
||||
elif what == "call":
|
||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address, arguments=arguments)
|
||||
elif what == "fcall":
|
||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address, arguments=arguments, preserve_regs=False)
|
||||
else:
|
||||
raise ValueError("invalid what")
|
||||
if arguments:
|
||||
raise self.PError("call cannot take any arguments here, use a subroutine for that")
|
||||
if arguments:
|
||||
# verify that all arguments have gotten a name
|
||||
if any(not a[0] for a in arguments):
|
||||
raise self.PError("all call arguments should have a name or be matched on a named parameter")
|
||||
if isinstance(target, (type(None), ParseResult.Value)):
|
||||
if is_goto:
|
||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address, arguments=arguments, is_goto=True)
|
||||
else:
|
||||
return ParseResult.CallStmt(self.sourceref.line, target, address=address, arguments=arguments, preserve_regs=preserve_regs)
|
||||
else:
|
||||
raise TypeError("target should be a Value", target)
|
||||
|
||||
@ -1085,12 +1107,15 @@ class Parser:
|
||||
else:
|
||||
raise self.PError("invalid statement")
|
||||
|
||||
def parse_expression(self, text: str) -> ParseResult.Value:
|
||||
def parse_expression(self, text: str, is_indirect=False) -> ParseResult.Value:
|
||||
# parse an expression into whatever it is (primitive value, register, memory, register, etc)
|
||||
# @todo only numeric expressions supported for now
|
||||
text = text.strip()
|
||||
if not text:
|
||||
raise self.PError("value expected")
|
||||
if text[0] == '#':
|
||||
if is_indirect:
|
||||
raise self.PError("using the address-of something in an indirect value makes no sense")
|
||||
# take the pointer (memory address) from the thing that follows this
|
||||
expression = self.parse_expression(text[1:])
|
||||
if isinstance(expression, ParseResult.StringValue):
|
||||
@ -1175,11 +1200,16 @@ class Parser:
|
||||
if typestr in ("byte", "word", "float"):
|
||||
type_modifier, type_len, _ = self.get_datatype(sep + typestr)
|
||||
indirect = indirect2
|
||||
expr = self.parse_expression(indirect)
|
||||
expr = self.parse_expression(indirect, True)
|
||||
if not isinstance(expr, (ParseResult.IntegerValue, ParseResult.MemMappedValue, ParseResult.RegisterValue)):
|
||||
raise self.PError("only integers, memmapped vars, and registers can be used in an indirect value")
|
||||
if type_modifier is None:
|
||||
if isinstance(expr, (ParseResult.RegisterValue, ParseResult.MemMappedValue)):
|
||||
type_modifier = expr.datatype
|
||||
else:
|
||||
type_modifier = DataType.BYTE
|
||||
if isinstance(expr, ParseResult.IntegerValue):
|
||||
if type_modifier not in (None, DataType.BYTE, DataType.WORD, DataType.FLOAT):
|
||||
if type_modifier not in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
|
||||
raise self.PError("invalid type modifier for the value's datatype")
|
||||
elif isinstance(expr, ParseResult.MemMappedValue):
|
||||
if type_modifier and expr.datatype != type_modifier:
|
||||
|
@ -262,7 +262,7 @@ class SymbolTable:
|
||||
return self, getattr(math, nameparts[0])
|
||||
elif nameparts[0] in BUILTIN_SYMBOLS:
|
||||
return self, getattr(builtins, nameparts[0])
|
||||
raise SymbolError("undefined symbol '{:s}'".format(nameparts[0]))
|
||||
raise SymbolError("undefined symbol '{:s}'".format(nameparts[0])) from None
|
||||
# start from toplevel namespace:
|
||||
scope = self
|
||||
while scope.parent:
|
||||
@ -272,7 +272,7 @@ class SymbolTable:
|
||||
scope = scope.symbols[namepart] # type: ignore
|
||||
assert scope.name == namepart
|
||||
except LookupError:
|
||||
raise SymbolError("undefined block '{:s}'".format(namepart))
|
||||
raise SymbolError("undefined block '{:s}'".format(namepart)) from None
|
||||
if isinstance(scope, SymbolTable):
|
||||
return scope.lookup(nameparts[-1])
|
||||
else:
|
||||
@ -445,11 +445,11 @@ class SymbolTable:
|
||||
|
||||
|
||||
class EvalSymbolDict(dict):
|
||||
def __init__(self, symboltable: SymbolTable, ppsymbols: SymbolTable, constants: bool=True) -> None:
|
||||
def __init__(self, symboltable: SymbolTable, ppsymbols: SymbolTable, constant: bool=True) -> None:
|
||||
super().__init__()
|
||||
self._symboltable = symboltable
|
||||
self._constants = constants
|
||||
self._ppsymbols = ppsymbols
|
||||
self._is_constant = constant
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.__getitem__(name)
|
||||
@ -471,17 +471,17 @@ class EvalSymbolDict(dict):
|
||||
if self._ppsymbols:
|
||||
return self._ppsymbols.as_eval_dict(None)[name]
|
||||
raise SymbolError("undefined symbol '{:s}'".format(name)) from None
|
||||
if self._constants:
|
||||
if self._is_constant:
|
||||
if isinstance(symbol, ConstantDef):
|
||||
return symbol.value
|
||||
elif isinstance(symbol, VariableDef):
|
||||
return symbol.value
|
||||
raise SymbolError("can't reference a variable inside a (constant) expression")
|
||||
elif inspect.isbuiltin(symbol):
|
||||
return symbol
|
||||
elif isinstance(symbol, SymbolTable):
|
||||
return symbol.as_eval_dict(self._ppsymbols)
|
||||
elif isinstance(symbol, LabelDef):
|
||||
raise SymbolError("can't reference a label here")
|
||||
elif isinstance(symbol, (LabelDef, SubroutineDef)):
|
||||
raise SymbolError("can't reference a label or subroutine inside a (constant) expression")
|
||||
else:
|
||||
raise SymbolError("invalid symbol type referenced " + repr(symbol))
|
||||
else:
|
||||
|
@ -158,7 +158,8 @@ The syntax "[address]" means: the contents of the memory at address.
|
||||
By default, if not otherwise known, a single byte is assumed. You can add the ".byte" or ".word" or ".float" suffix
|
||||
to make it clear what data type the address points to.
|
||||
|
||||
Everything after a semicolon ';' is a comment and is ignored, however the comment is copied into the resulting assembly source code.
|
||||
Everything after a semicolon ';' is a comment and is ignored, however the comment (if it is the only thing
|
||||
on the line) is copied into the resulting assembly source code.
|
||||
|
||||
|
||||
FLOW CONTROL
|
||||
@ -349,28 +350,20 @@ ISOLATION (register preservation when calling subroutines): @todo isolation
|
||||
SUBROUTINE CALLS
|
||||
----------------
|
||||
|
||||
CALL and FCALL:
|
||||
They are just inserting a call to the specified location or subroutine.
|
||||
[F]CALL: calls subroutine and continue afterwards ('gosub'):
|
||||
[f]call <subroutine> / <label> / <address> / `[`indirect-pointer`]` [arguments...]
|
||||
You call a subroutine like this:
|
||||
subroutinename[!] ( [arguments...] )
|
||||
|
||||
A 'call' preserves all registers when doing the procedure call and restores them afterwards.
|
||||
'fcall' (fast call) doesn't preserve registers, so generates code that is a lot faster.
|
||||
It's basically one jmp or jsr instruction. It can clobber register values because of this.
|
||||
If you provide arguments (not required) these will be matched to the subroutine's parameters.
|
||||
If you don't provide arguments, it is assumed you have prepared the correct registers etc yourself.
|
||||
Normally, the registers are preserved when calling the subroutine and restored on return.
|
||||
If you add a '!' after the name, no register preserving is done and the call essentially
|
||||
is just a single JSR instruction.
|
||||
Arguments should match the subroutine definition. You are allowed to omit the parameter names.
|
||||
If no definition is available (because you're directly calling memory or a label or something else),
|
||||
you can freely add arguments (but in this case they all have to be named).
|
||||
|
||||
To jump to a subroutine (without returning), prefix the subroutine call with the word 'goto'.
|
||||
Unlike gotos in other languages, here it take arguments as well, because it
|
||||
essentially is the same as calling a subroutine and only doing something different when it's finished.
|
||||
|
||||
The following contemporary syntax to call a subroutine is also available:
|
||||
subroutine `(` [arguments...] `)`
|
||||
subroutine! `(` [arguments...] `)`
|
||||
These are understood as: "call subroutine arguments" and "fcall subroutine arguments" respectively.
|
||||
You can only call a subroutine or label this way. This syntax cannot be used
|
||||
to call a memory address or variable, you have to use the call statement for that.
|
||||
|
||||
GO:
|
||||
'go' continues execution with the specified routine or address and doesn't retuurn (it is a 'goto'):
|
||||
go <subroutine> / <label> / <address> / [indirect-pointer]
|
||||
|
||||
|
||||
@todo support call non-register args (variable parameter passing)
|
||||
|
@ -16,19 +16,27 @@
|
||||
const .text constt = "derp"
|
||||
|
||||
subx sub1 () -> (X?) = $ffdd
|
||||
subx sub2 (A) -> (Y?) = $eecc
|
||||
|
||||
|
||||
bar
|
||||
go sub1
|
||||
go bar
|
||||
go [AX]
|
||||
go [var1]
|
||||
go [mem1]
|
||||
go [#mem1] ; @todo error: indirection of address-of makes no sense, should have outputted jmp mem1 instead of jmp(mem1)
|
||||
go [$c2.word]
|
||||
go [$c2dd.word]
|
||||
go $c000
|
||||
go $c2
|
||||
goto sub1
|
||||
goto sub2 (1 )
|
||||
goto bar ()
|
||||
goto [AX]
|
||||
goto [AX] ()
|
||||
goto [var1]
|
||||
goto [var1] () ; comment
|
||||
goto [mem1] ; comment
|
||||
goto [mem1] ()
|
||||
goto [$c2.word]
|
||||
goto [$c2.word] ()
|
||||
goto [$c2dd.word]
|
||||
goto [$c2dd.word] ( )
|
||||
goto $c000
|
||||
goto $c000 ( )
|
||||
goto $c2
|
||||
goto $c2()
|
||||
|
||||
asm {
|
||||
nop
|
||||
@ -37,16 +45,16 @@ bar
|
||||
nop
|
||||
}
|
||||
|
||||
fcall sub1
|
||||
fcall bar
|
||||
fcall [XY]
|
||||
fcall [var1]
|
||||
fcall [mem1]
|
||||
fcall [#mem1] ; @todo error: indirection of address-of makes no sense
|
||||
fcall [$c2.word]
|
||||
fcall [$c2dd.word]
|
||||
fcall $c000
|
||||
fcall $c2
|
||||
sub1!()
|
||||
sub2!(11)
|
||||
bar!()
|
||||
[XY] ! ()
|
||||
[var1] !()
|
||||
[mem1]!()
|
||||
[$c2.word]!()
|
||||
[$c2dd.word]!()
|
||||
$c000!()
|
||||
$c2!()
|
||||
|
||||
asm {
|
||||
nop
|
||||
@ -55,16 +63,16 @@ bar
|
||||
nop
|
||||
}
|
||||
|
||||
call sub1
|
||||
call bar
|
||||
call [AX]
|
||||
call [var1]
|
||||
call [mem1]
|
||||
call [#mem1] ; @todo error: indirection of address-of makes no sense
|
||||
call [$c2.word]
|
||||
call [$c2dd.word]
|
||||
call $c000
|
||||
call $c2
|
||||
sub1()
|
||||
sub2(11)
|
||||
bar ()
|
||||
[AX]()
|
||||
[var1] ( )
|
||||
[mem1] ()
|
||||
[$c2.word]()
|
||||
[$c2dd.word]()
|
||||
$c000()
|
||||
$c2()
|
||||
|
||||
|
||||
asm {
|
||||
@ -74,16 +82,15 @@ bar
|
||||
nop
|
||||
}
|
||||
|
||||
call constw
|
||||
call sub1
|
||||
call main.start
|
||||
|
||||
constw()
|
||||
sub1()
|
||||
main.start()
|
||||
}
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
start
|
||||
call foo.bar
|
||||
foo.bar()
|
||||
return
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ clobberzp
|
||||
var .word initword1b = true
|
||||
var .word initword2 = false
|
||||
var .word initword3 = 9876.554321
|
||||
var .word initword4 = initword3
|
||||
var .word initword5 = 20
|
||||
var .float uninitfloat
|
||||
var .float initfloat1 = 0
|
||||
@ -76,8 +75,7 @@ clobberzp
|
||||
|
||||
; memory-mapped variables
|
||||
memory membyte1 = $cf01
|
||||
memory .byte membyte2 = $c2
|
||||
memory .byte membyte3 = initbyte2
|
||||
memory .byte membyte2 = $c222
|
||||
memory .word memword1 = $cf03
|
||||
memory .float memfloat = $cf04
|
||||
memory .array(10 ) membytes = $cf05
|
||||
@ -109,7 +107,6 @@ clobberzp
|
||||
var .word vmemaddr4 = #membytes
|
||||
var .word vmemaddr5 = #memwords
|
||||
var .word vmemaddr6 = #memmatrix
|
||||
var .word vmemaddr7 = vmemaddr1
|
||||
var .word vmemaddr8 = 100*sin(cbyte1)
|
||||
var .word vmemaddr9 = cword2+$5432
|
||||
var .word vmemaddr10 = cfloat2b
|
||||
@ -144,17 +141,16 @@ start
|
||||
A = false
|
||||
A = 255
|
||||
A = X
|
||||
A = [$99]
|
||||
A = [$c020.byte]
|
||||
A = [$c020]
|
||||
A = [#membyte2]
|
||||
A = cbyte3
|
||||
A = membyte2
|
||||
A = [membyte2] ; @todo error, invalid rvalue, use membyte without indirect?
|
||||
A = [membyte2.byte] ; @todo error, "
|
||||
A = expr_byte1b ; @todo ok
|
||||
;A = #expr_byte1b ; @todo cannot assign address to byte, correct error
|
||||
;A = cbyte3 ; @todo fix assignment to lda #cybte3
|
||||
A = uninitbyte1
|
||||
;A = [membyte2] ; @todo error, invalid rvalue, use membyte without indirect?
|
||||
;A = [membyte2.byte] ; @todo error, "
|
||||
;A = [cbyte3] ; @todo error invalid rvalue
|
||||
A = initbytea0
|
||||
A = [initbytea0] ; @todo error, invalid rvalue, use initbytea0 without indirect?
|
||||
;A = [initbytea0] ; @todo error, invalid rvalue, use initbytea0 without indirect?
|
||||
|
||||
|
||||
XY = 0
|
||||
@ -165,43 +161,67 @@ start
|
||||
XY = true
|
||||
XY = false
|
||||
XY = text
|
||||
XY = cbyte3
|
||||
XY = cword2
|
||||
XY = uninitbyte1
|
||||
XY = "text-immediate"
|
||||
AY = "text-immediate"
|
||||
AX = #"text-immediate" ; equivalent to simply assigning the string directly
|
||||
AX = # "text-immediate" ; equivalent to simply assigning the string directly
|
||||
AX = ctext3
|
||||
AX = ""
|
||||
AX = XY
|
||||
AX = Y
|
||||
;XY = [membyte2] ; @todo ok pad
|
||||
;XY = [membyte2.byte] ; @todo ok pad
|
||||
;XY = membyte2 ; @todo ok pad
|
||||
;XY = #membyte2 ; @todo ok
|
||||
;XY = [memword1] ; @todo ok
|
||||
;XY = [memword1.byte] ; @todo ok pad
|
||||
;XY = [memword1.word] ; @todo ok
|
||||
XY = sin ; @todo ok
|
||||
XY = [membyte2] ; @todo indirection error?
|
||||
XY = [membyte2.byte] ; @todo indirection error?
|
||||
XY = membyte2
|
||||
XY = #membyte2
|
||||
XY = [memword1] ; @todo indirection error?
|
||||
XY = [memword1.word] ; @todo indirection error?
|
||||
XY = memword1
|
||||
XY = sin
|
||||
XY = #sin
|
||||
|
||||
|
||||
[$c000] = A
|
||||
[$c000] = 255
|
||||
[$c000] = '@'
|
||||
[$c000] = 1.2345
|
||||
[$c000] = true
|
||||
[$c000] = false
|
||||
[$c000] = cbyte3
|
||||
[$c000] = uninitbyte1
|
||||
[$c000] = [membyte2] ; @todo indirection error?
|
||||
[$c000] = [membyte2.byte] ; @todo indirection error?
|
||||
[$c000] = membyte2
|
||||
|
||||
[$c000.word] = A
|
||||
[$c000.word] = AX
|
||||
[$c000.word] = cbyte3
|
||||
[$c000.word] = cword2
|
||||
[$c000.word] = ctext3
|
||||
[$c000.word] = 65535
|
||||
[$c000.word] = 456.66
|
||||
[$c000.word] = "text"
|
||||
[$c000.word] = ""
|
||||
[$c000.word] = uninitbyte1
|
||||
[$c000.word] = [membyte2] ; @todo indirection error?
|
||||
[$c000.word] = [membyte2.byte] ; @todo indirection error?
|
||||
[$c000.word] = membyte2
|
||||
[$c000.word] = #membyte2
|
||||
[$c000.word] = [memword1] ; @todo indirection error?
|
||||
[$c000.word] = [memword1.word] ; @todo indirection error?
|
||||
[$c000.word] = memword1
|
||||
[$c000.float] = 65535
|
||||
[$c000.float] = 456.66
|
||||
[$c000.float] = 1.70141183e+38
|
||||
[$c000.word] = AX
|
||||
[$c000.float] = cbyte3
|
||||
[$c000.float] = cword2
|
||||
|
||||
[$c001] = [$c002]
|
||||
[$c111.word] = [$c222]
|
||||
[$c112.word] = [$c223.byte]
|
||||
[$c222.word] = [$c333.word]
|
||||
[$c333] = sin ; @todo error byte required
|
||||
[$c333.word] = sin ; @todo ok
|
||||
|
||||
[$c333.word] = sin
|
||||
[$c333.word] = #sin
|
||||
|
||||
|
||||
SC = 0
|
||||
@ -215,36 +235,59 @@ start
|
||||
initbyte1 = 1.234
|
||||
initbyte1 = '@'
|
||||
initbyte1 = A
|
||||
initbyte1 = cbyte3
|
||||
uninitword = 99
|
||||
uninitword = 5.6778
|
||||
uninitword = "test"
|
||||
uninitword = '@'
|
||||
uninitword = A
|
||||
uninitword = XY
|
||||
uninitword = ctext3
|
||||
initword1 = cbyte3
|
||||
initword1 = cword2
|
||||
initfloat1 = 99
|
||||
initfloat1 = 9.8765
|
||||
initfloat1 = '@'
|
||||
initfloat1 = cbyte3
|
||||
initfloat1 = cword2
|
||||
uninitfloat = 99
|
||||
uninitfloat = 9.8765
|
||||
uninitfloat = '@'
|
||||
;uninitfloat = A ; @todo support this
|
||||
; uninitfloat = XY ; @todo support this
|
||||
initword1 = sin ; @todo ok
|
||||
initword1 = sin
|
||||
initword1 = #sin
|
||||
|
||||
membyte1 = A
|
||||
membyte1 = cbyte3
|
||||
memword1 = A
|
||||
memword1 = AX
|
||||
memword1 = cbyte3
|
||||
memword1 = cword2
|
||||
memword1 = ctext3
|
||||
|
||||
|
||||
membyte1 = 22
|
||||
memword1 = 2233
|
||||
memfloat = 3.4567
|
||||
;[membyte1] = 33 ; @todo error, invalid lvalue, use without []
|
||||
[memword1] = 4444
|
||||
;[memword1] = 4444 ; @todo error ^
|
||||
;[memword1] = [AX] ; @todo error, only address allowed in []
|
||||
[memfloat] = 5.5566
|
||||
memword1 = sin ; @todo ok
|
||||
;[memfloat] = 5.5566 ; @todo error ^
|
||||
memword1 = sin
|
||||
memword1 = #sin
|
||||
|
||||
membyte1 = A
|
||||
memword1 = A
|
||||
memword1 = XY
|
||||
;memfloat = A ; @todo support this
|
||||
;memfloat = XY ; @todo support this
|
||||
memfloat = cbyte3
|
||||
memfloat = cword2
|
||||
|
||||
; @todo float assignments that require ROM functions:
|
||||
; memfloat = Y
|
||||
; memfloat = XY
|
||||
; uninitfloat = Y
|
||||
; uninitfloat = XY
|
||||
; initfloat2 = Y
|
||||
; initfloat2 = XY
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ start
|
||||
|
||||
|
||||
start
|
||||
; assign some float values to the memory
|
||||
AY = #flt_pi
|
||||
[some_address] = # flt_pi
|
||||
[some_address] = # flt_pi
|
||||
@ -65,6 +66,8 @@ start
|
||||
[some_address.word] = #flt_pi
|
||||
[$c000.word] = # flt_pi
|
||||
|
||||
; print some floating points from source and compare them with ROM
|
||||
|
||||
c64.MOVFM!(#flt_pi)
|
||||
c64.FPRINTLN!()
|
||||
c64.MOVFM!(#c64.FL_PIVAL)
|
||||
|
@ -74,8 +74,8 @@ _loop block2.zpw1 ++
|
||||
Y--
|
||||
[$d020]--
|
||||
[block2.zpw2] = 99
|
||||
call fidget.subroutine
|
||||
go _loop
|
||||
fidget.subroutine()
|
||||
goto _loop
|
||||
return 155,2,%00000101 ; will end up in A, X, Y
|
||||
}
|
||||
|
||||
@ -102,8 +102,8 @@ somelabel1
|
||||
nop
|
||||
}
|
||||
|
||||
go somelabel1
|
||||
go block2.somelabel1222
|
||||
goto somelabel1
|
||||
goto block2.somelabel1222
|
||||
A=X=Y=A=X=Y=A=X=Y=99
|
||||
[$d020]=[$d021]=[$d020]=[$d021]=55
|
||||
|
||||
|
@ -28,7 +28,7 @@ import "c64lib" ; searched in several locations and with .ill file
|
||||
var .text hello2 = "@@\f\b\n\r\t@@"
|
||||
|
||||
start
|
||||
call global2.make_screen_black
|
||||
global2.make_screen_black()
|
||||
|
||||
A='?'
|
||||
[$d020] = '?'
|
||||
@ -46,9 +46,7 @@ start
|
||||
X=A
|
||||
A='\xf2'
|
||||
X=A
|
||||
A='A'
|
||||
call c64.CHROUT ;(A)
|
||||
call c64.CHROUT ;(char=66)
|
||||
c64.CHROUT('A')
|
||||
A='\f'
|
||||
X=A
|
||||
A='\b'
|
||||
@ -58,18 +56,13 @@ start
|
||||
A='\r'
|
||||
X=A
|
||||
A='\t'
|
||||
X=A
|
||||
call c64.CHROUT ;(foo=A)
|
||||
A='0'
|
||||
call c64.CHROUT ;('0')
|
||||
A='1'
|
||||
call c64.CHROUT ;(49)
|
||||
A='2'
|
||||
call c64.CHROUT
|
||||
c64.CHROUT('0')
|
||||
c64.CHROUT('1')
|
||||
c64.CHROUT('2')
|
||||
XY = hello
|
||||
call c64util.print_string
|
||||
A='!'
|
||||
go c64.CHROUT
|
||||
c64util.print_string()
|
||||
goto c64.CHROUT('!')
|
||||
|
||||
|
||||
return
|
||||
|
@ -1,4 +1,4 @@
|
||||
output prg,sys ; create a c-64 program with basic SYS call to launch it
|
||||
output prg,sys ; create a c-64 program with basic SYS to() launch it
|
||||
|
||||
import "c64lib.ill"
|
||||
|
||||
@ -9,67 +9,58 @@ output prg,sys ; create a c-64 program with basic SYS call to launch it
|
||||
const .word BORDER = $d020
|
||||
|
||||
start
|
||||
fcall c64util.print_pimmediate ; this prints the pstring immediately following it
|
||||
c64util.print_pimmediate ! () ; this prints the pstring immediately following it
|
||||
asm {
|
||||
.ptext "hello-pimmediate!{cr}"
|
||||
}
|
||||
|
||||
A = 19
|
||||
fcall c64util.print_byte_decimal0
|
||||
A = 13
|
||||
fcall c64.CHROUT
|
||||
c64util.print_byte_decimal0 ! ()
|
||||
c64.CHROUT ! (13)
|
||||
A = 19
|
||||
fcall c64util.print_byte_decimal
|
||||
A = 13
|
||||
fcall c64.CHROUT
|
||||
c64util.print_byte_decimal ! ()
|
||||
c64.CHROUT ! (13)
|
||||
|
||||
|
||||
X = $01
|
||||
Y = $02
|
||||
fcall c64util.print_word_decimal0
|
||||
A = 13
|
||||
fcall c64.CHROUT
|
||||
c64util.print_word_decimal0 ! ()
|
||||
c64.CHROUT ! (13)
|
||||
X = $01
|
||||
Y = $02
|
||||
fcall c64util.print_word_decimal
|
||||
A = 13
|
||||
fcall c64.CHROUT
|
||||
c64util.print_word_decimal ! ()
|
||||
c64.CHROUT ! (13)
|
||||
return
|
||||
|
||||
start2
|
||||
call global2.make_screen_black
|
||||
call c64.CLEARSCR
|
||||
global2.make_screen_black()
|
||||
c64.CLEARSCR()
|
||||
XY = greeting
|
||||
call c64util.print_string
|
||||
c64util.print_string()
|
||||
XY = p_greeting
|
||||
call c64util.print_pstring
|
||||
c64util.print_pstring()
|
||||
A = 0
|
||||
call c64util.print_byte_decimal
|
||||
c64util.print_byte_decimal()
|
||||
A = 0
|
||||
call c64util.print_byte_hex
|
||||
c64util.print_byte_hex()
|
||||
c64.CHROUT(13)
|
||||
c64util.print_byte_decimal()
|
||||
A = 13
|
||||
call c64.CHROUT
|
||||
call c64util.print_byte_decimal
|
||||
A = 13
|
||||
call c64util.print_byte_hex
|
||||
A = 13
|
||||
call c64.CHROUT
|
||||
c64util.print_byte_hex()
|
||||
c64.CHROUT(13)
|
||||
A = 255
|
||||
call c64util.print_byte_decimal
|
||||
c64util.print_byte_decimal()
|
||||
A = 254
|
||||
call c64util.print_byte_hex
|
||||
c64util.print_byte_hex()
|
||||
A = 129
|
||||
call c64util.print_byte_hex
|
||||
A = 13
|
||||
call c64.CHROUT
|
||||
c64util.print_byte_hex()
|
||||
c64.CHROUT(13)
|
||||
|
||||
A = 13
|
||||
call c64.CHROUT
|
||||
c64.CHROUT(13)
|
||||
X = 1
|
||||
Y = 0
|
||||
call c64util.print_word_decimal
|
||||
A = 13
|
||||
call c64.CHROUT
|
||||
c64util.print_word_decimal()
|
||||
c64.CHROUT(13)
|
||||
return
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user