From 37f049ee54280541707305161524906121dcab7b Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 23 Dec 2017 14:36:23 +0100 Subject: [PATCH] changes to call --- il65/astparse.py | 7 -- il65/codegen.py | 88 ++++++++++++++----------- il65/parse.py | 142 +++++++++++++++++++++++++---------------- il65/symbols.py | 16 ++--- reference.txt | 33 ++++------ testsource/calls.ill | 77 ++++++++++++---------- testsource/dtypes.ill | 111 ++++++++++++++++++++++---------- testsource/floats.ill | 3 + testsource/source1.ill | 8 +-- testsource/source3.ill | 21 ++---- testsource/source4.ill | 63 ++++++++---------- 11 files changed, 316 insertions(+), 253 deletions(-) diff --git a/il65/astparse.py b/il65/astparse.py index c3c12c686..1cc1548d7 100644 --- a/il65/astparse.py +++ b/il65/astparse.py @@ -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__() diff --git a/il65/codegen.py b/il65/codegen.py index 640e353b7..caf190050 100644 --- a/il65/codegen.py +++ b/il65/codegen.py @@ -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 diff --git a/il65/parse.py b/il65/parse.py index f46018e9b..8ef6a8f2d 100644 --- a/il65/parse.py +++ b/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): - 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[\w\.]+)\s*(?P[!]?)\s*\((?P.*)\)\s*$", line) + match = re.match(r"(?Pgoto\s+)?(?P[\S]+?)\s*(?P[!]?)\s*(\((?P.*)\))?\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: diff --git a/il65/symbols.py b/il65/symbols.py index 8b6c5f7fe..a378c76aa 100644 --- a/il65/symbols.py +++ b/il65/symbols.py @@ -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: diff --git a/reference.txt b/reference.txt index 930c15d05..47ade7078 100644 --- a/reference.txt +++ b/reference.txt @@ -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 /