From a3faf07c8cab635bc506292072173ec62f8082b5 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 27 Dec 2017 12:39:19 +0100 Subject: [PATCH] if statement --- il65/astparse.py | 10 ++++++ il65/parse.py | 58 +++++++++++++++++----------------- lib/c64lib.ill | 55 +++++++++++++++++++++++++++++---- reference.md | 72 ++++++++++++++++++++++++------------------- testsource/floats.ill | 4 +-- testsource/input.ill | 9 ++++++ 6 files changed, 139 insertions(+), 69 deletions(-) diff --git a/il65/astparse.py b/il65/astparse.py index 20c7c1fb9..67c409ad3 100644 --- a/il65/astparse.py +++ b/il65/astparse.py @@ -100,6 +100,16 @@ def parse_arguments(text: str, sourceref: SourceRef) -> List[Tuple[str, Primitiv raise TypeError("ast.Expression expected") +def parse_expr_as_comparison(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef) -> None: + src = SourceLine(text, sourceref) + text = src.preprocess() + try: + node = ast.parse(text, sourceref.file, mode="eval") + except SyntaxError as x: + raise src.to_error(str(x)) + print("AST NODE", node) + + def parse_expr_as_int(text: str, context: Optional[SymbolTable], ppcontext: Optional[SymbolTable], sourceref: SourceRef, *, minimum: int=0, maximum: int=0xffff) -> int: result = parse_expr_as_primitive(text, context, ppcontext, sourceref, minimum=minimum, maximum=maximum) diff --git a/il65/parse.py b/il65/parse.py index 38d579958..8018e6205 100644 --- a/il65/parse.py +++ b/il65/parse.py @@ -944,25 +944,25 @@ class Parser: raise self.PError(str(x)) from x def parse_subroutine_def(self, line: str) -> None: - match = re.match(r"^sub\s+(?P\w+)\s+" - r"\((?P[\w\s:,]*)\)" - r"\s*->\s*" - r"\((?P[\w\s?,]*)\)\s*" - r"(?P\s+=\s+(?P
\S*)|{)\s*$", line) + match = re.fullmatch(r"sub\s+(?P\w+)\s+" + r"\((?P[\w\s:,]*)\)" + r"\s*->\s*" + r"\((?P[\w\s?,]*)\)\s*" + r"(?P\s+=\s+(?P
\S*)|{)\s*", line) if not match: raise self.PError("invalid sub declaration") - code_decl = match.group("decltype") == "{" - name, parameterlist, resultlist, address_str = \ - match.group("name"), match.group("parameters"), match.group("results"), match.group("address") - parameters = [(match.group("name"), match.group("target")) - for match in re.finditer(r"(?:(?:(?P[\w]+)\s*:\s*)?(?P[\w]+))(?:,|$)", parameterlist)] + groups = match.groupdict() + code_decl = groups["decltype"] == "{" + name, parameterlist, resultlist, address_str = groups["name"], groups["parameters"], groups["results"], groups["address"] + parameters = [(m.group("name"), m.group("target")) + for m in re.finditer(r"(?:(?:(?P[\w]+)\s*:\s*)?(?P[\w]+))(?:,|$)", parameterlist)] for _, regs in parameters: if regs not in REGISTER_SYMBOLS: raise self.PError("invalid register(s) in parameter or return values") all_paramnames = [p[0] for p in parameters if p[0]] if len(all_paramnames) != len(set(all_paramnames)): raise self.PError("duplicates in parameter names") - results = [match.group("name") for match in re.finditer(r"\s*(?P(?:\w+)\??)\s*(?:,|$)", resultlist)] + results = [m.group("name") for m in re.finditer(r"\s*(?P(?:\w+)\??)\s*(?:,|$)", resultlist)] subroutine_block = None if code_decl: address = None @@ -1040,31 +1040,29 @@ class Parser: return varname, datatype, length, matrix_dimensions, valuetext def parse_statement(self, line: str) -> ParseResult._AstNode: - match = re.match(r"(?P.*\s*=)\s*(?P[\S]+?)\s*(?P[!]?)\s*(\((?P.*)\))?\s*$", line) + match = re.fullmatch(r"goto\s+(?P[\S]+?)\s*(\((?P.*)\))?\s*", line) if match: - # subroutine call (not a goto) with output param assignment - preserve = not bool(match.group("fcall")) - subname = match.group("subname") - arguments = match.group("arguments") - outputs = match.group("outputs") + # goto + groups = match.groupdict() + subname = groups["subname"] + if '!' in subname: + raise self.PError("goto is always without register preservation, should not have exclamation mark") + arguments = groups["arguments"] + return self.parse_call_or_goto(subname, arguments, None, False, True) + match = re.fullmatch(r"(?P[^\(]*\s*=)?\s*(?P[\S]+?)\s*(?P[!]?)\s*(\((?P.*)\))?\s*", line) + if match: + # subroutine call (not a goto) with possible output param assignment + groups = match.groupdict() + preserve = not bool(groups["fcall"]) + subname = groups["subname"] + arguments = groups["arguments"] + outputs = groups["outputs"] or "" if outputs.strip() == "=": raise self.PError("missing assignment target variables") outputs = outputs.rstrip("=") - if arguments or match.group(4): + if arguments or match.group(4): # group 4 = (possibly empty) parenthesis return self.parse_call_or_goto(subname, arguments, outputs, preserve, False) # apparently it is not a call (no arguments), fall through - match = re.match(r"(?Pgoto\s+)?(?P[\S]+?)\s*(?P[!]?)\s*(\((?P.*)\))?\s*$", line) - if match: - # subroutine or goto call, without output param assignment - is_goto = bool(match.group("goto")) - preserve = not bool(match.group("fcall")) - subname = match.group("subname") - arguments = match.group("arguments") - if is_goto: - return self.parse_call_or_goto(subname, arguments, None, preserve, True) - elif arguments or match.group(4): - return self.parse_call_or_goto(subname, arguments, None, preserve, False) - # apparently it is not a call (no arguments), fall through if line == "return" or line.startswith(("return ", "return\t")): return self.parse_return(line) elif line.endswith(("++", "--")): diff --git a/lib/c64lib.ill b/lib/c64lib.ill index 21bc9732e..453301987 100644 --- a/lib/c64lib.ill +++ b/lib/c64lib.ill @@ -10,7 +10,7 @@ output raw ~ c64 { - memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP + memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP memory .byte COLOR = $0286 ; cursor color @@ -92,7 +92,6 @@ output raw memory .float FL_FR4 = $e2ea ; .25 -; @todo verify clobbered registers? ; note: fac1/2 might get clobbered even if not mentioned in the function's name. ; note: for subtraction and division, the left operand is in fac2, the right operand in fac1. @@ -132,7 +131,7 @@ sub FADDT () -> (A?, X?, Y?) = $b86a ; fac1 += fac2 sub FADD (mflpt: AY) -> (A?, X?, Y?) = $b867 ; fac1 += mflpt value from A/Y sub FSUBT () -> (A?, X?, Y?) = $b853 ; fac1 = fac2-fac1 mind the order of the operands sub FSUB (mflpt: AY) -> (A?, X?, Y?) = $b850 ; fac1 = mflpt from A/Y - fac1 -sub FMULTT () -> (A?, X?, Y?) = $ba2b ; fac1 *= fac2 +sub FMULTT () -> (A?, X?, Y?) = $ba2b ; fac1 *= fac2 sub FMULT (mflpt: AY) -> (A?, X?, Y?) = $ba28 ; fac1 *= mflpt value from A/Y sub FDIVT () -> (A?, X?, Y?) = $bb12 ; fac1 = fac2/fac1 mind the order of the operands sub FDIV (mflpt: AY) -> (A?, X?, Y?) = $bb0f ; fac1 = mflpt in A/Y / fac1 @@ -230,7 +229,7 @@ sub FREADS32 () -> (A?, X?, Y?) { ldx #$a0 jmp $bc4f ; internal BASIC routine } -} +} sub FREADUS32 () -> (A?, X?, Y?) { ; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST) @@ -244,7 +243,7 @@ sub FREADUS32 () -> (A?, X?, Y?) { sub FREADS24AXY (lo: A, mid: X, hi: Y) -> (A?, X?, Y?) { ; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes) - ; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead. + ; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead. asm { sty $62 stx $63 @@ -258,7 +257,7 @@ sub FREADS24AXY (lo: A, mid: X, hi: Y) -> (A?, X?, Y?) { jmp $bc4f ; internal BASIC routine } } - + sub GIVUAYF (uword: AY) -> (A?, X?, Y?) { ; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1 asm { @@ -589,4 +588,48 @@ sub input_chars (buffer: AX) -> (A?, Y) { } } +sub memcopy_basic () -> (A?, X?, Y?) { + ; ---- copy a memory block by using a BASIC ROM routine @todo fix code + ; it calls a function from the basic interpreter, so: + ; - BASIC ROM must be banked in + ; - the source block must be readable (so no RAM hidden under BASIC, Kernal, or I/O) + ; - the target block must be writable (so no RAM hidden under I/O) + ; higher addresses are copied first, so: + ; - moving data to higher addresses works even if areas overlap + ; - moving data to lower addresses only works if areas do not overlap + asm { + lda #src_start + sta $5f + stx $60 + lda #src_end + sta $5a + stx $5b + lda #<(target_start + src_end - src_start) + ldx #>(target_start + src_end - src_start) + sta $58 + stx $59 + jmp $a3bf + } +} + +; macro version of the above memcopy_basic routine: @todo macro support? +; MACRO PARAMS src_start, src_end, target_start +; lda #src_start +; sta $5f +; stx $60 +; lda #src_end +; sta $5a +; stx $5b +; lda #<(target_start + src_end - src_start) +; ldx #>(target_start + src_end - src_start) +; sta $58 +; stx $59 +; jsr $a3bf + + + } diff --git a/reference.md b/reference.md index 5aa3d9268..8fb53b7d4 100644 --- a/reference.md +++ b/reference.md @@ -18,7 +18,7 @@ which aims to provide many conveniences over raw assembly code (even when using - various data types other than just bytes - automatic type conversions - floating point operations -- automatically preserving and restoring CPU registers state, when calling routines that otherwise would clobber these +- automatically preserving and restoring CPU registers state, when calling routines that otherwise would clobber these - abstracting away low level aspects such as zero page handling, program startup, explicit memory addresses - @todo: conditionals and loops - @todo: memory block operations @@ -28,7 +28,7 @@ to write performance critical pieces of code, but otherwise compiles fairly stra into 6502 assembly code. This resulting code is assembled into a binary program by using an external macro assembler, [64tass](https://sourceforge.net/projects/tass64/). It can be compiled pretty easily for various platforms (Linux, Mac OS, Windows) or just ask me -to provide a small precompiled executable if you need that. +to provide a small precompiled executable if you need that. You need [Python 3.5](https://www.python.org/downloads/) or newer to run IL65 itself. IL65 is mainly targeted at the Commodore-64 machine, but should be mostly system independent. @@ -42,7 +42,7 @@ Most of the 64 kilobyte address space can be accessed by your program. | type | memory area | note | |-----------------|-------------------------|-----------------------------------------------------------------| | Zero page | ``$00`` - ``$ff`` | contains many sensitive system variables | -| Hardware stack | ``$100`` - ``$1ff`` | is used by the CPU and should normally not be accessed directly | +| Hardware stack | ``$100`` - ``$1ff`` | is used by the CPU and should normally not be accessed directly | | Free RAM or ROM | ``$0200`` - ``$ffff`` | free to use memory area, often a mix of RAM and ROM | @@ -92,7 +92,7 @@ For the Commodore-64 here is a list of free-to-use zero page locations even when ``$02`` - ``$03`` (but see remark above); ``$04`` - ``$05``; ``$06``; ``$0a``; ``$2a``; ``$52``; ``$93``; -``$f7`` - ``$f8``; ``$f9`` - ``$fa``; ``$fb`` - ``$fc``; ``$fd`` - ``$fe`` +``$f7`` - ``$f8``; ``$f9`` - ``$fa``; ``$fb`` - ``$fc``; ``$fd`` - ``$fe`` IL65 knows about all this: it will use the above zero page locations to place its ZP variables in, until they're all used up. You can instruct it to treat your program as taking over the entire @@ -118,7 +118,7 @@ IL65 supports the following data types: | address-of | 16 bits | | ``#variable`` | | indirect | variable | | ``[ address ]`` | -Strings can be writen in your code as CBM PETSCII or as C-64 screencode variants, +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. @@ -134,7 +134,7 @@ treats those as a value that you manipulate via its address, so the ``#`` is ign For most other types this prefix is not supported. **Indirect addressing:** The ``[address]`` syntax means: the contents of the memory at address, or "indirect addressing". -By default, if not otherwise known, a single byte is assumed. You can add the ``.byte`` or ``.word`` or ``.float`` +By default, if not otherwise known, a single byte is assumed. You can add the ``.byte`` or ``.word`` or ``.float`` type identifier suffix to make it clear what data type the address points to. This addressing mode is only supported for constant (integer) addresses and not for variable types, unless it is part of a subroutine call statement. For an indirect goto call, the 6502 CPU has a special opcode @@ -146,7 +146,7 @@ PROGRAM STRUCTURE In IL65 every line in the source file can only contain *one* statement or declaration. Compilation is done on *one* main source code file, but other files can be imported. - + ### Comments Everything after a semicolon '``;``' is a comment and is ignored. @@ -171,11 +171,11 @@ at the beginning of your program: ### Program Entry Point -Every program has to have one entry point where code execution begins. +Every program has to have one entry point where code execution begins. The compiler looks for the ``start`` label in the ``main`` block for this. For proper program termination, this block has to end with a ``return`` statement (or a ``goto`` call). Blocks and other details are described below. - + ### Blocks @@ -241,7 +241,7 @@ if this makes sense. Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere. Their definition, using the sub statement, includes the specification of the required input- and output parameters. -For now, only register based parameters are supported (A, X, Y and paired registers, +For now, only register based parameters are supported (A, X, Y and paired registers, the carry status bit SC and the interrupt disable bit SI as specials). For subroutine return values, the special SZ register is also available, it means the zero status bit. @@ -280,11 +280,11 @@ You call a subroutine like this: If the subroutine returns one or more values as results, you must use an assignment statement to store those values somewhere: outputvar1, outputvar2 = subroutine ( arg1, arg2, arg3 ) - + The output variables must occur in the correct sequence of return registers as specified in the subroutine's definiton. It is possible to not specify any of them but the compiler will issue a warning then if the result values of a subroutine call are discarded. -Even if the subroutine returns something in a register that already is the correct one +Even if the subroutine returns something in a register that already is the correct one you want to keep, you'll have to explicitly assign the return value to that register. If you omit it, no return value is stored at all (well, unless you call the subroutine without register preserving, see the next paragraph.) @@ -314,15 +314,15 @@ TODOS Required building blocks: additional forms of 'go' statement: including an if clause, comparison statement. - a primitive conditional branch instruction (special case of 'go'): directly translates to a branch instruction: - if[_XX] go