diff --git a/il65/parse.py b/il65/parse.py index 143dea4d3..4dbd4a1e1 100644 --- a/il65/parse.py +++ b/il65/parse.py @@ -17,7 +17,7 @@ from typing import Set, List, Tuple, Optional, Any, Dict, Union, Generator from .astparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\ parse_expr_as_string, parse_arguments, parse_expr_as_comparison from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \ - zeropage, check_value_in_range, char_to_bytevalue, \ + Zeropage, check_value_in_range, char_to_bytevalue, \ PrimitiveType, VariableDef, ConstantDef, SymbolError, STRING_DATATYPES, \ REGISTER_SYMBOLS, REGISTER_WORDS, REGISTER_BYTES, REGISTER_SBITS, RESERVED_NAMES @@ -37,6 +37,7 @@ class ParseResult: self.start_address = 0 self.blocks = [] # type: List['ParseResult.Block'] self.subroutine_usage = defaultdict(set) # type: Dict[Tuple[str, str], Set[str]] + self.zeropage = Zeropage() def all_blocks(self) -> Generator['ParseResult.Block', None, None]: for block in self.blocks: @@ -463,6 +464,11 @@ class ParseResult: for name, value in self.arguments or []: assert name is not None, "all call arguments should have a name or be matched on a named parameter" assignment = parser.parse_assignment(name, value) + if assignment.leftvalues[0].datatype != DataType.BYTE: + if isinstance(assignment.right, ParseResult.IntegerValue) and assignment.right.constant: + # a call that doesn't expect a BYTE argument but gets one, converted from a 1-byte string most likely + if value.startswith("'") and value.endswith("'"): + parser.print_warning("possible problematic string to byte conversion (use a .text var instead?)") if not assignment.is_identity(): assignment.lineno = self.lineno self.desugared_call_arguments.append(assignment) @@ -562,6 +568,7 @@ class Parser: self._cur_lineidx = -1 # used to efficiently go to next/previous line in source self.cur_block = None # type: ParseResult.Block self.root_scope = SymbolTable("", None, None) + self.root_scope.set_zeropage(self.result.zeropage) self.ppsymbols = ppsymbols # symboltable from preprocess phase self.print_block_parsing = True @@ -642,7 +649,8 @@ class Parser: self.cur_block = ParseResult.Block("
", self.sourceref, self.root_scope) self.result.add_block(self.cur_block) self.parse_header() - zeropage.configure(self.result.clobberzp) + if not self.parsing_import: + self.result.zeropage.configure(self.result.clobberzp) while True: self._parse_comments() next_line = self.peek_next_line().lstrip() @@ -1030,7 +1038,7 @@ class Parser: varname, datatype, length, dimensions, valuetext = self.parse_def_common(line, "memory") memaddress = parse_expr_as_int(valuetext, self.cur_block.symbols, self.ppsymbols, self.sourceref) if is_zeropage and memaddress > 0xff: - raise self.PError("address must lie in zeropage $00-$ff") + raise self.PError("address must be in zeropage $00-$ff") try: self.cur_block.symbols.define_variable(varname, self.sourceref, datatype, length=length, address=memaddress, matrixsize=dimensions) @@ -1267,6 +1275,15 @@ class Parser: if any(not a[0] for a in arguments or []): raise self.PError("all call arguments should have a name or be matched on a named parameter") if isinstance(target, (type(None), ParseResult.Value)): + # special case for the C-64 lib's print function, to be able to use it with a single character argument + if target.name == "c64util.print_string" and len(arguments) == 1 and len(arguments[0][0]) > 1: + if arguments[0][1].startswith("'") and arguments[0][1].endswith("'"): + target = self.parse_expression("c64.CHROUT") + address = target.address + outputvars = None + _, newsymbol = self.lookup_with_ppsymbols("c64.CHROUT") + assert len(newsymbol.parameters) == 1 + arguments = [(newsymbol.parameters[0][1], arguments[0][1])] if is_goto: return ParseResult.CallStmt(self.sourceref.line, target, address=address, arguments=arguments, outputs=outputvars, is_goto=True, condition=condition) diff --git a/il65/preprocess.py b/il65/preprocess.py index dfc4be9a2..e11dcb232 100644 --- a/il65/preprocess.py +++ b/il65/preprocess.py @@ -12,8 +12,8 @@ from .symbols import SourceRef class PreprocessingParser(Parser): - def __init__(self, filename: str) -> None: - super().__init__(filename, "", parsing_import=True) + def __init__(self, filename: str, parsing_import: bool=False) -> None: + super().__init__(filename, "", parsing_import=parsing_import) self.print_block_parsing = False def preprocess(self) -> Tuple[List[Tuple[int, str]], SymbolTable]: @@ -63,4 +63,4 @@ class PreprocessingParser(Parser): super().parse_subroutine_def(line) def create_import_parser(self, filename: str, outputdir: str) -> 'Parser': - return PreprocessingParser(filename) + return PreprocessingParser(filename, parsing_import=True) diff --git a/il65/symbols.py b/il65/symbols.py index 7b4b0ed5d..09843d178 100644 --- a/il65/symbols.py +++ b/il65/symbols.py @@ -212,8 +212,11 @@ class Zeropage: def __init__(self) -> None: self.unused_bytes = [] # type: List[int] self.unused_words = [] # type: List[int] + self._configured = False def configure(self, clobber_zp: bool = False) -> None: + if self._configured: + raise SymbolError("cannot configure the ZP multiple times") if clobber_zp: self.unused_bytes = list(range(0x04, 0x80)) self.unused_words = list(range(0x80, 0x100, 2)) @@ -224,6 +227,7 @@ class Zeropage: self.unused_words = [0x04, 0xf7, 0xf9, 0xfb, 0xfd] # 5 zp variables (16 bits each) assert self.SCRATCH_B1 not in self.unused_bytes and self.SCRATCH_B1 not in self.unused_words assert self.SCRATCH_B2 not in self.unused_bytes and self.SCRATCH_B2 not in self.unused_words + self._configured = True def get_unused_byte(self): return self.unused_bytes.pop() @@ -240,10 +244,6 @@ class Zeropage: return len(self.unused_words) -# the single, global Zeropage object -zeropage = Zeropage() - - class SymbolTable: def __init__(self, name: str, parent: Optional['SymbolTable'], owning_block: Any) -> None: @@ -252,6 +252,13 @@ class SymbolTable: self.parent = parent self.owning_block = owning_block self.eval_dict = None + self._zeropage = parent._zeropage if parent else None + + def set_zeropage(self, zp: Zeropage) -> None: + if self._zeropage is None: + self._zeropage = zp + else: + raise SymbolError("already have a zp") def __iter__(self): yield from self.symbols.values() @@ -358,17 +365,17 @@ class SymbolTable: if datatype == DataType.BYTE: if allocate and self.name == "ZP": try: - address = zeropage.get_unused_byte() + address = self._zeropage.get_unused_byte() except LookupError: - raise SymbolError("too many global 8-bit variables in ZP") + raise SymbolError("no space in ZP left for more global 8-bit variables (try zp clobber)") self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.BYTE, allocate, value=value, length=1, address=address) elif datatype == DataType.WORD: if allocate and self.name == "ZP": try: - address = zeropage.get_unused_word() + address = self._zeropage.get_unused_word() except LookupError: - raise SymbolError("too many global 16-bit variables in ZP") + raise SymbolError("no space in ZP left for more global 16-bit variables (try zp clobber)") self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.WORD, allocate, value=value, length=1, address=address) elif datatype == DataType.FLOAT: diff --git a/lib/c64lib.ill b/lib/c64lib.ill index b54b57731..e26d9e554 100644 --- a/lib/c64lib.ill +++ b/lib/c64lib.ill @@ -333,8 +333,6 @@ sub GETADRAY () -> (AY, X?) { } } -; @todo optimize print_string()/ print_pstring() of a single character into a call to c64.CHROUT instead - sub print_string (address: XY) -> (A?, Y?) { ; ---- print null terminated string from X/Y asm { diff --git a/lib/mathlib.ill b/lib/mathlib.ill new file mode 100644 index 000000000..83bcc2e9e --- /dev/null +++ b/lib/mathlib.ill @@ -0,0 +1,18 @@ +; IL65 integer math library for 6502 +; (floating point math is done via the C-64's BASIC ROM routines) +; +; Written by Irmen de Jong (irmen@razorvine.net) +; License: GNU GPL 3.0, see LICENSE +; +; indent format: TABS, size=8 + + +output raw + +~ math { + + +; @todo some interesting routines can be found here http://6502org.wikidot.com/software-math + + +} diff --git a/reference.md b/reference.md index fe7c3f0de..a42ff0f1f 100644 --- a/reference.md +++ b/reference.md @@ -126,6 +126,11 @@ IL65 supports the following data types: Strings can be writen in your code as CBM PETSCII or as C-64 screencode variants, these will be translated by the compiler. PETSCII is the default, if you need screencodes you have to use the ``s`` variants of the type identifier. +If you write a string with just one character in it, it is *always* considered to be a BYTE instead with +that character's PETSCII value. So if you really need a string of length 1 you must declare it +explicitly as a variable of type ``.text``, you cannot put ``"x"`` as a subroutine argument where +the subroutine expects (the address of) a string. IL65's type system is unfortunately not strict enough to +avoid this mistake, but it does print a warning if the situation is detected. For many floating point operations, the compiler has to use routines in the C-64 BASIC and KERNAL ROMs. So they will only work if the BASIC ROM (and KERNAL ROM) are banked in, and your code imports the ``c654lib.ill``.