changes to call

This commit is contained in:
Irmen de Jong 2017-12-23 14:36:23 +01:00
parent fbb4ba4bd8
commit 37f049ee54
11 changed files with 316 additions and 253 deletions

View File

@ -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__()

View File

@ -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,25 +307,29 @@ class CodeGenerator:
vardef.matrixsize[0] * vardef.matrixsize[1],
vardef.value or 0,
vardef.matrixsize[0], vardef.matrixsize[1]))
elif 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:
# pascal string
self.p("{:s}\n\t\t.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value))))
elif vardef.type == DataType.STRING_S:
# 0-terminated string in screencode encoding
self.p(".enc 'screen'")
self.p("{:s}\n\t\t.null {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
self.p(".enc 'none'")
elif vardef.type == DataType.STRING_PS:
# 0-terminated pascal string in screencode encoding
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'")
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:
# pascal string
self.p("{:s}\n\t\t.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value))))
elif vardef.type == DataType.STRING_S:
# 0-terminated string in screencode encoding
self.p(".enc 'screen'")
self.p("{:s}\n\t\t.null {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
self.p(".enc 'none'")
elif vardef.type == DataType.STRING_PS:
# 0-terminated pascal string in screencode encoding
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'")
def generate_statement(self, stmt: ParseResult._AstNode) -> None:
if isinstance(stmt, ParseResult.ReturnStmt):
if stmt.a:
@ -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

View File

@ -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):
return True, ""
return False, "incompatible value for register assignment"
if self.datatype == DataType.BYTE:
if other.datatype == DataType.BYTE:
return True, ""
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
else:
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 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)
if is_goto:
return ParseResult.CallStmt(self.sourceref.line, target, address=address, arguments=arguments, is_goto=True)
else:
raise ValueError("invalid what")
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:

View File

@ -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:

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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