├── .gitignore ├── .reuse └── dep5 ├── HISTORY.md ├── LICENSES ├── LicenseRef-BSD-2-Clause-X-SixtyPical.txt └── Unlicense.txt ├── README.md ├── TODO.md ├── bin └── sixtypical ├── doc ├── 6502 Opcodes.md ├── Design Goals.md ├── Future directions for SixtyPical.md ├── Output Formats.md └── SixtyPical.md ├── eg ├── README.md ├── apple2 │ ├── README.md │ ├── lores.60p │ └── print.60p ├── atari2600 │ ├── .gitignore │ ├── atari-2600-example.oph │ ├── build.sh │ ├── smiley.60p │ └── smiley.oph ├── c64 │ ├── README.md │ ├── demo-game │ │ ├── demo-game.60p │ │ └── run.sh │ ├── hearts.60p │ ├── intr1.60p │ ├── joystick-demo.60p │ ├── petulant │ │ ├── README.md │ │ ├── build.sh │ │ ├── petulant-60p.prg │ │ ├── petulant.60p │ │ ├── petulant.p65 │ │ └── petulant.prg │ ├── ribos │ │ ├── README.md │ │ ├── build.sh │ │ ├── ribos.p65 │ │ ├── ribos.png │ │ ├── ribos.prg │ │ ├── ribos2-60p.prg │ │ ├── ribos2.60p │ │ ├── ribos2.p65 │ │ └── ribos2.prg │ └── screen1.60p ├── rudiments │ ├── README.md │ ├── add.60p │ ├── buffer.60p │ ├── call.60p │ ├── cmp-byte.60p │ ├── cmp-litword.60p │ ├── cmp-word.60p │ ├── conditional.60p │ ├── conditional2.60p │ ├── errorful │ │ ├── README.md │ │ ├── add.60p │ │ ├── range.60p │ │ └── vector.60p │ ├── example.60p │ ├── forever.60p │ ├── goto.60p │ ├── loop.60p │ ├── memloc.60p │ ├── nested-for.60p │ ├── print.60p │ ├── vector-table.60p │ ├── vector.60p │ └── word-table.60p └── vic20 │ └── hearts.60p ├── images └── hearts.png ├── include ├── c64 │ ├── chrout.60p │ └── joystick.60p ├── stdlib │ └── prbyte.60p └── vic20 │ └── chrout.60p ├── src └── sixtypical │ ├── __init__.py │ ├── analyzer.py │ ├── ast.py │ ├── callgraph.py │ ├── compiler.py │ ├── context.py │ ├── emitter.py │ ├── fallthru.py │ ├── gen6502.py │ ├── model.py │ ├── outputter.py │ ├── parser.py │ ├── scanner.py │ └── symtab.py ├── test.sh └── tests ├── SixtyPical Analysis.md ├── SixtyPical Callgraph.md ├── SixtyPical Compilation.md ├── SixtyPical Control Flow.md ├── SixtyPical Fallthru.md ├── SixtyPical Storage.md ├── SixtyPical Syntax.md └── appliances ├── bin └── dcc6502-adapter ├── sixtypical-py2.7.md └── sixtypical.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | vicerc 4 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | 3 | Files: 4 | .gitignore 5 | bin/sixtypical 6 | eg/rudiments/errorful/*.60p 7 | eg/rudiments/*.60p 8 | eg/*/.gitignore 9 | eg/*/*.png 10 | eg/*/*.prg 11 | eg/*/*.sh 12 | images/* 13 | src/sixtypical/__init__.py 14 | test.sh 15 | tests/appliances/* 16 | Copyright: Chris Pressey, the original author of this work, has dedicated it to the public domain. 17 | License: Unlicense 18 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | History of SixtyPical 2 | ===================== 3 | 4 | 9 | 10 | 0.21-2023.0309 11 | -------------- 12 | 13 | * Python 3 is the default interpreter for `sixtypical`. 14 | * Test appliance added for testing under Python 2.7. 15 | * `dcc6502` test adapter made runnable under both 16 | Python 2 and Python 3. 17 | 18 | 0.21 19 | ---- 20 | 21 | * A source file can be included in another source file 22 | by means of the `include` directive. 23 | * A routine can be declared `preserved`, which prevents a 24 | compiler from omitting it from the final executable, even 25 | if it determines it is not called by any other routine. 26 | * The reference implementation constructs a callgraph and 27 | determines the set of routines which are not reachable 28 | (directly or indirectly) from `main`, with an eye to 29 | omitting them from the final executable. 30 | * Added `--prune-unreachable-routines` option, which causes 31 | the compiler to in fact omit routines determined to be 32 | unreachable as described above. 33 | * Added `--include-path` option, which configures the list 34 | of directories that are searched when a source file is 35 | included with the `include` directive. 36 | * Code generation now performs modest peephole optimization 37 | at the end of each routine. This results in better code 38 | generation for constructs in tail position, notably 39 | tail optimization of `calls`, but also for `goto`s and 40 | `if` blocks at the end of a routine. 41 | * Began collecting architecture-specific and portable 42 | include-files for a nascent "standard library". 43 | 44 | 0.20 45 | ---- 46 | 47 | * A `point ... into` block no longer initializes the pointer 48 | by default. A subequent `reset` instruction must be used 49 | to initialize the pointer. The pointer may be reset to any 50 | valid offset within the table (not only 0) and it may be 51 | reset multiple times inside the block. 52 | * Local locations need no longer be static. If they are not 53 | static, they are considered uninitialized until assigned, 54 | and they can be declared with an explicit fixed address. 55 | * Along with `goto`, `call` and `with interrupts off` are 56 | now forbidden inside a `with interrupts off` block. 57 | * More tests to assure that using `call` inside a `point into` 58 | block or inside a `for` block does not cause trouble, 59 | particularly when the routine being called also uses the 60 | variable named in that block. 61 | * Fixed a bug where two local statics could be declared with 62 | the same name. 63 | * Split analysis context support off from analyzer, and 64 | symbol table support from parse, and it their own modules. 65 | * Split the SixtyPical Analysis tests across three files, 66 | and placed test appliances for `sixtypical` in own file. 67 | 68 | 0.19 69 | ---- 70 | 71 | * A `table` may be defined with more than 256 entries, even 72 | though the conventional index syntax can only refer to the 73 | first 256 entries. 74 | * A `pointer` may point inside values of type `byte table`, 75 | allowing access to entries beyond the 256th. 76 | * `buffer` types have been eliminated from the language, 77 | as the above two improvements allow `byte table`s to 78 | do everything `buffer`s previously did. 79 | * When accessing a table with an index, a constant offset 80 | can also be given. 81 | * Accessing a `table` through a `pointer` must be done in 82 | the context of a `point ... into` block. This allows the 83 | analyzer to check *which* table is being accessed. 84 | * Refactored compiler internals so that type information 85 | is stored in a single symbol table shared by all phases. 86 | * Refactored internal data structures that represent 87 | references and types to be immutable `namedtuple`s. 88 | * Added `--dump-exit-contexts` option to `sixtypical`. 89 | * Added a new `--run-on=` option to `sixtypical`, which 90 | replaces the old `loadngo.sh` script. 91 | 92 | 0.18 93 | ---- 94 | 95 | * The "consistent initialization" check inside `if` blocks has 96 | been dropped. If a location is initialized inside one block 97 | but not the other, it is treated as uninitialized afterwards. 98 | * Syntactically, `goto` may only appear at the end of a block. 99 | It need no longer be the final instruction in a routine, 100 | as long as the type context is consistent at every exit. 101 | * When the range of a location is known, `inc` and `dec` 102 | on it will usually shift the known instead of invalidating it. 103 | * `cmp` instruction can now perform a 16-bit unsigned comparison 104 | of `word` memory locations and `word` literals (at the cost of 105 | trashing the `a` register.) 106 | * `add` (resp. `sub`) now support adding (resp. subtracting) a 107 | byte location or a byte literal from a byte location. 108 | * Fixed pathological memory use in the lexical scanner - should 109 | be much less inefficient now when parsing large source files. 110 | * Reorganized the examples in `eg/rudiments/` to make them 111 | officially platform-agnostic and to state the expected output. 112 | 113 | 0.17 114 | ---- 115 | 116 | * `save X, Y, Z { }` now allowed as a shortcut for nested `save`s. 117 | * If the name in a location expression isn't found in the symbol 118 | table, a forward reference will _always_ be generated; and forward 119 | references in _all_ operations will be resolved after parsing. 120 | * As a consequence, trying to call or goto a non-routine-typed symbol 121 | is now an analysis error, not a syntax error. 122 | * Deprecated `routine foo ...` syntax has been removed. 123 | * Split TODO off into own file. 124 | * `sixtypical` no longer writes the compiled binary to standard 125 | output. The `--output` command-line argument should be given 126 | to get a compiled binary; otherwise only analysis is run. 127 | * Internal cleanups, including a hierarchy of `Outputters`. 128 | * All tests pass when `sixtypical` is run under Python 3.5.2. 129 | 130 | 0.16 131 | ---- 132 | 133 | * Added `save` block, which allows the named locations to be modified 134 | arbitrarily inside the block, and automatically restored at the end. 135 | * More thorough tests and justifications written for the case of 136 | assigning a routine to a vector with a "wider" type. 137 | * Support for `copy [ptra]+y, [ptrb]+y` to indirect LDA indirect STA. 138 | * Support for `shl foo` and `shr foo` where `foo` is a byte storage. 139 | * Support for `I a, btable + x` where `I` is `add`, `sub`, `cmp`, 140 | `and`, `or`, or `xor` 141 | * Support for `I btable + x` where `I` is `shl`, `shr`, `inc`, `dec` 142 | * `or a, z`, `and a, z`, and `eor a, z` compile to zero-page operations 143 | if the address of z < 256. 144 | * Removed `--prelude` in favour of specifying both format and prelude 145 | with a single option, `--output-format`. Documentation for same. 146 | 147 | 0.15 148 | ---- 149 | 150 | * Symbolic constants can be defined with the `const` keyword, and can 151 | be used in most places where literal values can be used. 152 | * Added `nop` opcode, which compiles to `NOP` (mainly for timing.) 153 | * Accessing zero-page with `ld` and `st` generates zero-page opcodes. 154 | * A `byte` or `word` table can be initialized with a list of constants. 155 | * Branching and repeating on the `n` flag is now supported. 156 | * The `--optimize-fallthru` option causes the routines of the program 157 | to be re-ordered to maximize the number of cases where a `goto`'ed 158 | routine can be simply "falled through" to instead of `JMP`ed to. 159 | * `--dump-fallthru-info` option outputs the information from the 160 | fallthru analysis phase, in JSON format, to stdout. 161 | * Even without fallthru optimization, `RTS` is no longer emitted after 162 | the `JMP` from compiling a final `goto`. 163 | * Specifying multiple SixtyPical source files will produce a single 164 | compiled result from their combination. 165 | * Rudimentary support for Atari 2600 prelude in a 4K cartridge image, 166 | and an example program in `eg/atari2600` directory. 167 | 168 | 0.14 169 | ---- 170 | 171 | * Added the so-called "open-faced `for` loop", which spans a loop 172 | variable over a finite range, the end of which is fixed. 173 | * "Tail position" is now more correctly determined for the purposes of 174 | insisting that `goto` only appears in it. 175 | * New `--origin` and `--output-format` options added to the compiler. 176 | * Fixed bug when `--prelude` option was missing. 177 | * Fixed bug when reporting line numbers of scanner-level syntax errors. 178 | * Translated the small demo projects Ribos and "The PETulant Cursor" to 179 | SixtyPical, and added them to the `eg/c64/` section of the repo. 180 | * Added a `eg/vic20` example directory, with one VIC-20 example program. 181 | 182 | 0.13 183 | ---- 184 | 185 | * It is a static analysis error if it cannot be proven that a read or write 186 | to a table falls within the defined size of that table. 187 | * The reference analyzer's ability to prove this is currently fairly weak, 188 | but it does exist: 189 | * Loading a constant into a memory location means we know the range 190 | is exactly that one constant value. 191 | * `AND`ing a memory location with a value means the range of the 192 | memory location cannot exceed the range of the value. 193 | * Doing arithmetic on a memory location invalidates our knowledge 194 | of its range. 195 | * Copying a value from one memory location to another copies the 196 | known range as well. 197 | * Cleaned up the internals of the reference implementation (incl. the AST) 198 | and re-organized the example programs in the `eg` subdirectory. 199 | * Most errors produced by the reference implementation now include a line number. 200 | * Compiler supports multiple preludes, specifically both Commodore 64 and 201 | Commodore VIC-20; the `loadngo.sh` script supports both architectures too. 202 | 203 | 0.12 204 | ---- 205 | 206 | * `copy` is now understood to trash `a`, thus it is not valid to use `a` in `copy`. 207 | To compensate, indirect addressing is supported in `ld` and `st`, for example, 208 | as `ld a, [ptr] + y` and `st a, [ptr] + y`. 209 | * Implements the "union rule for trashes" when analyzing `if` blocks. 210 | * Even if we `goto` another routine, we can't trash an output. 211 | * `static` storage locations local to routines can now be defined within routines. 212 | * Small grammar changes that obviate the need for: 213 | * the parentheses in type expressions like `vector (routine ...) table[256]` 214 | * the `forward` keyword in forward references in source of `copy` instruction 215 | * Fixed bug where `trash` was not marking the location as being virtually altered. 216 | 217 | 0.11 218 | ---- 219 | 220 | * Each table has a specified size now (although, bounds checking is not performed.) 221 | * Initialized `byte table` values need not have all 256 bytes initialized. 222 | * Syntax for types has changed. `routine` (with constraints) is a type, while 223 | `vector` is now a type constructor (taking `routine`s only) and `table` is 224 | also a type constructor. This permits a new `vector table` type. 225 | * Added `typedef`, allowing the user to define type aliases for readability. 226 | * Added `define name routine {...}` syntax; `routine name {...}` is now legacy. 227 | * Ability to copy vectors and routines into vector tables, and vectors out of same. 228 | * Removed the evaluator. The reference implementation only analyzes and compiles. 229 | * Fixed bug where index register wasn't required to be initialized before table access. 230 | * Fixed bug where trampolines for indirect calls weren't including a final `RTS`. 231 | 232 | 0.10 233 | ---- 234 | 235 | * Can `call` and `goto` routines that are defined further down in the source code. 236 | * The `forward` modifier can also be used to indicate that the symbol being copied 237 | in a `copy` to a vector is a routine that is defined further down in the source. 238 | * Initialized `word` memory locations. 239 | * Can `copy` a literal word to a word table. 240 | * Subtract word (constant or memory location) from word memory location. 241 | * `trash` instruction explicitly indicates a value is no longer considered meaningful. 242 | * `copy []+y, a` can indirectly read a byte value into the `a` register. 243 | * Initialized `byte table` memory locations. 244 | * Fixed bug which was preventing `if` branches to diverge in what they initialized, 245 | if it was already initialized when going into the `if`. 246 | * Fixed a bug which was making it crash when trying to analyze `repeat forever` loops. 247 | 248 | 0.9 249 | --- 250 | 251 | * Add word (constant or memory location) to word memory location. 252 | * Add word to pointer (unchecked for now). 253 | * Added `word table` type. 254 | * Can `copy` from word storage location to word table and back. 255 | * A `vector` can name itself in its `inputs` and `outputs` or `trashes` sets. 256 | * Implementation: `--debug` shows some extra info during analysis. 257 | * Fixed bug where `copy`ing literal word into word storage used wrong endianness. 258 | * Fixed bug where every memory location was allocated 2 bytes of storage, regardless of type. 259 | * Tests: use https://github.com/tcarmelveilleux/dcc6502 to disassemble code for comparison. 260 | 261 | 0.8 262 | --- 263 | 264 | * Explicit word literals prefixed with `word` token. 265 | * Can `copy` literals into user-defined destinations. 266 | * Fixed bug where loop variable wasn't being checked at end of `repeat` loop. 267 | * `buffer` and `pointer` types. 268 | * `copy ^` syntax to load the addr of a buffer into a pointer. 269 | * `copy []+y` syntax to read and write values to and from memory through a pointer. 270 | 271 | 0.7 272 | --- 273 | 274 | * User-defined `byte` locations can be given an initial value. 275 | * `word` type locations which can be defined and `copy`ed between. 276 | * Can `copy` directly from one user-defined `byte` location to another. 277 | 278 | 0.6 279 | --- 280 | 281 | * Added `routine` and `vector` types, and `copy` instruction. 282 | * Both routines and vectors can declare `inputs`, `outputs`, and `trashes`, 283 | and these must be compatible to assign a routine or vector to a vector. 284 | * Added `goto` (tail call) instruction, jumps to routine or through vector. 285 | * `call` can call a subroutine indirectly, via a vector. 286 | * Routine name is now shown in analysis error messages. 287 | 288 | 0.5 289 | --- 290 | 291 | * Added `byte table` type locations and indexed addressing (`+ x`, `+ y`). 292 | * Integer literals may be given in hexadecimal. 293 | * Line comments may be included in source code by prefixing them with `//`. 294 | 295 | 0.4 296 | --- 297 | 298 | * Added `repeat` loops to the language, which can repeat until a flag 299 | is set (or `not` set), or which can repeat `forever`. 300 | * `if not` inverts the sense of the test. 301 | * Added explicitly-addressed memory locations. 302 | 303 | 0.3 304 | --- 305 | 306 | * Added external routine declarations. 307 | * Added ability to compile to 6502 machine code and output a `PRG` file. 308 | 309 | 0.2 310 | --- 311 | 312 | A complete reboot of SixtyPical 0.1. The reference implementation was 313 | rewritten in Python. The language was much simplified. The aim was to get the 314 | analysis completely right before adding more sophisticated and useful features 315 | in future versions. 316 | 317 | 0.1 318 | --- 319 | 320 | Initial inspired-but-messy version implemented in Haskell. 321 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-BSD-2-Clause-X-SixtyPical.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | Redistributions of source code must retain the above copyright 9 | notices, this list of conditions and the following disclaimer. 10 | 11 | Redistributions in binary form must reproduce the above copyright 12 | notices, this list of conditions, and the following disclaimer in 13 | the documentation and/or other materials provided with the 14 | distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 26 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/Unlicense.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. 4 | 5 | In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and 6 | successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | 10 | For more information, please refer to 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SixtyPical 2 | ========== 3 | 4 | 9 | 10 | _Version 0.21_ 11 | | _See also:_ [Bubble Escape 2K](https://codeberg.org/catseye/Bubble-Escape#bubble-escape) 12 | ∘ [SITU-SOL](https://git.catseye.tc/SITU-SOL/) 13 | 14 | - - - - 15 | 16 | _NOTE: Having met the majority of its goals, the SixtyPical project_ 17 | _might not undergo much more development going forward. See_ 18 | [Future directions for SixtyPical][] _for more information._ 19 | 20 | - - - - 21 | 22 | **SixtyPical** brings advanced static analysis to the [6502][]. 23 | 24 | SixtyPical is a [low-level](#low-level) programming language 25 | supporting some advanced [static analysis](#static-analysis) methods. 26 | Its reference compiler can generate [efficient code](#efficient-code) for 27 | several 6502-based [target platforms](#target-platforms) while catching many 28 | common mistakes at compile-time, reducing the time spent in debugging. 29 | 30 | Quick Start 31 | ----------- 32 | 33 | Make sure you have Python (2.7 or 3.5+) installed. Then 34 | clone this repository and put its `bin` directory on your 35 | executable search path. Then you can run: 36 | 37 | sixtypical 38 | 39 | If you have the [VICE][] emulator suite installed, you can run 40 | 41 | sixtypical --run-on=x64 eg/c64/hearts.60p 42 | 43 | and it will compile the [hearts.60p source code](eg/c64/hearts.60p) and 44 | automatically start it in the `x64` emulator, and you should see: 45 | 46 | ![Screenshot of result of running hearts.60p](images/hearts.png?raw=true) 47 | 48 | You can try `sixtypical --run-on` on other sources in the `eg` directory 49 | tree, which contains more extensive examples, including an entire 50 | game(-like program); see [eg/README.md](eg/README.md) for a listing. 51 | 52 | Features 53 | -------- 54 | 55 | SixtyPical aims to fill this niche: 56 | 57 | * You'd use assembly, but you don't want to spend hours 58 | debugging (say) a memory overrun that happened because of a 59 | ridiculous silly error. 60 | * You'd use C or some other "high-level" language, but you don't 61 | want the extra overhead added by the compiler to manage the 62 | stack and registers. 63 | 64 | SixtyPical gives the programmer a coding regimen on par with assembly 65 | language in terms of size and hands-on-ness, but also able to catch 66 | many ridiculous silly errors at compile time. 67 | 68 | ### Low level 69 | 70 | Many of SixtyPical's primitive instructions resemble those of the 71 | [MOS Technology 6502][] — it is in fact intended to be compiled to 6502 72 | machine code. However, it also provides some "higher-level" operations 73 | based on common 8-bit machine-language programming idioms, including 74 | 75 | * copying values from one register to another (via a third register when 76 | there are no underlying instructions that directly support it) 77 | * copying, adding, and comparing 16-bit values (done in two steps) 78 | * explicit tail calls 79 | * indirect subroutine calls 80 | 81 | While a programmer will find these constructs convenient, their 82 | inclusion in the language is primarily to make programs easier to analyze. 83 | 84 | ### Static analysis 85 | 86 | The SixtyPical language defines an [effect system][], and the reference 87 | compiler [abstractly interprets][] the input program in the manner of 88 | [flow typing][] to confirm that it does not violate it. This can detect 89 | common mistakes such as 90 | 91 | * you forgot to clear carry before adding something to the accumulator 92 | * a subroutine that you called trashes a register you thought it preserved 93 | * you tried to read or write a byte beyond the end of a byte array 94 | * you tried to write the address of something that was not a routine, to 95 | a jump vector 96 | 97 | ### Efficient code 98 | 99 | Unlike most conventional languages, in SixtyPical the programmer must manage 100 | memory very explicitly, selecting the registers and memory locations to store 101 | each piece of data in. So, unlike a C compiler such as [cc65][], a SixtyPical 102 | compiler doesn't need to generate code to handle [calling conventions][] or 103 | [register allocation][]. This results in smaller (and thus faster) programs. 104 | 105 | The flagship demo, a minigame for the Commodore 64, compiles to 106 | a **930**-byte `.PRG` file. 107 | 108 | ### Target platforms 109 | 110 | The reference implementation can analyze and compile SixtyPical programs to 111 | 6502 machine code formats which can run on several 6502-based 8-bit architectures: 112 | 113 | * [Commodore 64][] 114 | * [Commodore VIC-20][] 115 | * [Atari 2600][] 116 | * [Apple II series][] 117 | 118 | For example programs for each of these, see [eg/README.md](eg/README.md). 119 | 120 | Specification 121 | ------------- 122 | 123 | SixtyPical is defined by a specification document, a set of test cases, 124 | and a reference implementation written in Python. 125 | 126 | There are over 400 test cases, written in [Falderal][] format for readability. 127 | In order to run the tests for compilation, [dcc6502][] needs to be installed. 128 | 129 | * [SixtyPical specification](doc/SixtyPical.md) 130 | * [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md) 131 | * [Literate test suite for SixtyPical analysis (operations)](tests/SixtyPical%20Analysis.md) 132 | * [Literate test suite for SixtyPical analysis (storage)](tests/SixtyPical%20Storage.md) 133 | * [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md) 134 | * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md) 135 | * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md) 136 | * [Literate test suite for SixtyPical callgraph construction](tests/SixtyPical%20Callgraph.md) 137 | 138 | Documentation 139 | ------------- 140 | 141 | * [Design Goals](doc/Design%20Goals.md) 142 | * [SixtyPical revision history](HISTORY.md) 143 | * [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md) 144 | * [Output formats supported by `sixtypical`](doc/Output%20Formats.md) 145 | * [TODO](TODO.md) 146 | 147 | [Future directions for SixtyPical]: doc/Future%20directions%20for%20SixtyPical.md 148 | [6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502 149 | [MOS Technology 6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502 150 | [effect system]: https://en.wikipedia.org/wiki/Effect_system 151 | [abstractly interprets]: https://en.wikipedia.org/wiki/Abstract_interpretation 152 | [flow typing]: https://en.wikipedia.org/wiki/Flow-sensitive_typing 153 | [calling conventions]: https://en.wikipedia.org/wiki/Calling_convention 154 | [register allocation]: https://en.wikipedia.org/wiki/Register_allocation 155 | [VICE]: http://vice-emu.sourceforge.net/ 156 | [cc65]: https://cc65.github.io/ 157 | [Commodore 64]: https://en.wikipedia.org/wiki/Commodore_64 158 | [Commodore VIC-20]: https://en.wikipedia.org/wiki/Commodore_VIC-20 159 | [Atari 2600]: https://en.wikipedia.org/wiki/Atari_2600 160 | [Apple II series]: https://en.wikipedia.org/wiki/Apple_II_series 161 | [Falderal]: https://catseye.tc/node/Falderal 162 | [dcc6502]: https://github.com/tcarmelveilleux/dcc6502 163 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TODO for SixtyPical 2 | =================== 3 | 4 | 9 | 10 | Language 11 | -------- 12 | 13 | ### Save values to other-than-the-stack 14 | 15 | Allow 16 | 17 | save a to temp_a { 18 | ... 19 | } 20 | 21 | Which uses some other storage location instead of the stack. A local non-static 22 | would be a good candidate for such. At any rate, the location must not 23 | be writeable by anything that is called from within the block. So, probably 24 | just restrict this to local non-statics. 25 | 26 | ### Copy byte to/from table 27 | 28 | Do we want a `copy bytevar, table + x` instruction? We don't currently have one. 29 | You have to `ld a`, `st a`. I think maybe we should have one. 30 | 31 | ### Character literals 32 | 33 | For goodness sake, let the programmer say `'A'` instead of `65`. 34 | 35 | ### Character set mapping 36 | 37 | Not all computers think `'A'` should be `65`. Allow the character set to be 38 | mapped. Probably copy what Ophis does. 39 | 40 | ### Pointers into non-byte tables 41 | 42 | Right now you cannot get a pointer into a non-byte (for instance, word or vector) table. 43 | 44 | Word and vector tables are stored as two byte tables in memory. This is useful for 45 | indexed access, but makes pointer access more difficult. 46 | 47 | Laying them out for pointer access would make indexed access more difficult. 48 | 49 | ### Saving non-byte values 50 | 51 | Right now you cannot save a word value. 52 | 53 | There doesn't seem to be a hugely pressing reason why not. 54 | 55 | Analysis 56 | -------- 57 | 58 | ### Forbid recursion 59 | 60 | What happens if a routine calls itself, directly or indirectly? Many 61 | constraints might be violated in this case. We should probably disallow 62 | recursion by default. (Which means assembling the callgraph in all cases.) 63 | 64 | However note, it's okay for a routine to goto itself. It's a common 65 | pattern for implementing a state machine, for a routine to tail-goto a 66 | vector, which might contain the address of the same routine. 67 | 68 | The problems only come up, I think, when a routine calls itself re-entrantly. 69 | 70 | So the callgraph would need to distinguish between these two cases. 71 | 72 | ### Analyze memory usage 73 | 74 | If you define two variables that occupy the same address, an analysis error ought 75 | to be raised. (But there should also be a way to annotate this as intentional. 76 | Intentionally making two tables overlap could be valuable. However, the analysis 77 | will probably completely miss this fact.) 78 | 79 | Optimization 80 | ------------ 81 | 82 | ### Space optimization of local non-statics 83 | 84 | If there are two routines A and B, and A never calls B (even indirectly), and 85 | B never calls A (even indirectly), then their non-static locals can 86 | be allocated at the same space. 87 | 88 | This is not just an impressive trick -- in the presence of local pointers, which 89 | use up a word in zero-page, which we consider a precious resource, it allow those 90 | zero-page locations to be re-used. 91 | 92 | Implementation 93 | -------------- 94 | 95 | ### Filename and line number in analysis error messages 96 | 97 | For analysis errors, there is a line number, but it's the line of the routine 98 | after the routine in which the analysis error occurred. Fix this. 99 | 100 | ### Better selection of options 101 | 102 | `-O` should turn on the standard optimizations. 103 | 104 | There should maybe be a flag to turn off tail-call optimization. 105 | 106 | Some options should automatically add the appropriate architecture include 107 | directory to the path. 108 | 109 | Distribution 110 | ------------ 111 | 112 | ### Demo game 113 | 114 | Seems you're not be able to get killed unless you go off the top or bottom of 115 | the screen? In particular, you cannot collide with a bar? 116 | 117 | Blue-skying 118 | ----------- 119 | 120 | ### Pointers associated globally with a table(?) 121 | 122 | We have `point into` blocks, but we would also like to sometimes pass a pointer 123 | around to different routines, and have them all "know" what table it operates on. 124 | 125 | We could associate every pointer variable with a specific table variable, in its 126 | declaration. This makes some things simple, and would allow us to know what table a 127 | pointer is supposed to point into, even if that pointer was passed into our routine. 128 | 129 | One drawback is that it would limit each pointer to be used only on one table. Since a 130 | pointer basically represents a zero-page location, and since those are a relatively scarce 131 | resource, we would prefer if a single pointer could be used to point into different tables 132 | at different times. 133 | 134 | These can co-exist with general, non-specific-table-linked `pointer` variables. 135 | 136 | If we have local pointers and space optimization for local non-statics, though, 137 | these don't add as much. 138 | -------------------------------------------------------------------------------- /bin/sixtypical: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from os.path import realpath, dirname, join 4 | import sys 5 | 6 | sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src')) 7 | 8 | # ----------------------------------------------------------------- # 9 | 10 | from argparse import ArgumentParser 11 | import codecs 12 | import json 13 | from pprint import pprint 14 | from subprocess import check_call 15 | import sys 16 | from tempfile import NamedTemporaryFile 17 | import traceback 18 | 19 | from sixtypical.symtab import SymbolTable 20 | from sixtypical.parser import Parser, load_program, merge_programs 21 | from sixtypical.analyzer import Analyzer 22 | from sixtypical.callgraph import construct_callgraph, prune_unreachable_routines 23 | from sixtypical.outputter import outputter_class_for 24 | from sixtypical.compiler import Compiler 25 | 26 | 27 | def process_input_files(filenames, options): 28 | symtab = SymbolTable() 29 | include_path = options.include_path.split(':') 30 | 31 | programs = [] 32 | 33 | for filename in options.filenames: 34 | program = load_program(filename, symtab, include_path, include_file=False) 35 | if options.debug: 36 | print(symtab) 37 | programs.append(program) 38 | 39 | if options.parse_only: 40 | return 41 | 42 | program = merge_programs(programs) 43 | 44 | analyzer = Analyzer(symtab, debug=options.debug) 45 | 46 | try: 47 | analyzer.analyze_program(program) 48 | finally: 49 | if options.dump_exit_contexts: 50 | sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ': '))) 51 | sys.stdout.write("\n") 52 | 53 | callgraph = construct_callgraph(program) 54 | if options.dump_callgraph: 55 | sys.stdout.write(json.dumps(callgraph, indent=4, sort_keys=True, separators=(',', ': '))) 56 | if options.prune_unreachable_routines: 57 | program = prune_unreachable_routines(program, callgraph) 58 | 59 | compilation_roster = None 60 | if options.optimize_fallthru: 61 | from sixtypical.fallthru import FallthruAnalyzer 62 | 63 | fa = FallthruAnalyzer(symtab, debug=options.debug) 64 | fa.analyze_program(program) 65 | compilation_roster = fa.serialize() 66 | if options.dump_fallthru_info: 67 | sys.stdout.write(json.dumps(compilation_roster, indent=4, sort_keys=True, separators=(',', ': '))) 68 | 69 | if options.analyze_only or (options.output is None and not options.run_on): 70 | return 71 | 72 | start_addr = None 73 | if options.origin is not None: 74 | if options.origin.startswith('0x'): 75 | start_addr = int(options.origin, 16) 76 | else: 77 | start_addr = int(options.origin, 10) 78 | 79 | if options.run_on: 80 | fh = NamedTemporaryFile(delete=False) 81 | output_filename = fh.name 82 | Outputter = outputter_class_for({ 83 | 'x64': 'c64-basic-prg', 84 | 'xvic': 'vic20-basic-prg', 85 | 'stella': 'atari2600-cart', 86 | }.get(options.run_on)) 87 | else: 88 | fh = open(options.output, 'wb') 89 | output_filename = options.output 90 | Outputter = outputter_class_for(options.output_format) 91 | 92 | outputter = Outputter(fh, start_addr=start_addr) 93 | outputter.write_prelude() 94 | compiler = Compiler(symtab, outputter.emitter) 95 | compiler.compile_program(program, compilation_roster=compilation_roster) 96 | outputter.write_postlude() 97 | if options.debug: 98 | pprint(outputter.emitter) 99 | else: 100 | outputter.emitter.serialize_to(fh) 101 | 102 | fh.close() 103 | 104 | if options.run_on: 105 | emu = { 106 | 'x64': "x64 -config vicerc", 107 | 'xvic': "xvic -config vicerc", 108 | 'stella': "stella" 109 | }.get(options.run_on) 110 | if not emu: 111 | raise ValueError("No emulator configured for selected --run-on '{}'".format(options.output_format)) 112 | 113 | command = "{} {}".format(emu, output_filename) 114 | check_call(command, shell=True) 115 | 116 | 117 | if __name__ == '__main__': 118 | argparser = ArgumentParser() 119 | 120 | argparser.add_argument( 121 | 'filenames', metavar='FILENAME', type=str, nargs='+', 122 | help="The SixtyPical source files to compile." 123 | ) 124 | 125 | argparser.add_argument( 126 | "--output", "-o", type=str, metavar='FILENAME', 127 | help="File to which generated 6502 code will be written." 128 | ) 129 | argparser.add_argument( 130 | "--origin", type=str, default=None, 131 | help="Location in memory where the `main` routine will be " 132 | "located. Default: depends on output format." 133 | ) 134 | argparser.add_argument( 135 | "--output-format", type=str, default='raw', 136 | help="Executable format to produce; also sets a default origin. " 137 | "Options are: raw, prg, c64-basic-prg, vic20-basic-prg, atari2600-cart." 138 | "Default: raw." 139 | ) 140 | 141 | argparser.add_argument( 142 | "--include-path", "-I", type=str, metavar='PATH', default='.', 143 | help="A colon-separated list of directories in which to look for " 144 | "files which are included during `include` directives." 145 | ) 146 | 147 | argparser.add_argument( 148 | "--analyze-only", 149 | action="store_true", 150 | help="Only parse and analyze the program; do not compile it." 151 | ) 152 | argparser.add_argument( 153 | "--dump-exit-contexts", 154 | action="store_true", 155 | help="Dump a map, in JSON, of the analysis context at each exit of each routine " 156 | "after analyzing the program." 157 | ) 158 | argparser.add_argument( 159 | "--optimize-fallthru", 160 | action="store_true", 161 | help="Reorder the routines in the program to maximize the number of tail calls " 162 | "that can be removed by having execution 'fall through' to the next routine." 163 | ) 164 | argparser.add_argument( 165 | "--dump-fallthru-info", 166 | action="store_true", 167 | help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program." 168 | ) 169 | argparser.add_argument( 170 | "--prune-unreachable-routines", 171 | action="store_true", 172 | help="Omit code for unreachable routines (as determined by the callgraph) " 173 | "from the final output." 174 | ) 175 | argparser.add_argument( 176 | "--dump-callgraph", 177 | action="store_true", 178 | help="Dump the call graph, in JSON, to stdout after analyzing the program." 179 | ) 180 | argparser.add_argument( 181 | "--parse-only", 182 | action="store_true", 183 | help="Only parse the program; do not analyze or compile it." 184 | ) 185 | argparser.add_argument( 186 | "--debug", 187 | action="store_true", 188 | help="Display debugging information when analyzing and compiling." 189 | ) 190 | argparser.add_argument( 191 | "--run-on", type=str, default=None, 192 | help="If given, engage 'load-and-go' operation with the given emulator: write " 193 | "the output to a temporary filename using an appropriate --output-format, " 194 | "and boot the emulator with it. Options are: x64, xvic, stella." 195 | ) 196 | argparser.add_argument( 197 | "--traceback", 198 | action="store_true", 199 | help="When an error occurs, display a full Python traceback." 200 | ) 201 | argparser.add_argument( 202 | "--version", 203 | action="version", 204 | version="%(prog)s 0.21" 205 | ) 206 | 207 | options, unknown = argparser.parse_known_args(sys.argv[1:]) 208 | remainder = ' '.join(unknown) 209 | 210 | try: 211 | process_input_files(options.filenames, options) 212 | except Exception as e: 213 | if options.traceback: 214 | raise 215 | else: 216 | traceback.print_exception(e.__class__, e, None) 217 | sys.exit(1) 218 | -------------------------------------------------------------------------------- /doc/6502 Opcodes.md: -------------------------------------------------------------------------------- 1 | 6502 Opcodes 2 | ============ 3 | 4 | 9 | 10 | As used or unused in SixtyPical. 11 | 12 | ### ld ### 13 | 14 | ld a, 123 → LDA #123 15 | ld a, lives → LDA LIVES 16 | ld x, 123 → LDX #123 17 | ld x, lives → LDX LIVES 18 | ld y, 123 → LDY #123 19 | ld y, lives → LDY LIVES 20 | ld x, a → TAX 21 | ld y, a → TAY 22 | ld a, x → TXA 23 | ld a, y → TYA 24 | 25 | ### st ### 26 | 27 | st a, lives → STA LIVES 28 | st x, lives → STX LIVES 29 | st y, lives → STY LIVES 30 | st on, c → SEC 31 | st off, c → CLC 32 | 33 | ### add dest, src ### 34 | 35 | add a, delta → ADC DELTA 36 | add a, 1 → ADC #1 37 | 38 | ### inc ### 39 | 40 | inc x → INX 41 | inc y → INY 42 | inc lives → INC LIVES 43 | 44 | ### sub ### 45 | 46 | sub a, delta → SBC DELTA 47 | sub a, 1 → SBC #1 48 | 49 | ### dec ### 50 | 51 | dec x → DEX 52 | dec y → DEY 53 | dec lives → DEC LIVES 54 | 55 | ### cmp ### 56 | 57 | cmp a, delta → CMP DELTA 58 | cmp a, 1 → CMP #1 59 | cmp x, 1 → CPX #1 60 | cmp y, 1 → CPY #1 61 | 62 | ### and, or, xor ### 63 | 64 | and a, 8 → AND #8 65 | or a, 8 → ORA #8 66 | xor a, 8 → EOR #8 67 | 68 | ### shl, shr ### 69 | 70 | shl 71 | shr 72 | 73 | shl a → ROL A 74 | shl lives → ROL LIVES 75 | shr a → ROR A 76 | shr lives → ROR LIVES 77 | 78 | ### call ### 79 | 80 | call routine → JSR ROUTINE 81 | 82 | ### if ### 83 | 84 | if z → BEQ LABEL 85 | if not z → BNE LABEL 86 | if n → BMI LABEL 87 | if not n → BPL LABEL 88 | if c → BCS LABEL 89 | if not c → BCC LABEL 90 | if v → BVS LABEL 91 | if not v → BVC LABEL 92 | 93 | ### 6502 instructions unsupported ### 94 | 95 | ASL Shift Left One Bit (Memory or Accumulator) 96 | LSR Shift Right One Bit (Memory or Accumulator) 97 | 98 | BIT Test Bits in Memory with Accumulator 99 | BRK Force Break 100 | 101 | CLD Clear Decimal Mode 102 | CLI Clear interrupt Disable Bit 103 | CLV Clear Overflow Flag 104 | 105 | NOP No Operation 106 | 107 | JMP Jump to New Location // but may be generated as part of `if` 108 | 109 | PHA Push Accumulator on Stack 110 | PHP Push Processor Status on Stack 111 | PLA Pull Accumulator from Stack 112 | PLP Pull Processor Status from Stack 113 | 114 | RTI Return from Interrupt 115 | RTS Return from Subroutine 116 | 117 | SED Set Decimal Mode 118 | SEI Set Interrupt Disable Status 119 | 120 | TSX Transfer Stack Pointer to Index X 121 | TXS Transfer Index X to Stack Pointer 122 | -------------------------------------------------------------------------------- /doc/Design Goals.md: -------------------------------------------------------------------------------- 1 | Design Goals for SixtyPical 2 | =========================== 3 | 4 | 9 | 10 | (draft) 11 | 12 | The intent of SixtyPical is to have a very low-level language that 13 | benefits from abstract interpretation. 14 | 15 | "Very low-level" means, on a comparable level of abstraction as 16 | assembly language. 17 | 18 | In the original vision for SixtyPical, SixtyPical instructions mapped 19 | nearly 1:1 to 6502 instructions. However, many times when programming 20 | in 6502 you're using idioms (e.g. adding a 16-bit constant to a 16-bit 21 | value stored in 2 bytes) and it's just massively easier to analyze such 22 | actions when they are represented by a single instruction. 23 | 24 | So SixtyPical instructions are similar to, inspired by, and have 25 | analogous restrictions as 6502 instructions, but in many ways, they 26 | are more abstract. For example, `copy`. 27 | 28 | The intent is that programming in SixtyPical is a lot like programming 29 | in 6052 assembler, but it's harder to make a stupid error that you have 30 | to spend a lot of time debugging. 31 | 32 | The intent is not to make it absolutely impossible to make such errors, 33 | just harder. 34 | 35 | ### Things it will Not Do ### 36 | 37 | To emphasize the point, the intent is not to make it impossible to make 38 | data-usage (and other) errors, just harder. 39 | 40 | Here are some things SixtyPical will not try to detect or prevent you 41 | from doing: 42 | 43 | * Check that a vector is initialized before it's called. 44 | * Check that the stack has enough room on it. 45 | * Prevent bad things happening (e.g. clobbering a static storage 46 | location) because of a recursive call. (You can always recursively 47 | call yourself through a vector.) 48 | * Check that reads and writes to a buffer are in bounds. (This may 49 | happen someday, but it's difficult. It's more likely that this 50 | will happen for tables, than for buffers.) 51 | 52 | At one point I wanted to do a call-tree analysis to find sets of 53 | routines that would never be called together (i.e. would never be on 54 | the call stack at the same time) and allow any static storage locations 55 | defined within them to occupy the same addresses, i.e. allow storage 56 | to be re-used across these routines. But, in the presence of vectors, 57 | this becomes difficult (see "Prevent bad things happening", above.) 58 | Also, it would usually only save a few bytes of storage space. 59 | 60 | ### Some Background ### 61 | 62 | The ideas in SixtyPical came from a couple of places. 63 | 64 | One major impetus was when I was working on [Shelta][], trying to cram 65 | all that code for that compiler into 512 bytes. This involved looking 66 | at the x86 registers and thinking hard about which ones were preserved 67 | when (and which ones weren't) and making the best use of that. And 68 | while doing that, one thing that came to mind was: I Bet The Assembler 69 | Could Track This. 70 | 71 | Another influence was around 2007 when "Typed Assembly Language" (and 72 | "Proof Carrying Code") were all the rage. I haven't heard about them 73 | in a while, so I guess they turned out to be research fads? But for a 74 | while there, it was all Necula, Necula, Necula. Anyway, I remember at 75 | the time looking into TAL and expecting to find something that matched 76 | the impression I had pre-formulated about what a "Typed Assembly" 77 | might be like. And finding that it didn't match my vision very well. 78 | 79 | I don't actually remember what TAL seemed like to me at the time, but 80 | what I had in mind was more like SixtyPical. 81 | 82 | (I'll also write something about abstract interpretation here at some 83 | point, hopefully.) 84 | -------------------------------------------------------------------------------- /doc/Future directions for SixtyPical.md: -------------------------------------------------------------------------------- 1 | Future directions for SixtyPical 2 | ================================ 3 | 4 | 9 | 10 | [SixtyPical](https://codeberg.org/catseye/SixtyPical) has reached a mature 11 | stage of development. There are small features that could be added, but 12 | they are minor compared to the main features 13 | (abstract-interpretation/symbolic-execution/flow-typing-based 14 | static analysis and optimization of low-level 6502 programs). 15 | So the question arises -- what would be next for SixtyPical? 16 | 17 | It would be nice to port SixtyPical to work on a ISA other than the 6502 -- 18 | the Z80, for example. Or, a more practical choice would be the X86 19 | architecture. 20 | 21 | It would also be nice if it could somehow be made to work on regular 22 | assembly language programs. It would still be acceptable if, in this 23 | case, it only worked on programs written in a particular style, to be 24 | compatible with SixtyPical (structured "for" loops, no arbitrary jumps, 25 | and so forth). 26 | 27 | It would also be nice to simply generalize the idea: a generic 28 | low-level language with a generic set of registers 29 | (global variables), the rules for which we can specify as part of 30 | the program. The static analyzer then checks the program according to the rules. 31 | 32 | It would also be nice to design a more formal theory behind all 33 | this. While SixtyPical works well for what it does, much of it was 34 | "unit-tested into existence" and the theories behind these parts are not made explicit. 35 | 36 | In fact, I think we can combine all these ideas. (And, if these are 37 | really all nice to have in a next version of SixtyPical -- we should.) 38 | 39 | We can do this by splitting it up into a few different phases: 40 | 41 | The first phase is a "frontend" that takes a 6502 assembly language program in a common 42 | 6502 assembly language format (perhaps with annotations, or perhaps accompanied by a 43 | configuration file) and translated it into a program description in the generic language, 44 | including the specification of the rules particular to the 6502. 45 | 46 | There is then a generic analyzer which checks the program in the 47 | generic language. 48 | 49 | If all the checks pass, then our guarantees have been met and the 50 | original assembly language program can simply be assembled, using any 51 | assembler that can understand its format. (This approach is similar 52 | to using a model checker.) 53 | 54 | Unlike SixtyPical currently, 55 | which is an optimizing translator, in this method there would be no 56 | optimizations applied. But I think this is an acceptable trade-off. 57 | It is especially acceptable if the assembly language input can be hand-optimized 58 | and still be checked by the analyzer, but this would be trickier to accomplish. 59 | 60 | Then, to support other architectures, one could define a similar "frontend" 61 | which works on Z80 assembly code, or X86 assembly, or what have you. 62 | 63 | In practice, it would probably be easiest to start with a 64 | "frontend" which converts 6502 assembly to the existing SixtyPical 65 | language. Then design and implement the generic language. 66 | Then re-target the frontend to the generic language. 67 | -------------------------------------------------------------------------------- /doc/Output Formats.md: -------------------------------------------------------------------------------- 1 | Output Formats 2 | ============== 3 | 4 | 9 | 10 | `sixtypical` can generate an output file in a number of formats. 11 | 12 | ### `raw` 13 | 14 | The file contains only the emitted bytes of the compiled SixtyPical 15 | program. 16 | 17 | The default origin is $0000; you will likely want to override this. 18 | 19 | Note that the origin is not stored in the output file in this format; 20 | that information must be recorded separately. 21 | 22 | ### `prg` 23 | 24 | The first two bytes of the file contain the origin address in 25 | little-endian format. The remainder of the file is the emitted bytes 26 | of the compiled SixtyPical program, starting at that origin. 27 | 28 | The default origin is $C000; you will likely want override this. 29 | 30 | This format coincides with Commodore's PRG format for disk files, 31 | thus its name. 32 | 33 | ### `c64-basic-prg` 34 | 35 | The first few bytes of the file contain a short Commodore 2.0 BASIC 36 | program. Directly after this is the emitted bytes of the compiled 37 | SixtyPical program. The BASIC program contains a `SYS` to that code. 38 | 39 | The default origin is $0801; it is unlikely that you will want to 40 | override this. 41 | 42 | This format allows the PRG file to be loaded and run on a Commodore 64 43 | with 44 | 45 | LOAD"FOO.PRG",8:RUN 46 | 47 | ### `vic20-basic-prg` 48 | 49 | Exactly like `--c64-basic-prg` except intended for the Commodore VIC-20. 50 | 51 | The default origin is $1001; it is unlikely that you will want to 52 | override this. 53 | 54 | This format allows the PRG file to be loaded and run on a VIC-20 with 55 | 56 | LOAD"FOO.PRG",8:RUN 57 | 58 | ### `atari2600-cart` 59 | 60 | The file starts with a short machine-language prelude which is intended 61 | to initialize an Atari 2600 system, followed by the emitted bytes of the 62 | compiled SixtyPical program. 63 | 64 | The file is padded to 4096 bytes in length. The padding is mostly 65 | zeroes, except for the final 4 bytes of the file, which consist of 66 | two addresses in little-endian format; both are the origin address. 67 | 68 | The default origin is $F000; it is unlikely you will want to 69 | override this. 70 | 71 | This is the format used by Atari 2600 cartridges. 72 | -------------------------------------------------------------------------------- /eg/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | This directory contains SixtyPical example programs, categorized 8 | in subdirectories by machine architecture. 9 | 10 | ### rudiments 11 | 12 | In the [rudiments](rudiments/) directory are programs which are 13 | meant to demonstrate the elementary features of SixtyPical, and 14 | to serve as manual integration test cases. See 15 | [the README in that directory](rudiments/README.md) for details. 16 | 17 | ### c64 18 | 19 | In the [c64](c64/) directory are programs that run on the Commodore 64. 20 | The directory itself contains some simple demos, for example 21 | [hearts.60p](c64/hearts.60p), while there are subdirectories for more 22 | elaborate demos, like the flagship demo game. See 23 | [the README in that directory](c64/README.md) for details. 24 | 25 | ### vic20 26 | 27 | In the [vic20](vic20/) directory are programs that run on the 28 | Commodore VIC-20. The directory itself contains some simple demos, 29 | for example [hearts.60p](vic20/hearts.60p). 30 | 31 | ### atari2600 32 | 33 | In the [atari2600](atari2600/) directory are programs that run on the 34 | Atari 2600 (4K cartridge). The directory itself contains a simple 35 | demo, [smiley.60p](atari2600/smiley.60p) which was converted from an 36 | older Atari 2600 skeleton program written in [Ophis][]. 37 | 38 | ### apple2 39 | 40 | In the [apple2](apple2/) directory are programs that run on 41 | Apple II series computers (Apple II+, Apple //e). `sixtypical`'s 42 | support for this architecture could be called embryonic. 43 | 44 | [Ophis]: http://michaelcmartin.github.io/Ophis/ 45 | -------------------------------------------------------------------------------- /eg/apple2/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | This directory contains SixtyPical example programs 8 | specifically for the Apple II series of computers. 9 | 10 | See the [README in the parent directory](../README.md) for 11 | more information on these example programs. 12 | 13 | Note that `sixtypical` does not currently support "load 14 | and go" execution of these programs, because constructing 15 | an Apple II disk image file on the fly is not something 16 | it can currently do. If you have the linapple sources 17 | checked out, and the a2tools available, you could do 18 | something like this: 19 | 20 | bin/sixtypical --traceback --origin=0x2000 --output-format=raw eg/apple2/prog.60p --output prog.bin 21 | cp /path/to/linapple/res/Master.dsk sixtypical.dsk 22 | a2rm sixtypical.dsk PROG 23 | a2in B sixtypical.dsk PROG prog.bin 24 | linapple -d1 sixtypical.dsk -autoboot 25 | 26 | and then enter 27 | 28 | BLOAD PROG 29 | CALL 8192 30 | 31 | Ideally you could 32 | 33 | BRUN PROG 34 | 35 | But that does not always return to BASIC and I'm not sure why. 36 | -------------------------------------------------------------------------------- /eg/apple2/lores.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | byte ds_graphics @ $C050 6 | byte ds_text @ $C051 7 | byte ds_full @ $C052 8 | byte ds_split @ $C053 9 | byte ds_page1 @ $C054 10 | byte ds_page2 @ $C055 11 | byte ds_lores @ $C056 12 | byte ds_hires @ $C057 13 | 14 | define main routine 15 | inputs a 16 | outputs ds_lores, ds_page1, ds_split, ds_graphics 17 | trashes a, z, n 18 | { 19 | ld a, 0 20 | st a, ds_lores 21 | st a, ds_page1 22 | st a, ds_split 23 | st a, ds_graphics 24 | } 25 | -------------------------------------------------------------------------------- /eg/apple2/print.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | // Write ">AB>" to "standard output" 6 | 7 | define cout routine 8 | inputs a 9 | trashes a 10 | @ $FDED 11 | 12 | define main routine 13 | trashes a, z, n 14 | { 15 | ld a, 62 16 | call cout 17 | ld a, 65 18 | call cout 19 | ld a, 66 20 | call cout 21 | ld a, 62 22 | call cout 23 | } 24 | -------------------------------------------------------------------------------- /eg/atari2600/.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.disasm.txt 3 | -------------------------------------------------------------------------------- /eg/atari2600/atari-2600-example.oph: -------------------------------------------------------------------------------- 1 | ; 2 | ; atari-2600-example.oph 3 | ; Skeleton code for an Atari 2600 ROM, 4 | ; plus an example of reading the joystick. 5 | ; By Chris Pressey, November 2, 2012. 6 | ; 7 | ; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 8 | ; For more information, please refer to 9 | ; SPDX-License-Identifier: Unlicense 10 | ; 11 | ; Based on Chris Cracknell's Atari 2600 clock (also in the public domain): 12 | ; http://everything2.com/title/An+example+of+Atari+2600+source+code 13 | ; 14 | ; to build and run in Stella: 15 | ; ophis atari-2600-example.oph -o example.bin 16 | ; stella example.bin 17 | ; 18 | ; More useful information can be found in the Stella Programmer's Guide: 19 | ; http://alienbill.com/2600/101/docs/stella.html 20 | ; 21 | 22 | ; 23 | ; Useful system addresses (TODO: briefly describe each of these.) 24 | ; 25 | 26 | .alias VSYNC $00 27 | .alias VBLANK $01 28 | .alias WSYNC $02 29 | .alias NUSIZ0 $04 30 | .alias NUSIZ1 $05 31 | .alias COLUPF $08 32 | .alias COLUBK $09 33 | .alias PF0 $0D 34 | .alias PF1 $0E 35 | .alias PF2 $0F 36 | .alias SWCHA $280 37 | .alias INTIM $284 38 | .alias TIM64T $296 39 | .alias CTRLPF $0A 40 | .alias COLUP0 $06 41 | .alias COLUP1 $07 42 | .alias GP0 $1B 43 | .alias GP1 $1C 44 | .alias HMOVE $2a 45 | .alias RESP0 $10 46 | .alias RESP1 $11 47 | 48 | ; 49 | ; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF). 50 | ; Thus, typically, the program will occupy all that space too. 51 | ; 52 | ; Zero-page RAM we can use with impunity starts at $80 and goes 53 | ; upward (at least until $99, but probably further.) 54 | ; 55 | 56 | .alias colour $80 57 | .alias luminosity $81 58 | .alias joystick_delay $82 59 | 60 | .org $F000 61 | 62 | ; 63 | ; Standard prelude for Atari 2600 cartridge code. 64 | ; 65 | ; Get various parts of the machine into a known state: 66 | ; 67 | ; - Disable interrupts 68 | ; - Clear the Decimal flag 69 | ; - Initialize the Stack Pointer 70 | ; - Zero all bytes in Zero Page memory 71 | ; 72 | 73 | start: 74 | sei 75 | cld 76 | ldx #$FF 77 | txs 78 | lda #$00 79 | 80 | zero_loop: 81 | sta $00, x 82 | dex 83 | bne zero_loop 84 | 85 | ; and fall through to... 86 | 87 | ; 88 | ; Initialization. 89 | ; 90 | ; - Clear the Playfield Control register. 91 | ; - Set the player (sprite) colour to light green (write to COLUP0.) 92 | ; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.) 93 | ; 94 | 95 | lda #$00 96 | sta CTRLPF 97 | lda #$0c 98 | sta colour 99 | lda #$0a 100 | sta luminosity 101 | lda #$00 102 | sta NUSIZ0 103 | 104 | ; and fall through to... 105 | 106 | ; 107 | ; Main loop. 108 | ; 109 | ; A typical main loop consists of: 110 | ; - Waiting for the frame to start (vertical blank period) 111 | ; - Displaying stuff on the screen (the _display kernel_) 112 | ; - Doing any processing you like (reading joysticks, updating program state, 113 | ; etc.), as long as you get it all done before the next frame starts! 114 | ; 115 | 116 | main: 117 | jsr vertical_blank 118 | jsr display_frame 119 | jsr read_joystick 120 | jmp main 121 | 122 | ; 123 | ; Vertical blank routine. 124 | ; 125 | ; In brief: wait until it is time for the next frame of video. 126 | ; TODO: describe this in more detail. 127 | ; 128 | 129 | vertical_blank: 130 | ldx #$00 131 | lda #$02 132 | sta WSYNC 133 | sta WSYNC 134 | sta WSYNC 135 | sta VSYNC 136 | sta WSYNC 137 | sta WSYNC 138 | lda #$2C 139 | sta TIM64T 140 | lda #$00 141 | sta WSYNC 142 | sta VSYNC 143 | rts 144 | 145 | ; 146 | ; Display kernal. 147 | ; 148 | ; First, wait until it's time to display the frame. 149 | ; 150 | 151 | .scope 152 | display_frame: 153 | lda INTIM 154 | bne display_frame 155 | 156 | ; 157 | ; (After that loop finishes, we know the accumulator must contain 0.) 158 | ; Wait for the next scanline, zero HMOVE (for some reason; TODO discover 159 | ; this), then turn on the screen. 160 | ; 161 | 162 | sta WSYNC 163 | sta HMOVE 164 | sta VBLANK 165 | 166 | ; 167 | ; Actual work in the display kernal is done here. 168 | ; 169 | ; This is a pathological approach to writing a display kernal. 170 | ; This wouldn't be how you'd do things in a game. So be it. 171 | ; One day I may improve it. For now, be happy that it displays 172 | ; anything at all! 173 | ; 174 | 175 | ; 176 | ; Wait for $3f (plus one?) scan lines to pass, by waiting for 177 | ; WSYNC that many times. 178 | ; 179 | 180 | ldx #$3F 181 | _wsync_loop: 182 | sta WSYNC 183 | dex 184 | bpl _wsync_loop 185 | sta WSYNC 186 | 187 | ; 188 | ; Delay while the raster scans across the screen. The more 189 | ; we delay here, the more to the right the player will be when 190 | ; we draw it. 191 | ; 192 | 193 | nop 194 | nop 195 | nop 196 | nop 197 | nop 198 | nop 199 | nop 200 | nop 201 | nop 202 | nop 203 | nop 204 | nop 205 | nop 206 | nop 207 | nop 208 | 209 | ; 210 | ; OK, *now* display the player. 211 | ; 212 | 213 | sta RESP0 214 | 215 | ; 216 | ; Loop over the rows of the sprite data, drawing each to the screen 217 | ; over four scan lines. 218 | ; 219 | ; TODO understand this better and describe it! 220 | ; 221 | 222 | ldy #$07 223 | _image_loop: 224 | lda image_data, y 225 | sta GP0 226 | 227 | sta WSYNC 228 | sta WSYNC 229 | sta WSYNC 230 | sta WSYNC 231 | dey 232 | bpl _image_loop 233 | 234 | lda #$00 235 | sta GP0 236 | 237 | ; 238 | ; Turn off screen display and clear display registers. 239 | ; 240 | 241 | lda #$02 242 | sta WSYNC 243 | sta VBLANK 244 | lda #$00 245 | sta PF0 246 | sta PF1 247 | sta PF2 248 | sta COLUPF 249 | sta COLUBK 250 | 251 | rts 252 | .scend 253 | 254 | 255 | ; 256 | ; Read the joystick and use it to modify the colour and luminosity 257 | ; of the player. 258 | ; 259 | 260 | .scope 261 | read_joystick: 262 | lda joystick_delay 263 | beq _continue 264 | 265 | dec joystick_delay 266 | rts 267 | 268 | _continue: 269 | lda SWCHA 270 | and #$f0 271 | cmp #$e0 272 | beq _up 273 | cmp #$d0 274 | beq _down 275 | cmp #$b0 276 | beq _left 277 | cmp #$70 278 | beq _right 279 | jmp _tail 280 | 281 | _up: 282 | inc luminosity 283 | jmp _tail 284 | _down: 285 | dec luminosity 286 | jmp _tail 287 | _left: 288 | dec colour 289 | jmp _tail 290 | _right: 291 | inc colour 292 | ;jmp _tail 293 | 294 | _tail: 295 | lda colour 296 | and #$0f 297 | sta colour 298 | 299 | lda luminosity 300 | and #$0f 301 | sta luminosity 302 | 303 | lda colour 304 | clc 305 | rol 306 | rol 307 | rol 308 | rol 309 | ora luminosity 310 | sta COLUP0 311 | 312 | lda #$06 313 | sta joystick_delay 314 | 315 | rts 316 | .scend 317 | 318 | ; 319 | ; Player (sprite) data. 320 | ; 321 | ; Because we loop over these bytes with the Y register counting *down*, 322 | ; this image is stored "upside-down". 323 | ; 324 | 325 | image_data: 326 | .byte %01111110 327 | .byte %10000001 328 | .byte %10011001 329 | .byte %10100101 330 | .byte %10000001 331 | .byte %10100101 332 | .byte %10000001 333 | .byte %01111110 334 | 335 | ; 336 | ; Standard postlude for Atari 2600 cartridge code. 337 | ; Give BRK and boot vectors that point to the start of the code. 338 | ; 339 | 340 | .advance $FFFC 341 | .word start 342 | .word start 343 | -------------------------------------------------------------------------------- /eg/atari2600/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sixtypical --prelude=atari2600 smiley.60p > smiley-60p.bin 4 | if [ "x$COMPARE" != "x" ]; then 5 | ophis smiley.oph -o smiley.bin 6 | dcc6502 -o 0xf000 -m 200 smiley.bin > smiley.bin.disasm.txt 7 | dcc6502 -o 0xf000 -m 200 smiley-60p.bin > smiley-60p.bin.disasm.txt 8 | paste smiley.bin.disasm.txt smiley-60p.bin.disasm.txt | pr -t -e24 9 | #diff -ru smiley.bin.disasm.txt smiley-60p.bin.disasm.txt 10 | fi 11 | -------------------------------------------------------------------------------- /eg/atari2600/smiley.60p: -------------------------------------------------------------------------------- 1 | // smiley.60p - SixtyPical translation of smiley.oph (2018), 2 | // which is itself a stripped-down version of atari-2600-example.oph 3 | 4 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 5 | // For more information, please refer to 6 | // SPDX-License-Identifier: Unlicense 7 | 8 | byte VSYNC @ $00 9 | byte VBLANK @ $01 10 | byte WSYNC @ $02 11 | byte NUSIZ0 @ $04 12 | byte NUSIZ1 @ $05 13 | byte COLUPF @ $08 14 | byte COLUBK @ $09 15 | byte PF0 @ $0D 16 | byte PF1 @ $0E 17 | byte PF2 @ $0F 18 | byte SWCHA @ $280 19 | byte INTIM @ $284 20 | byte TIM64T @ $296 21 | byte CTRLPF @ $0A 22 | byte COLUP0 @ $06 23 | byte COLUP1 @ $07 24 | byte GP0 @ $1B 25 | byte GP1 @ $1C 26 | byte HMOVE @ $2a 27 | byte RESP0 @ $10 28 | byte RESP1 @ $11 29 | 30 | byte colour @ $80 31 | byte luminosity @ $81 32 | byte joystick_delay @ $82 33 | 34 | byte table[8] image_data : 126, 129, 153, 165, 129, 165, 129, 126 35 | // %01111110 36 | // %10000001 37 | // %10011001 38 | // %10100101 39 | // %10000001 40 | // %10100101 41 | // %10000001 42 | // %01111110 43 | 44 | 45 | define vertical_blank routine 46 | outputs VSYNC, WSYNC, TIM64T 47 | trashes a, x, z, n 48 | { 49 | ld x, $00 50 | ld a, $02 51 | st a, WSYNC 52 | st a, WSYNC 53 | st a, WSYNC 54 | st a, VSYNC 55 | st a, WSYNC 56 | st a, WSYNC 57 | ld a, $2C 58 | st a, TIM64T 59 | ld a, $00 60 | st a, WSYNC 61 | st a, VSYNC 62 | } 63 | 64 | define display_frame routine 65 | inputs INTIM, image_data 66 | outputs WSYNC, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK 67 | trashes a, x, y, z, n 68 | { 69 | repeat { 70 | ld a, INTIM 71 | } until z 72 | 73 | //; (After that loop finishes, we know the accumulator must contain 0.) 74 | 75 | st a, WSYNC 76 | st a, HMOVE 77 | st a, VBLANK 78 | 79 | //; 80 | //; Wait for $3f (plus one?) scan lines to pass, by waiting for 81 | //; WSYNC that many times. 82 | //; 83 | 84 | ld x, $3F 85 | repeat { 86 | st a, WSYNC 87 | dec x 88 | } until n 89 | st a, WSYNC 90 | 91 | //; 92 | //; Delay while the raster scans across the screen. The more 93 | //; we delay here, the more to the right the player will be when 94 | //; we draw it. 95 | //; 96 | 97 | nop 98 | nop 99 | nop 100 | nop 101 | nop 102 | nop 103 | nop 104 | nop 105 | nop 106 | nop 107 | nop 108 | nop 109 | nop 110 | nop 111 | nop 112 | 113 | //; 114 | //; OK, *now* display the player. 115 | //; 116 | 117 | st a, RESP0 118 | 119 | //; 120 | //; Loop over the rows of the sprite data, drawing each to the screen 121 | //; over four scan lines. 122 | //; 123 | //; TODO understand this better and describe it! 124 | //; 125 | 126 | ld y, $07 127 | for y down to 0 { 128 | ld a, image_data + y 129 | st a, GP0 130 | 131 | st a, WSYNC 132 | st a, WSYNC 133 | st a, WSYNC 134 | st a, WSYNC 135 | } // FIXME original was "dec y; bpl _image_loop" 136 | 137 | ld a, $00 138 | st a, GP0 139 | 140 | //; 141 | //; Turn off screen display and clear display registers. 142 | //; 143 | 144 | ld a, $02 145 | st a, WSYNC 146 | st a, VBLANK 147 | ld a, $00 148 | st a, PF0 149 | st a, PF1 150 | st a, PF2 151 | st a, COLUPF 152 | st a, COLUBK 153 | } 154 | 155 | define colourize_player routine 156 | inputs colour, luminosity 157 | outputs COLUP0 158 | trashes a, z, c, n 159 | { 160 | ld a, colour 161 | st off, c 162 | shl a 163 | shl a 164 | shl a 165 | shl a 166 | or a, luminosity 167 | st a, COLUP0 168 | } 169 | 170 | define main routine 171 | inputs image_data, INTIM 172 | outputs CTRLPF, colour, luminosity, NUSIZ0, VSYNC, WSYNC, TIM64T, HMOVE, VBLANK, RESP0, GP0, PF0, PF1, PF2, COLUPF, COLUBK, COLUP0 173 | trashes a, x, y, z, c, n 174 | { 175 | ld a, $00 176 | st a, CTRLPF 177 | ld a, $0c 178 | st a, colour 179 | ld a, $0a 180 | st a, luminosity 181 | ld a, $00 182 | st a, NUSIZ0 183 | repeat { 184 | call vertical_blank 185 | call display_frame 186 | call colourize_player 187 | } forever 188 | } 189 | -------------------------------------------------------------------------------- /eg/atari2600/smiley.oph: -------------------------------------------------------------------------------- 1 | ; 2 | ; smiley.oph (2018) 3 | ; stripped-down version of atari-2600-example.oph (2012) 4 | ; 5 | ; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 6 | ; For more information, please refer to 7 | ; SPDX-License-Identifier: Unlicense 8 | ; 9 | ; to build and run in Stella: 10 | ; ophis smiley.oph -o smiley.bin 11 | ; stella smiley.bin 12 | 13 | ; 14 | ; Useful system addresses (TODO: briefly describe each of these.) 15 | ; 16 | 17 | .alias VSYNC $00 18 | .alias VBLANK $01 19 | .alias WSYNC $02 20 | .alias NUSIZ0 $04 21 | .alias NUSIZ1 $05 22 | .alias COLUPF $08 23 | .alias COLUBK $09 24 | .alias PF0 $0D 25 | .alias PF1 $0E 26 | .alias PF2 $0F 27 | .alias SWCHA $280 28 | .alias INTIM $284 29 | .alias TIM64T $296 30 | .alias CTRLPF $0A 31 | .alias COLUP0 $06 32 | .alias COLUP1 $07 33 | .alias GP0 $1B 34 | .alias GP1 $1C 35 | .alias HMOVE $2a 36 | .alias RESP0 $10 37 | .alias RESP1 $11 38 | 39 | ; 40 | ; Cartridge ROM occupies the top 4K of memory ($F000-$FFFF). 41 | ; Thus, typically, the program will occupy all that space too. 42 | ; 43 | ; Zero-page RAM we can use with impunity starts at $80 and goes 44 | ; upward (at least until $99, but probably further.) 45 | ; 46 | 47 | .alias colour $80 48 | .alias luminosity $81 49 | .alias joystick_delay $82 50 | 51 | .org $F000 52 | 53 | ; 54 | ; Standard prelude for Atari 2600 cartridge code. 55 | ; 56 | ; Get various parts of the machine into a known state: 57 | ; 58 | ; - Disable interrupts 59 | ; - Clear the Decimal flag 60 | ; - Initialize the Stack Pointer 61 | ; - Zero all bytes in Zero Page memory 62 | ; 63 | 64 | start: 65 | sei 66 | cld 67 | ldx #$FF 68 | txs 69 | lda #$00 70 | 71 | zero_loop: 72 | sta $00, x 73 | dex 74 | bne zero_loop 75 | 76 | ; and fall through to... 77 | 78 | ; 79 | ; Initialization. 80 | ; 81 | ; - Clear the Playfield Control register. 82 | ; - Set the player (sprite) colour to light green (write to COLUP0.) 83 | ; - Set the player (sprite) size/repetion to normal (write to NUSIZ0.) 84 | ; 85 | 86 | lda #$00 87 | sta CTRLPF 88 | lda #$0c 89 | sta colour 90 | lda #$0a 91 | sta luminosity 92 | lda #$00 93 | sta NUSIZ0 94 | 95 | ; and fall through to... 96 | 97 | ; 98 | ; Main loop. 99 | ; 100 | ; A typical main loop consists of: 101 | ; - Waiting for the frame to start (vertical blank period) 102 | ; - Displaying stuff on the screen (the _display kernel_) 103 | ; - Doing any processing you like (reading joysticks, updating program state, 104 | ; etc.), as long as you get it all done before the next frame starts! 105 | ; 106 | 107 | main: 108 | jsr vertical_blank 109 | jsr display_frame 110 | jsr colourize_player 111 | jmp main 112 | rts ; NOTE just to pad out to match the SixtyPical version 113 | 114 | ; 115 | ; Vertical blank routine. 116 | ; 117 | ; In brief: wait until it is time for the next frame of video. 118 | ; TODO: describe this in more detail. 119 | ; 120 | 121 | vertical_blank: 122 | ldx #$00 123 | lda #$02 124 | sta WSYNC 125 | sta WSYNC 126 | sta WSYNC 127 | sta VSYNC 128 | sta WSYNC 129 | sta WSYNC 130 | lda #$2C 131 | sta TIM64T 132 | lda #$00 133 | sta WSYNC 134 | sta VSYNC 135 | rts 136 | 137 | ; 138 | ; Display kernal. 139 | ; 140 | ; First, wait until it's time to display the frame. 141 | ; 142 | 143 | .scope 144 | display_frame: 145 | lda INTIM 146 | bne display_frame 147 | 148 | ; 149 | ; (After that loop finishes, we know the accumulator must contain 0.) 150 | ; Wait for the next scanline, zero HMOVE (for some reason; TODO discover 151 | ; this), then turn on the screen. 152 | ; 153 | 154 | sta WSYNC 155 | sta HMOVE 156 | sta VBLANK 157 | 158 | ; 159 | ; Actual work in the display kernal is done here. 160 | ; 161 | ; This is a pathological approach to writing a display kernal. 162 | ; This wouldn't be how you'd do things in a game. So be it. 163 | ; One day I may improve it. For now, be happy that it displays 164 | ; anything at all! 165 | ; 166 | 167 | ; 168 | ; Wait for $3f (plus one?) scan lines to pass, by waiting for 169 | ; WSYNC that many times. 170 | ; 171 | 172 | ldx #$3F 173 | _wsync_loop: 174 | sta WSYNC 175 | dex 176 | bpl _wsync_loop 177 | sta WSYNC 178 | 179 | ; 180 | ; Delay while the raster scans across the screen. The more 181 | ; we delay here, the more to the right the player will be when 182 | ; we draw it. 183 | ; 184 | 185 | nop 186 | nop 187 | nop 188 | nop 189 | nop 190 | nop 191 | nop 192 | nop 193 | nop 194 | nop 195 | nop 196 | nop 197 | nop 198 | nop 199 | nop 200 | 201 | ; 202 | ; OK, *now* display the player. 203 | ; 204 | 205 | sta RESP0 206 | 207 | ; 208 | ; Loop over the rows of the sprite data, drawing each to the screen 209 | ; over four scan lines. 210 | ; 211 | ; TODO understand this better and describe it! 212 | ; 213 | 214 | ldy #$07 215 | _image_loop: 216 | lda image_data, y 217 | sta GP0 218 | 219 | sta WSYNC 220 | sta WSYNC 221 | sta WSYNC 222 | sta WSYNC 223 | dey 224 | bpl _image_loop 225 | 226 | lda #$00 227 | sta GP0 228 | 229 | ; 230 | ; Turn off screen display and clear display registers. 231 | ; 232 | 233 | lda #$02 234 | sta WSYNC 235 | sta VBLANK 236 | lda #$00 237 | sta PF0 238 | sta PF1 239 | sta PF2 240 | sta COLUPF 241 | sta COLUBK 242 | 243 | rts 244 | .scend 245 | 246 | ; 247 | ; Modify the colour and luminosity of the player. 248 | ; 249 | 250 | .scope 251 | colourize_player: 252 | lda colour 253 | clc 254 | rol 255 | rol 256 | rol 257 | rol 258 | ora luminosity 259 | sta COLUP0 260 | rts 261 | .scend 262 | 263 | ; 264 | ; Player (sprite) data. 265 | ; 266 | ; Because we loop over these bytes with the Y register counting *down*, 267 | ; this image is stored "upside-down". 268 | ; 269 | 270 | image_data: 271 | .byte %01111110 272 | .byte %10000001 273 | .byte %10011001 274 | .byte %10100101 275 | .byte %10000001 276 | .byte %10100101 277 | .byte %10000001 278 | .byte %01111110 279 | 280 | ; 281 | ; Standard postlude for Atari 2600 cartridge code. 282 | ; Give BRK and boot vectors that point to the start of the code. 283 | ; 284 | 285 | .advance $FFFC 286 | .word start 287 | .word start 288 | -------------------------------------------------------------------------------- /eg/c64/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | This directory contains SixtyPical example programs 8 | specifically for the Commodore 64. 9 | 10 | There are subdirectories for more elaborate demos: 11 | 12 | * [demo-game](demo-game/): a little game-like program written as a 13 | "can we write something you'd see in practice?" test case for SixtyPical. 14 | 15 | * [ribos](ribos/): a well-commented example of a C64 raster interrupt 16 | routine. Originally written with the P65 assembler (which has since 17 | been reborn as [Ophis][]). 18 | 19 | The second version of Ribos has been translated to SixtyPical. 20 | 21 | * [petulant](petulant/): "The PETulant Cursor", a tiny (44 bytes) 22 | "display hack". Originally written in the late 80's. Rewritten with 23 | the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a 24 | hint as to its nature). 25 | 26 | Translated to SixtyPical (in 2018), after adding some optimizations 27 | to the SixtyPical compiler, the resulting executable is still 44 bytes! 28 | 29 | [Ophis]: http://michaelcmartin.github.io/Ophis/ 30 | -------------------------------------------------------------------------------- /eg/c64/demo-game/demo-game.60p: -------------------------------------------------------------------------------- 1 | // **************************** 2 | // * Demo Game for SixtyPical * 3 | // **************************** 4 | 5 | // Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 6 | // This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 7 | // SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 8 | 9 | include "joystick.60p" 10 | 11 | // ---------------------------------------------------------------- 12 | // Type Definitions 13 | // ---------------------------------------------------------------- 14 | 15 | // 16 | // Type of routines (and vectors to those routines) which are called on each frame 17 | // to implement a certain state of the game (title screen, in play, game over, etc.) 18 | // 19 | // This type is also used as the type for the interrupt vector, even though 20 | // the interrupt routine saves and restores everything before being called and 21 | // thus clearly does not actually trash all the registers. It is declared this 22 | // way so that the game state routines, which do trash these registers, can be 23 | // assigned to it. 24 | // 25 | // This type is also used as the type for the location the old interrupt vector 26 | // is backed up to, because all the game state routines `goto` the old handler 27 | // and the end of their own routines, so the type needs to be compatible. 28 | // (In a good sense, it is a continuation.) 29 | // 30 | 31 | typedef routine 32 | inputs joy2, press_fire_msg, dispatch_game_state, 33 | actor_pos, actor_delta, actor_logic, 34 | player_died, 35 | screen, colormap 36 | outputs dispatch_game_state, 37 | actor_pos, actor_delta, actor_logic, 38 | player_died, 39 | screen, colormap 40 | trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic 41 | game_state_routine 42 | 43 | // 44 | // Routines that are called to get the new state of each actor (player, enemy, etc.) 45 | // 46 | // Routines that conform to this type also follow this convention: 47 | // 48 | // Set player_died to 1 if the player perished. Unchanged otherwise. 49 | // 50 | 51 | typedef routine 52 | inputs pos, delta, joy2, screen, player_died 53 | outputs pos, delta, new_pos, screen, player_died 54 | trashes a, x, y, z, n, v, c, ptr 55 | logic_routine 56 | 57 | // ---------------------------------------------------------------- 58 | // System Locations 59 | // ---------------------------------------------------------------- 60 | 61 | byte vic_border @ 53280 62 | byte vic_bg @ 53281 63 | byte table[2048] screen @ 1024 64 | byte table[2048] colormap @ 55296 65 | 66 | // ---------------------------------------------------------------- 67 | // Global Variables 68 | // ---------------------------------------------------------------- 69 | 70 | pointer ptr @ 254 71 | 72 | word table[256] actor_pos 73 | word pos 74 | word new_pos 75 | 76 | word table[256] actor_delta 77 | 78 | byte player_died 79 | 80 | vector logic_routine table[256] actor_logic 81 | vector logic_routine dispatch_logic 82 | 83 | byte table[18] press_fire_msg: "PRESS`FIRE`TO`PLAY" 84 | 85 | // 86 | // Points to the routine that implements the current game state. 87 | // 88 | 89 | vector game_state_routine 90 | dispatch_game_state 91 | 92 | // 93 | // Interrupt vector. Has same type as game states (see above.) 94 | // 95 | 96 | vector game_state_routine 97 | cinv @ 788 98 | 99 | // 100 | // Location to which the old interrupt vector is saved before replacement. 101 | // 102 | 103 | vector game_state_routine 104 | save_cinv 105 | 106 | // ---------------------------------------------------------------- 107 | // Utility Routines 108 | // ---------------------------------------------------------------- 109 | 110 | define clear_screen routine 111 | outputs screen, colormap 112 | trashes a, y, c, n, z 113 | { 114 | ld y, 0 115 | repeat { 116 | ld a, 1 117 | st a, colormap + y 118 | st a, colormap + 250 + y 119 | st a, colormap + 500 + y 120 | st a, colormap + 750 + y 121 | 122 | ld a, 32 123 | st a, screen + y 124 | st a, screen + 250 + y 125 | st a, screen + 500 + y 126 | st a, screen + 750 + y 127 | 128 | inc y 129 | cmp y, 250 130 | } until z 131 | } 132 | 133 | define calculate_new_position routine 134 | inputs pos, delta 135 | outputs new_pos 136 | trashes a, c, n, z, v 137 | { 138 | copy pos, new_pos 139 | st off, c 140 | add new_pos, delta 141 | } 142 | 143 | define check_new_position_in_bounds routine 144 | inputs new_pos 145 | outputs c 146 | trashes a, z, n, v 147 | static word compare_target : 0 148 | { 149 | copy 1000, compare_target 150 | st on, c 151 | sub compare_target, new_pos 152 | 153 | if not c { 154 | copy word 0, compare_target 155 | st on, c 156 | sub compare_target, new_pos 157 | if not c { 158 | st off, c 159 | } else { 160 | st on, c 161 | } 162 | } else { 163 | st on, c 164 | } 165 | } 166 | 167 | define init_game routine 168 | inputs actor_pos, actor_delta, actor_logic 169 | outputs actor_pos, actor_delta, actor_logic, player_died 170 | trashes pos, a, y, z, n, c, v 171 | { 172 | ld y, 0 173 | copy word 0, pos 174 | repeat { 175 | copy pos, actor_pos + y 176 | copy word 40, actor_delta + y 177 | copy enemy_logic, actor_logic + y 178 | 179 | st off, c 180 | add pos, word 7 181 | 182 | inc y 183 | cmp y, 16 184 | } until z 185 | 186 | ld y, 0 187 | copy word 40, actor_pos + y 188 | copy word 0, actor_delta + y 189 | copy player_logic, actor_logic + y 190 | 191 | st y, player_died 192 | } 193 | 194 | // ---------------------------------------------------------------- 195 | // Actor Logics 196 | // ---------------------------------------------------------------- 197 | 198 | define player_logic logic_routine 199 | { 200 | call read_stick 201 | 202 | call calculate_new_position 203 | call check_new_position_in_bounds 204 | 205 | if c { 206 | point ptr into screen { 207 | reset ptr 0 208 | st off, c 209 | add ptr, new_pos 210 | ld y, 0 211 | 212 | // check collision. 213 | ld a, [ptr] + y 214 | 215 | // if "collision" is with your own self, treat it as if it's blank space! 216 | cmp a, 81 217 | if z { 218 | ld a, 32 219 | } 220 | cmp a, 32 221 | if z { 222 | reset ptr 0 223 | st off, c 224 | add ptr, pos 225 | copy 32, [ptr] + y 226 | 227 | copy new_pos, pos 228 | 229 | reset ptr 0 230 | st off, c 231 | add ptr, pos 232 | copy 81, [ptr] + y 233 | } 234 | } 235 | } else { 236 | ld a, 1 237 | st a, player_died 238 | } 239 | } 240 | 241 | define enemy_logic logic_routine 242 | static word compare_target : 0 243 | { 244 | call calculate_new_position 245 | call check_new_position_in_bounds 246 | 247 | if c { 248 | point ptr into screen { 249 | reset ptr 0 250 | st off, c 251 | add ptr, new_pos 252 | ld y, 0 253 | 254 | // check collision. 255 | ld a, [ptr] + y 256 | 257 | // if "collision" is with your own self, treat it as if it's blank space! 258 | cmp a, 82 259 | if z { 260 | ld a, 32 261 | } 262 | cmp a, 32 263 | if z { 264 | reset ptr 0 265 | st off, c 266 | add ptr, pos 267 | copy 32, [ptr] + y 268 | 269 | copy new_pos, pos 270 | 271 | reset ptr 0 272 | st off, c 273 | add ptr, pos 274 | copy 82, [ptr] + y 275 | } 276 | } 277 | } else { 278 | copy delta, compare_target 279 | st on, c 280 | sub compare_target, word 40 281 | if not z { 282 | copy word 40, delta 283 | } else { 284 | copy $ffd8, delta 285 | } 286 | } 287 | } 288 | 289 | // ---------------------------------------------------------------- 290 | // Game States 291 | // ---------------------------------------------------------------- 292 | 293 | define game_state_title_screen game_state_routine 294 | { 295 | ld y, 0 296 | for y up to 17 { 297 | ld a, press_fire_msg + y 298 | 299 | st on, c 300 | sub a, 64 // yuck. oh well 301 | 302 | st a, screen + y 303 | } 304 | 305 | st off, c 306 | call check_button 307 | 308 | if c { 309 | call clear_screen 310 | call init_game 311 | copy game_state_play, dispatch_game_state 312 | } 313 | 314 | goto save_cinv 315 | } 316 | 317 | define game_state_play game_state_routine 318 | { 319 | ld x, 0 320 | st x, player_died 321 | for x up to 15 { 322 | copy actor_pos + x, pos 323 | copy actor_delta + x, delta 324 | 325 | // 326 | // Save our loop counter on the stack temporarily. This means that routines 327 | // like `dispatch_logic` and `clear_screen` are allowed to do whatever they 328 | // want with the `x` register; we will restore it at the end of this block. 329 | // 330 | save x { 331 | copy actor_logic + x, dispatch_logic 332 | call dispatch_logic 333 | } 334 | 335 | copy pos, actor_pos + x 336 | copy delta, actor_delta + x 337 | } 338 | 339 | ld a, player_died 340 | if not z { 341 | // Player died! Want no dead! 342 | call clear_screen 343 | copy game_state_game_over, dispatch_game_state 344 | } 345 | 346 | goto save_cinv 347 | } 348 | 349 | define game_state_game_over game_state_routine 350 | { 351 | st off, c 352 | call check_button 353 | 354 | if c { 355 | call clear_screen 356 | call init_game 357 | copy game_state_title_screen, dispatch_game_state 358 | } 359 | 360 | goto save_cinv 361 | } 362 | 363 | // ************************* 364 | // * Main Game Loop Driver * 365 | // ************************* 366 | 367 | define our_cinv preserved game_state_routine 368 | { 369 | goto dispatch_game_state 370 | } 371 | 372 | define main routine 373 | inputs cinv 374 | outputs cinv, save_cinv, pos, dispatch_game_state, screen, colormap 375 | trashes a, y, n, c, z, vic_border, vic_bg 376 | { 377 | ld a, 5 378 | st a, vic_border 379 | ld a, 0 380 | st a, vic_bg 381 | ld y, 0 382 | 383 | call clear_screen 384 | 385 | copy game_state_title_screen, dispatch_game_state 386 | 387 | copy word 0, pos 388 | with interrupts off { 389 | copy cinv, save_cinv 390 | copy our_cinv, cinv 391 | } 392 | 393 | repeat { } forever 394 | } 395 | -------------------------------------------------------------------------------- /eg/c64/demo-game/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script builds and runs the demo game. You need 4 | # the VICE emulatore installed, in particular VICE's x64. 5 | 6 | # You might want a `vicerc` file like the following: 7 | # [C64] 8 | # VICIIDoubleScan=0 9 | # VICIIDoubleSize=0 10 | # KeySet1NorthWest=0 11 | # KeySet1North=273 12 | # KeySet1NorthEast=0 13 | # KeySet1East=275 14 | # KeySet1SouthEast=0 15 | # KeySet1South=274 16 | # KeySet1SouthWest=0 17 | # KeySet1West=276 18 | # KeySet1Fire=306 19 | # KeySetEnable=1 20 | # JoyDevice1=0 21 | # JoyDevice2=2 22 | 23 | ../../../bin/sixtypical --run-on x64 -I ../../../include/c64/ demo-game.60p 24 | -------------------------------------------------------------------------------- /eg/c64/hearts.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | // Displays 256 hearts at the top of the Commodore 64's screen. 6 | 7 | // Define where the screen starts in memory: 8 | byte table[256] screen @ 1024 9 | 10 | define main routine 11 | // These are the values that will be written to by this routine: 12 | trashes a, x, z, n, screen 13 | { 14 | ld x, 0 15 | ld a, 83 // 83 = screen code for heart 16 | repeat { 17 | st a, screen + x 18 | inc x 19 | } until z // this flag will be set when x wraps around from 255 to 0 20 | } 21 | -------------------------------------------------------------------------------- /eg/c64/intr1.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | byte vic_border @ 53280 6 | 7 | // 8 | // The constraints on these 2 vectors are kind-of sort-of big fibs. 9 | // They're only written this way so they can be compatible with our 10 | // routine. In fact, CINV is an interrupt routine where it doesn't 11 | // really matter what you trash anyway, because all registers were 12 | /// saved by the caller (the KERNAL) and will be restored by the end 13 | // of the code of the saved origin cinv routine that we goto. 14 | // 15 | // I wonder if this could be arranged somehow to be less fibby, in 16 | // a future version of SixtyPical. 17 | // 18 | 19 | vector routine 20 | inputs vic_border 21 | outputs vic_border 22 | trashes z, n 23 | cinv @ 788 24 | 25 | vector routine 26 | inputs vic_border 27 | outputs vic_border 28 | trashes z, n 29 | save_cinv 30 | 31 | define our_cinv routine 32 | inputs vic_border 33 | outputs vic_border 34 | trashes z, n 35 | { 36 | inc vic_border 37 | goto save_cinv 38 | } 39 | 40 | define main routine 41 | inputs cinv 42 | outputs cinv, save_cinv 43 | trashes a, n, z 44 | { 45 | with interrupts off { 46 | copy cinv, save_cinv 47 | copy our_cinv, cinv 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /eg/c64/joystick-demo.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | include "joystick.60p" 6 | 7 | word screen @ 1024 8 | 9 | define main routine 10 | inputs joy2 11 | outputs delta 12 | trashes a, x, z, n, screen 13 | { 14 | repeat { 15 | call read_stick 16 | copy delta, screen 17 | ld a, 1 18 | } until z 19 | } 20 | -------------------------------------------------------------------------------- /eg/c64/petulant/README.md: -------------------------------------------------------------------------------- 1 | The PETulant Cursor 2 | =================== 3 | 4 | 9 | 10 | This is a tiny (44 bytes long) machine-language demo for the Commodore 64, 11 | somewhat in the style of later "display hacks" for the Amiga -- surprising 12 | and silly ways to play with the user interface. 13 | 14 | So as not to not spoil the fun, try running it before reading the source. 15 | 16 | To run it, make the file PETULANT.PRG accessible to your favourite Commodore 17 | 64 (or Commodore 64 emulator) in your favourite way, then 18 | 19 | LOAD "PETULANT.PRG",8,1 20 | SYS 679 21 | 22 | For further fun, try changing the text colour (hold the C= or CTRL key while 23 | pressing a number) or the background colour (POKE 53281 with a number from 24 | 0 to 15) while it is running. 25 | 26 | I must have originally wrote this sometime in the late 80's. I disassembled 27 | and rewrote it (to make it play more nicely with existing interrupt handlers) 28 | in 2008. 29 | 30 | Who knows -- this could actually be useful, in a ridiculously minor way: it 31 | makes it much more obvious when control has returned to BASIC immediate mode 32 | (e.g., when a program has finished loading.) 33 | 34 | Enjoy! 35 | 36 | -Chris Pressey 37 | April 1, 2008 38 | Chicago, IL 39 | -------------------------------------------------------------------------------- /eg/c64/petulant/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sixtypical --output-format=prg --origin=0x02a7 petulant.60p > petulant-60p.prg 4 | if [ "x$COMPARE" != "x" ]; then 5 | dcc6502 petulant.prg > petulant.prg.disasm.txt 6 | dcc6502 petulant-60p.prg > petulant-60p.prg.disasm.txt 7 | paste petulant.prg.disasm.txt petulant-60p.prg.disasm.txt | pr -t -e24 8 | rm -f *.disasm.txt 9 | fi 10 | -------------------------------------------------------------------------------- /eg/c64/petulant/petulant-60p.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catseye/SixtyPical/df610e639c4a16855258c51e857b0376d42566a9/eg/c64/petulant/petulant-60p.prg -------------------------------------------------------------------------------- /eg/c64/petulant/petulant.60p: -------------------------------------------------------------------------------- 1 | // petulant.60p - The PETulant Cursor, a "display hack" for the Commodore 64 2 | // Originally written by Chris Pressey sometime in the late 1980's 3 | // Rewritten in P65 assembly and released March 2008 4 | // Rewritten in SixtyPical in March 2018 5 | 6 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 7 | // For more information, please refer to 8 | // SPDX-License-Identifier: Unlicense 9 | 10 | // ----- Types ----- 11 | 12 | typedef routine 13 | inputs border, blnon, color, background 14 | outputs border 15 | trashes a, z, n 16 | irq_handler 17 | 18 | // ----- Addresses ----- 19 | 20 | vector irq_handler cinv @ $314 // hw irq interrupt, 60x per second 21 | 22 | byte blnon @ $cf // was last cursor blink on or off? 23 | byte color @ $286 // current foreground colour for text 24 | 25 | byte border @ $d020 // colour of border of the screen 26 | byte background @ $d021 // colour of background of the screen 27 | 28 | // ----- Variables ----- 29 | 30 | vector irq_handler save_cinv 31 | 32 | // ----- Interrupt Handler ----- 33 | 34 | define our_cinv irq_handler 35 | { 36 | ld a, blnon 37 | if not z { 38 | ld a, color 39 | } else { 40 | ld a, background 41 | } 42 | st a, border 43 | goto save_cinv 44 | } 45 | 46 | // ----- Main Program ----- 47 | 48 | define main routine 49 | inputs cinv 50 | outputs cinv, save_cinv 51 | trashes a, n, z 52 | { 53 | with interrupts off { 54 | copy cinv, save_cinv 55 | copy our_cinv, cinv 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /eg/c64/petulant/petulant.p65: -------------------------------------------------------------------------------- 1 | ; petulant.p65 - The PETulant Cursor, a "display hack" for the Commodore 64 2 | ; Originally written by Chris Pressey sometime in the late 1980's 3 | ; Rewritten in P65 assembly and released March 2008 4 | 5 | ; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 6 | ; For more information, please refer to 7 | ; SPDX-License-Identifier: Unlicense 8 | 9 | ; ----- BEGIN petulant.p65 ----- 10 | 11 | ; PRG file header 12 | 13 | .org 0 14 | .word $02a7 15 | .org $02a7 16 | 17 | ; ----- Constants ----- 18 | 19 | .alias cinv $0314 ; hw irq interrupt, 60x per second 20 | .alias blnon $cf ; was last cursor blink on or off? 21 | .alias color $286 ; current foreground colour for text 22 | 23 | .alias vic $d000 ; base address of VIC-II chip 24 | .alias border vic+$20 ; colour of border of the screen 25 | .alias background vic+$21 ; colour of background of the screen 26 | 27 | ; ----- Start of Program ----- 28 | 29 | start: sei ; disable interrupts 30 | lda cinv ; save low byte of existing handler 31 | sta savecinv 32 | lda cinv+1 ; save high byte 33 | sta savecinv+1 34 | 35 | lda #newcinv 38 | sta cinv+1 39 | 40 | cli ; re-enable interrupts 41 | rts 42 | 43 | newcinv: lda blnon ; is the cursor on? 44 | beq cursor_off 45 | cursor_on: lda color ; yes, get its colour 46 | jmp egress 47 | cursor_off: lda background ; no, get the background colour 48 | egress: sta border ; colour the border 49 | jmp (savecinv) ; continue pre-existing interrupt handler 50 | 51 | ; ----- Uninitialized Data ----- 52 | 53 | .space savecinv 2 54 | 55 | ; ----- END of petulant.p65 ----- 56 | -------------------------------------------------------------------------------- /eg/c64/petulant/petulant.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catseye/SixtyPical/df610e639c4a16855258c51e857b0376d42566a9/eg/c64/petulant/petulant.prg -------------------------------------------------------------------------------- /eg/c64/ribos/README.md: -------------------------------------------------------------------------------- 1 | Ribos 2 | ===== 3 | 4 | 9 | 10 | This little demo is intended to be a well-commented example of how to 11 | program a raster interrupt in 6502 assembly language on a Commodore 64. 12 | 13 | This (r)aster (i)nterrupt changes the colour of a region of the 14 | (bo)rder of the C64 (s)creen; thus, RIBOS. Also, it's the name of a 15 | planet from Dr. Who, if that means anything. 16 | 17 | 18 | How to Run the Demo (using the VICE C64 emulator, x64) 19 | ------------------------------------------------------ 20 | 21 | 0. Obtain VICE from http://www.viceteam.org/, install it, 22 | and run x64 23 | 24 | 1. Mount this project's directory as drive 8: 25 | 26 | Make sure 27 | `Peripheral settings > Device #8 > Enable IEC Device` 28 | is checked, then select 29 | `Peripheral settings > Device #8 > File system directory...` 30 | and enter the path to the project directory. 31 | 32 | 2. LOAD "RIBOS.PRG",8,1 33 | 34 | 3. SYS 49152 35 | 36 | 4. You should see the colour of the middle of the border change 37 | while you get a READY. prompt and can continue working. 38 | 39 | 40 | How to Assemble the Program (using the p65 assembler) 41 | ----------------------------------------------------- 42 | 43 | 0. Obtain p65 from http://hkn.berkeley.edu/~mcmartin/P65/ 44 | (I used p65-Perl version 1.1) and install it somewhere 45 | on your path. If your Perl interpreter isn't located at 46 | /usr/bin/perl, change the first line of p65 appropriately. 47 | 48 | 1. p65 -v -t -b ribos.p65 ribos.prg 49 | 50 | The switches aren't necessary, but they make it feel like 51 | p65 is doing something difficult and important. It also 52 | isn't necessary to add the '.prg' extension on the end of 53 | the binary object's filename, since it will appear as a 54 | PRG file to VICE anyway, but it's nice as a reminder when 55 | you're working in a modern operating system. 56 | 57 | 2. Follow the steps under 'How to Run this Demo' to see that 58 | it worked. 59 | 60 | 61 | How it Works 62 | ------------ 63 | 64 | Read the source! I've tried to make it very well-commented, 65 | including what happens when you leave out some steps. 66 | 67 | I wrote this demo because it was a long time since I had done any C64 68 | programming, and, having just obtained a copy of the 'Commodore 64 69 | Programmer's Reference Guide,' I wanted to code something challenging, 70 | yet not too involved. I remembered raster interrupts as one of those 71 | quintessential C64 low-level graphics tricks, so I decided to try my 72 | hand at that. Looking around on the Internet, I found this page: 73 | 74 | http://everything2.com/index.pl?node_id=79254 75 | 76 | Although it's a fairly detailed description, it took me a couple of 77 | frustrating hours to implement it successfully - both the everything2 78 | article and the Reference Guide were pretty muddy on a couple of 79 | points. What I learned in the process is written into the comments. 80 | 81 | Happy raster-interrupting! 82 | 83 | -Chris Pressey 84 | April 10, 2007 85 | Vancouver, BC 86 | -------------------------------------------------------------------------------- /eg/c64/ribos/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sixtypical --output-format=prg --origin=0xc000 ribos2.60p > ribos2-60p.prg 4 | if [ "x$COMPARE" != "x" ]; then 5 | dcc6502 ribos2.prg > ribos2.prg.disasm.txt 6 | dcc6502 ribos2-60p.prg > ribos2-60p.prg.disasm.txt 7 | paste ribos2.prg.disasm.txt ribos2-60p.prg.disasm.txt | pr -t -e24 8 | rm -f *.disasm.txt 9 | fi 10 | -------------------------------------------------------------------------------- /eg/c64/ribos/ribos.p65: -------------------------------------------------------------------------------- 1 | ; ribos.p65 - p65 assembly source for RIBOS: 2 | ; Demonstration of the VIC-II raster interrupt on the Commodore 64: 3 | ; Alter the border colour in the middle part of the screen only. 4 | ; Original (hardware IRQ vector) version. 5 | 6 | ; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 7 | ; For more information, please refer to 8 | ; SPDX-License-Identifier: Unlicense 9 | 10 | ; ----- BEGIN ribos.p65 ----- 11 | 12 | ; This source file is intented to be assembled to produce a PRG file 13 | ; which can be loaded into the C64's memory from a peripheral device. 14 | ; All C64 PRG files start with a 16-bit word which represents the 15 | ; location in memory to which they will be loaded. We can provide this 16 | ; using p65 directives as follows: 17 | 18 | .org 0 19 | .word $C000 20 | 21 | ; Now the actual assembly starts (at memory location 49152.) 22 | 23 | .org $C000 24 | 25 | ; ----- Constants ----- 26 | 27 | ; We first define some symbolic constants to add some clarity to our 28 | ; references to hardware registers and other locations in memory. 29 | ; Descriptions of the registers follow those given in the 'Commodore 64 30 | ; Programmer's Reference Guide'. 31 | 32 | ; The CIA #1 chip. 33 | 34 | .alias cia1 $dc00 ; pp. 328-331 35 | .alias intr_ctrl cia1+$d ; "CIA Interrupt Control Register 36 | ; (Read IRQs/Write Mask)" 37 | 38 | ; The VIC-II chip. 39 | 40 | .alias vic $d000 ; Appendix G: 41 | .alias vic_ctrl vic+$11 ; "Y SCROLL MODE" 42 | .alias vic_raster vic+$12 ; "RASTER" 43 | .alias vic_intr vic+$19 ; "Interrupt Request's" (sic) 44 | .alias vic_intr_enable vic+$1a ; "Interrupt Request MASKS" 45 | .alias border_color vic+$20 ; "BORDER COLOR" 46 | 47 | ; The address at which the IRQ vector is stored. 48 | 49 | .alias irq_vec $fffe ; p. 411 50 | 51 | ; The zero-page address of the 6510's I/O register. 52 | 53 | .alias io_ctrl $01 ; p. 310, "6510 On-Chip 8-Bit 54 | ; Input/Output Register" 55 | 56 | ; KERNAL and BASIC ROMs, p. 320 57 | 58 | .alias kernal $e000 59 | .alias basic $a000 60 | 61 | ; Zero-page addresses that the memory-copy routine uses for 62 | ; scratch space: "FREKZP", p. 316 63 | 64 | .alias zp $fb 65 | .alias stop_at $fd 66 | 67 | ; ----- Main Routine ----- 68 | 69 | ; This routine is intended to be called by the user (by, e.g., SYS 49152). 70 | ; It installs the raster interrupt handler and returns to the caller. 71 | 72 | ; Key to installing the interrupt handler is altering the IRQ service 73 | ; vector. However, under normal circumstances, the address at which 74 | ; this vector is stored ($ffffe) maps to the C64's KERNAL ROM, which 75 | ; cannot be changed. So, in order to alter the vector, we must enable 76 | ; the RAM that underlies the ROM (i.e. the RAM that maps to the same 77 | ; address space as the KERNAL ROM.) If we were writing a bare-metal 78 | ; game, and didn't need any KERNAL routines or support, we could just 79 | ; switch it off. But for this demo, we'd like the raster effect to 80 | ; occur in the background as we use BASIC and whatnot, so we need to 81 | ; continue to have access to the KERNAL ROM. So what we do is copy the 82 | ; KERNAL ROM to the underlying RAM, then switch the RAM for the ROM. 83 | 84 | jsr copy_rom_to_ram 85 | 86 | ; Interrupts can occur at any time. If one were to occur while we were 87 | ; changing the interrupt vector - for example, after we have stored the 88 | ; low byte of the address but before we have stored the high byte - 89 | ; unpredictable behaviour would result. To be safe, we disable interrupts 90 | ; with the 'sei' instruction before changing anything. 91 | 92 | sei 93 | 94 | ; We obtain the address of the current IRQ service routine and save it 95 | ; in the variable 'saved_irq_vec'. 96 | 97 | lda irq_vec ; save low byte 98 | sta saved_irq_vec 99 | lda irq_vec+1 ; save high byte 100 | sta saved_irq_vec+1 101 | 102 | ; We then store the address of our IRQ service routine in its place. 103 | 104 | lda #our_service_routine 107 | sta irq_vec+1 108 | 109 | ; Now we must specify the raster line at which the interrupt gets called. 110 | 111 | lda scanline 112 | sta vic_raster 113 | 114 | ; Note that the position of the raster line is given by a 9-bit value, 115 | ; and we can't just assume that, because the raster line we want is less 116 | ; than 256, that the high bit will automatically be set to zero, because 117 | ; it won't. We have to explicitly set it high or low. It is found as 118 | ; the most significant bit of a VIC-II control register which has many 119 | ; different functions, so we must be careful to preserve all other bits. 120 | 121 | lda vic_ctrl 122 | and #%01111111 123 | sta vic_ctrl 124 | 125 | ; Then we enable the raster interrupt on the VIC-II chip. 126 | 127 | lda #$01 128 | sta vic_intr_enable 129 | 130 | ; The article at everything2 suggests that we read the interrupt control 131 | ; port of the CIA #1 chip, presumably to acknowledge any pending IRQ and 132 | ; avoid the problem of having some sort of lockup due to a spurious IRQ. 133 | ; I've tested leaving this out, and the interrupt handler still seems get 134 | ; installed alright. But, I haven't tested it very demandingly, and it's 135 | ; likely to open up a race condition that I just haven't encountered (much 136 | ; like if we were to forget to execute the 'sei' instruction, above.) 137 | ; So, to play it safe, we read the port here. 138 | 139 | lda intr_ctrl 140 | 141 | ; We re-enable interrupts to resume normal operation - normal, that is, 142 | ; except that our raster interrupt service routine will be called the next 143 | ; time the raster reaches the line stored in the 'vic_raster' register of 144 | ; the VIC-II chip. 145 | 146 | cli 147 | 148 | ; Finally, we return to the caller. 149 | 150 | rts 151 | 152 | ; ----- Raster Interrupt Service Routine ------ 153 | 154 | our_service_routine: 155 | 156 | ; This is an interrupt service routine (a.k.a. interrupt handler,) and as 157 | ; such, it can be called from anywhere. Since the code that was interrupted 158 | ; likely cares deeply about the values in its registers, we must be careful 159 | ; to save any that we change, and restore them before switching back to it. 160 | ; In this case, we only affect the processor flags and the accumulator, so 161 | ; we push them onto the stack. 162 | 163 | php 164 | pha 165 | 166 | ; The interrupt service routine on the Commodore 64 is very general-purpose, 167 | ; and may be invoked by any number of different kinds of interrupts. We, 168 | ; however, only care about a certain kind - the VIC-II's raster interrupt. 169 | ; We check to see if the current interrupt was caused by the raster by 170 | ; looking at the low bit of the VIC-II interrupt register. Note that we 171 | ; immediately store back the value found there before testing it. This is 172 | ; to acknowledge to the VIC-II chip that we got the interrupt. If we don't 173 | ; do this, it won't send us another interrupt next time. 174 | 175 | lda vic_intr 176 | sta vic_intr 177 | and #$01 178 | cmp #$01 179 | beq we_handle_it 180 | 181 | ; If the interrupt was not caused by the raster, we restore the values 182 | ; of the registers from the stack, and continue execution as normal with 183 | ; the existing interrupt service routine. 184 | 185 | pla 186 | plp 187 | jmp (saved_irq_vec) 188 | 189 | we_handle_it: 190 | 191 | ; If we got here, the interrupt _was_ caused by the raster. So, we get 192 | ; to do our thing. To keep things simple, we just invert the border colour. 193 | 194 | lda border_color 195 | eor #$ff 196 | sta border_color 197 | 198 | ; Now, we make the interrupt trigger on a different scan line so that we'll 199 | ; invert the colour back to normal lower down on the screen. 200 | 201 | lda scanline 202 | eor #$ff 203 | sta scanline 204 | sta vic_raster 205 | 206 | ; Restore the registers that we saved at the beginning of this routine. 207 | 208 | pla 209 | plp 210 | 211 | ; Return to normal operation. Note that we must issue an 'rti' instruction 212 | ; here, not 'rts', as we are returning from an interrupt. 213 | 214 | rti 215 | 216 | 217 | ; ----- Utility Routine: copy KERNAL ROM to underlying RAM ----- 218 | 219 | copy_rom_to_ram: 220 | 221 | ; This is somewhat more involved than I let on above. The memory mapping 222 | ; facilities of the C64 are a bit convoluted. The Programmer's Reference 223 | ; Guide states on page 261 that the way to map out the KERNAL ROM, and 224 | ; map in the RAM underlying it, is to set the HIRAM signal on the 6510's 225 | ; I/O line (which is memory-mapped to address $0001) to 0. This is true. 226 | ; However, it is not the whole story: setting HIRAM to 0 *also* maps out 227 | ; BASIC ROM and maps in the RAM underlying *it*. I suppose this makes 228 | ; sense from a design point of view; after all, BASIC uses the KERNAL, so 229 | ; there wouldn't be much sense leaving it mapped when the KERNAL is mapped 230 | ; out. Anyway, what this means for us is that we must copy both of these 231 | ; ROMs to their respective underlying RAMs if we want to survive returning 232 | ; to BASIC. 233 | 234 | ldx #>basic 235 | ldy #$c0 236 | jsr copy_block 237 | 238 | ldx #>kernal 239 | ldy #$00 240 | jsr copy_block 241 | 242 | ; To actually substitute the RAM for the ROM in the memory map, we 243 | ; set HIRAM (the second least significant bit) to 0. 244 | 245 | lda io_ctrl 246 | and #%11111101 247 | sta io_ctrl 248 | 249 | rts 250 | 251 | 252 | ; ----- Utility Routine: copy a ROM memory block to the underlying RAM ----- 253 | 254 | ; Input: x register = high byte of start address (low byte = #$00) 255 | ; y register = high byte of end address (stops at address $yy00 - 1) 256 | 257 | ; This subroutine is a fairly straightforward memory copy loop. A somewhat 258 | ; counter-intuitive feature is that we immediately store each byte in the 259 | ; same location where we just read it from. We can do this because, even 260 | ; when the KERNAL or BASIC ROM is mapped in, writes to those locations still 261 | ; go to the underlying RAM. 262 | 263 | copy_block: stx zp+1 264 | sty stop_at 265 | ldy #$00 266 | sty zp 267 | 268 | copy_loop: lda (zp), y 269 | sta (zp), y 270 | iny 271 | cpy #$00 272 | bne copy_loop 273 | ldx zp+1 274 | inx 275 | stx zp+1 276 | cpx stop_at 277 | bne copy_loop 278 | rts 279 | 280 | ; ----- Variables ----- 281 | 282 | ; 'scanline' stores the raster line that we want the interrupt to trigger 283 | ; on; it gets loaded into the VIC-II's 'vic_raster' register. 284 | 285 | scanline: .byte %01010101 286 | 287 | ; We also reserve space to store the address of the interrupt service 288 | ; routine that we are replacing in the IRQ vector, so that we can transfer 289 | ; control to it at the end of our routine. 290 | 291 | .space saved_irq_vec 2 292 | 293 | ; ----- END of ribos.p65 ----- 294 | -------------------------------------------------------------------------------- /eg/c64/ribos/ribos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catseye/SixtyPical/df610e639c4a16855258c51e857b0376d42566a9/eg/c64/ribos/ribos.png -------------------------------------------------------------------------------- /eg/c64/ribos/ribos.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catseye/SixtyPical/df610e639c4a16855258c51e857b0376d42566a9/eg/c64/ribos/ribos.prg -------------------------------------------------------------------------------- /eg/c64/ribos/ribos2-60p.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catseye/SixtyPical/df610e639c4a16855258c51e857b0376d42566a9/eg/c64/ribos/ribos2-60p.prg -------------------------------------------------------------------------------- /eg/c64/ribos/ribos2.60p: -------------------------------------------------------------------------------- 1 | // ribos2.60p - SixtyPical source for RIBOS2: 2 | // Demonstration of the VIC-II raster interrupt on the Commodore 64: 3 | // Alter the border colour in the middle part of the screen only, 4 | // Simplified (KERNAL IRQ vector) version. 5 | 6 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 7 | // For more information, please refer to 8 | // SPDX-License-Identifier: Unlicense 9 | 10 | // For comments, see ribos2.p65. 11 | 12 | // ----- Types ----- 13 | 14 | typedef routine 15 | inputs border_color, vic_intr, scanline 16 | outputs border_color, vic_intr, scanline 17 | trashes a, z, n, c, vic_raster 18 | irq_handler 19 | 20 | // ----- Addresses ----- 21 | 22 | // The CIA #1 chip. 23 | 24 | byte cia1 @ $dc00 // pp. 328-331 25 | byte intr_ctrl @ $dc0d // "CIA Interrupt Control Register 26 | // (Read IRQs/Write Mask)" 27 | 28 | // The VIC-II chip. 29 | 30 | byte vic @ $d000 // Appendix G: 31 | byte vic_ctrl @ $d011 // "Y SCROLL MODE" 32 | byte vic_raster @ $d012 // "RASTER" 33 | byte vic_intr @ $d019 // "Interrupt Request's" (sic) 34 | byte vic_intr_enable @ $d01a // "Interrupt Request MASKS" 35 | byte border_color @ $d020 // "BORDER COLOR" 36 | 37 | // The address at which the IRQ vector is stored. 38 | 39 | vector irq_handler cinv @ $314 // p. 319, "Vector: Hardware 40 | // IRQ Interrupt" 41 | 42 | // ----- Variables ----- 43 | 44 | vector irq_handler saved_irq_vec 45 | byte scanline : 85 // %01010101 46 | 47 | // ----- Externals ------ 48 | 49 | // An address in ROM which contains "PLA TAY PLA TAX RTI". 50 | // ribos2.p65 just contains these instructions itself, but 51 | // generating them as part of a SixtyPical program would not 52 | // be practical. So we just jump to this location instead. 53 | 54 | define pla_tay_pla_tax_pla_rti routine 55 | inputs border_color, vic_intr 56 | outputs border_color, vic_intr 57 | trashes a, z, n, c 58 | @ $EA81 59 | 60 | // ----- Interrupt Handler ----- 61 | 62 | define our_service_routine irq_handler 63 | { 64 | ld a, vic_intr 65 | st a, vic_intr 66 | and a, 1 67 | cmp a, 1 68 | if not z { 69 | goto saved_irq_vec 70 | } else { 71 | ld a, border_color 72 | xor a, $ff 73 | st a, border_color 74 | 75 | ld a, scanline 76 | xor a, $ff 77 | st a, scanline 78 | st a, vic_raster 79 | 80 | trash vic_raster 81 | 82 | goto pla_tay_pla_tax_pla_rti 83 | } 84 | } 85 | 86 | // ----- Main Program ----- 87 | 88 | define main routine 89 | inputs cinv, scanline, vic_ctrl, intr_ctrl 90 | outputs cinv, saved_irq_vec, vic_ctrl, vic_intr_enable 91 | trashes a, n, z, vic_raster 92 | { 93 | with interrupts off { 94 | copy cinv, saved_irq_vec 95 | copy our_service_routine, cinv 96 | 97 | ld a, scanline 98 | st a, vic_raster 99 | ld a, vic_ctrl 100 | and a, 127 101 | st a, vic_ctrl 102 | ld a, 1 103 | st a, vic_intr_enable 104 | ld a, intr_ctrl 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /eg/c64/ribos/ribos2.p65: -------------------------------------------------------------------------------- 1 | ; ribos2.p65 - p65 assembly source for RIBOS2: 2 | ; Demonstration of the VIC-II raster interrupt on the Commodore 64: 3 | ; Alter the border colour in the middle part of the screen only, 4 | ; Simplified (KERNAL IRQ vector) version. 5 | 6 | ; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 7 | ; For more information, please refer to 8 | ; SPDX-License-Identifier: Unlicense 9 | 10 | ; ----- BEGIN ribos2.p65 ----- 11 | 12 | ; This is a simplified version of ribos.p65 which uses the Commodore 64 13 | ; KERNAL's support for interrupt handling. Whereas ribos.p65 could run 14 | ; with the KERNAL completely disabled, ribos2.p65 relies on it. 15 | 16 | ; I'll assume you've read through ribos.p65 at least once, and won't 17 | ; say much about the code that's shared between the two programs. 18 | ; Again, page references are from the 'Commodore 64 Programmer's 19 | ; Reference Guide'. 20 | 21 | .org 0 22 | .word $C000 23 | .org $C000 24 | 25 | ; ----- Constants ----- 26 | 27 | ; The CIA #1 chip. 28 | 29 | .alias cia1 $dc00 ; pp. 328-331 30 | .alias intr_ctrl cia1+$d ; "CIA Interrupt Control Register 31 | ; (Read IRQs/Write Mask)" 32 | 33 | ; The VIC-II chip. 34 | 35 | .alias vic $d000 ; Appendix G: 36 | .alias vic_ctrl vic+$11 ; "Y SCROLL MODE" 37 | .alias vic_raster vic+$12 ; "RASTER" 38 | .alias vic_intr vic+$19 ; "Interrupt Request's" (sic) 39 | .alias vic_intr_enable vic+$1a ; "Interrupt Request MASKS" 40 | .alias border_color vic+$20 ; "BORDER COLOR" 41 | 42 | ; The address at which the IRQ vector is stored. 43 | 44 | .alias cinv $0314 ; p. 319, "Vector: Hardware 45 | ; IRQ Interrupt" 46 | 47 | ; ----- Main Routine ----- 48 | 49 | ; This routine is intended to be called by the user (by, e.g., SYS 49152). 50 | ; It installs the raster interrupt handler and returns to the caller. 51 | 52 | ; Key to installing the interrupt handler is altering the IRQ service 53 | ; vector. As I learned from a careful reading of Sheldon Leemon's 54 | ; "Mapping the Commodore 64", if we wish to leave the KERNAL intact and 55 | ; operational, it gives us an easy way to do that. Whenever it handles 56 | ; an IRQ, it has to decide whether the IRQ was generated by a BRK 57 | ; instruction, or some external device issuing an interrupt request. In 58 | ; either case, it jumps indirectly through its IRQ-handling vector. We 59 | ; can simply modify this vector (at $314) to point to our routine. In 60 | ; fact, this is the "CINV" vector, which may already be familiar to you 61 | ; if you have done any modest amount of C64 machine language programming; 62 | ; it is normally called every 1/60 of a second due to the interrupt 63 | ; request generated by CIA#1 Timer B. Here we will simply be using it 64 | ; to detect when a raster interrupt has occurred, as well. 65 | 66 | ; First, disable interrupts before changing interrupt vectors. 67 | 68 | sei 69 | 70 | ; We obtain the address of the current IRQ service routine in CINV 71 | ; and save it in the variable 'saved_irq_vec'. 72 | 73 | lda cinv 74 | sta saved_irq_vec 75 | lda cinv+1 76 | sta saved_irq_vec+1 77 | 78 | ; We then store the address of our IRQ service routine in its place. 79 | 80 | lda #our_service_routine 83 | sta cinv+1 84 | 85 | ; Now we must specify the raster line at which the interrupt gets called, 86 | ; setting the ninth bit to zero. 87 | 88 | lda scanline 89 | sta vic_raster 90 | lda vic_ctrl 91 | and #%01111111 92 | sta vic_ctrl 93 | 94 | ; Then we enable the raster interrupt on the VIC-II chip. 95 | 96 | lda #$01 97 | sta vic_intr_enable 98 | 99 | ; We read the interrupt control port of CIA #1 for good measure. 100 | 101 | lda intr_ctrl 102 | 103 | ; And we re-enable interrupts to resume normal operation -- plus our jazz. 104 | 105 | cli 106 | 107 | ; Finally, we return to the caller. 108 | 109 | rts 110 | 111 | ; ----- Raster Interrupt Service Routine ------ 112 | 113 | our_service_routine: 114 | 115 | ; First, we check to see if the current interrupt was caused by the raster. 116 | 117 | lda vic_intr 118 | sta vic_intr 119 | and #$01 120 | cmp #$01 121 | beq we_handle_it 122 | 123 | ; If the interrupt was not caused by the raster, we continue execution as 124 | ; normal, with the existing interrupt service routine. 125 | 126 | jmp (saved_irq_vec) 127 | 128 | we_handle_it: 129 | 130 | ; If we got here, the interrupt was caused by the raster, so we do our thing. 131 | 132 | lda border_color 133 | eor #$ff 134 | sta border_color 135 | 136 | ; Now, we make the interrupt trigger on the other scan line next time. 137 | 138 | lda scanline 139 | eor #$ff 140 | sta scanline 141 | sta vic_raster 142 | 143 | ; Return to normal operation. If we simply continue with the standard 144 | ; interrupt service routine by jumping through saved_irq_req, we will 145 | ; confuse it a bit, as it expects to be called 60 times per second, and 146 | ; continuing it here would make it occur more frequently. The result 147 | ; would be the cursor blinking more frequently and the time of day clock 148 | ; running fast. 149 | 150 | ; So, we issue a plain return from interrupt (RTI) here. But first, we 151 | ; must make sure that the state of the system is back to the way it was 152 | ; when the interrupt service routine was called. Since it can be called 153 | ; from anywhere, and since the code that was interrupted likely cares 154 | ; deeply about the values in its registers, the KERNAL routine which 155 | ; dispatches through CINV first carefully saves the contents of the 156 | ; registers on the stack. We must be equally careful about restoring 157 | ; those values before switching back to that interrupted code. 158 | 159 | pla 160 | tay 161 | pla 162 | tax 163 | pla 164 | 165 | rti 166 | 167 | 168 | ; ----- Variables ----- 169 | 170 | ; 'scanline' stores the raster line that we want the interrupt to trigger 171 | ; on; it gets loaded into the VIC-II's 'vic_raster' register. 172 | 173 | scanline: .byte %01010101 174 | 175 | ; We also reserve space to store the address of the interrupt service 176 | ; routine that we are replacing in the IRQ vector, so that we can transfer 177 | ; control to it at the end of our routine. 178 | 179 | .space saved_irq_vec 2 180 | 181 | ; ----- END of ribos2.p65 ----- 182 | -------------------------------------------------------------------------------- /eg/c64/ribos/ribos2.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catseye/SixtyPical/df610e639c4a16855258c51e857b0376d42566a9/eg/c64/ribos/ribos2.prg -------------------------------------------------------------------------------- /eg/c64/screen1.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | byte screen @ 1024 6 | 7 | define main routine 8 | trashes a, z, n, screen 9 | { 10 | ld a, 83 11 | st a, screen 12 | } 13 | -------------------------------------------------------------------------------- /eg/rudiments/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | This directory contains example sources which demonstrate 8 | the rudiments of SixtyPical. 9 | 10 | Examples that are meant to fail and produce an error message 11 | when being compiled are in the `errorful/` subdirectory. 12 | 13 | The other sources are portable across architectures. They use 14 | `include` directives to bring in architecture-dependent libraries 15 | to produce output. Libraries for such are provided in the 16 | architecture-specific subdirectories of the `include` directory 17 | in the root directory of this repository; make sure it is on the 18 | compiler's include search path. For example: 19 | 20 | sixtypical --run-on=x64 -I../../include/c64/:../../include/stdlib/ vector-table.60p 21 | 22 | `chrout` is a routine with outputs the value of the accumulator 23 | as an ASCII character, disturbing none of the other registers, 24 | simply for the purposes of producing some observable output. 25 | 26 | (There is a KERNAL routine which does this on both the 27 | Commodore 64 and the Commodore VIC-20. It should not be hard 28 | to find or write such a routine for most other architectures.) 29 | -------------------------------------------------------------------------------- /eg/rudiments/add.60p: -------------------------------------------------------------------------------- 1 | // Should print YY 2 | 3 | include "chrout.60p" 4 | 5 | word score 6 | 7 | define main routine 8 | inputs a, score 9 | outputs score 10 | trashes a, c, z, n, v 11 | { 12 | ld a, 3 13 | st off, c 14 | add a, 4 15 | 16 | cmp a, 7 17 | if z { 18 | ld a, 89 19 | call chrout 20 | } else { 21 | ld a, 78 22 | call chrout 23 | } 24 | 25 | copy 999, score 26 | st off, c 27 | add score, 1999 28 | 29 | cmp score, 2998 30 | if z { 31 | ld a, 89 32 | call chrout 33 | } else { 34 | ld a, 78 35 | call chrout 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /eg/rudiments/buffer.60p: -------------------------------------------------------------------------------- 1 | // Should print Y 2 | 3 | include "chrout.60p" 4 | 5 | byte table[2048] buf 6 | pointer ptr @ 254 7 | byte foo 8 | 9 | define main routine 10 | inputs buf 11 | outputs buf, y, foo 12 | trashes a, z, n, c, ptr 13 | { 14 | ld y, 0 15 | point ptr into buf { 16 | reset ptr 0 17 | copy 123, [ptr] + y 18 | copy [ptr] + y, foo 19 | copy foo, [ptr] + y 20 | } 21 | 22 | // TODO: support saying `cmp foo, 123`, maybe 23 | ld a, foo 24 | cmp a, 123 25 | 26 | if z { 27 | ld a, 89 28 | call chrout 29 | } else { 30 | ld a, 78 31 | call chrout 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /eg/rudiments/call.60p: -------------------------------------------------------------------------------- 1 | // Should print AA 2 | 3 | include "chrout.60p" 4 | 5 | define print routine 6 | trashes a, z, n 7 | { 8 | ld a, 65 9 | call chrout 10 | } 11 | 12 | define main routine 13 | trashes a, z, n 14 | { 15 | call print 16 | call print 17 | } 18 | -------------------------------------------------------------------------------- /eg/rudiments/cmp-byte.60p: -------------------------------------------------------------------------------- 1 | // Should print ENGGL 2 | 3 | include "chrout.60p" 4 | 5 | byte b 6 | 7 | define main routine 8 | outputs b 9 | trashes a, x, y, z, n, c, v 10 | { 11 | ld a, 40 12 | st a, b 13 | 14 | cmp a, b 15 | if z { 16 | ld a, 69 // E 17 | call chrout 18 | } else { 19 | ld a, 78 // N 20 | call chrout 21 | } 22 | 23 | ld a, 41 24 | st a, b 25 | ld a, 40 26 | 27 | cmp a, b 28 | if z { 29 | ld a, 69 // E 30 | call chrout 31 | } else { 32 | ld a, 78 // N 33 | call chrout 34 | } 35 | 36 | ld a, 20 37 | st a, b 38 | 39 | ld a, 21 40 | 41 | cmp a, b // 21 >= 20 42 | if c { 43 | ld a, 71 // G 44 | call chrout 45 | } else { 46 | ld a, 76 // L 47 | call chrout 48 | } 49 | 50 | ld a, 20 51 | 52 | cmp a, b // 20 >= 20 53 | if c { 54 | ld a, 71 // G 55 | call chrout 56 | } else { 57 | ld a, 76 // L 58 | call chrout 59 | } 60 | 61 | ld a, 19 62 | 63 | cmp a, b // 19 < 20 64 | if c { 65 | ld a, 71 // G 66 | call chrout 67 | } else { 68 | ld a, 76 // L 69 | call chrout 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /eg/rudiments/cmp-litword.60p: -------------------------------------------------------------------------------- 1 | // Should print ENGGL 2 | 3 | include "chrout.60p" 4 | 5 | word w1 6 | 7 | define main routine 8 | outputs w1 9 | trashes a, x, y, z, n, c, v 10 | { 11 | copy 4000, w1 12 | 13 | cmp w1, 4000 14 | if z { 15 | ld a, 69 // E 16 | call chrout 17 | } else { 18 | ld a, 78 // N 19 | call chrout 20 | } 21 | 22 | copy 4000, w1 23 | 24 | cmp w1, 4001 25 | if z { 26 | ld a, 69 // E 27 | call chrout 28 | } else { 29 | ld a, 78 // N 30 | call chrout 31 | } 32 | 33 | copy 20002, w1 34 | 35 | cmp w1, 20001 // 20002 >= 20001 36 | if c { 37 | ld a, 71 // G 38 | call chrout 39 | } else { 40 | ld a, 76 // L 41 | call chrout 42 | } 43 | 44 | copy 20001, w1 45 | 46 | cmp w1, 20001 // 20001 >= 20001 47 | if c { 48 | ld a, 71 // G 49 | call chrout 50 | } else { 51 | ld a, 76 // L 52 | call chrout 53 | } 54 | 55 | copy 20000, w1 56 | 57 | cmp w1, 20001 // 20000 < 20001 58 | if c { 59 | ld a, 71 // G 60 | call chrout 61 | } else { 62 | ld a, 76 // L 63 | call chrout 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /eg/rudiments/cmp-word.60p: -------------------------------------------------------------------------------- 1 | // Should print ENGGL 2 | 3 | include "chrout.60p" 4 | 5 | word w1 6 | word w2 7 | 8 | define main routine 9 | outputs w1, w2 10 | trashes a, x, y, z, n, c, v 11 | { 12 | copy 4000, w1 13 | copy 4000, w2 14 | 15 | cmp w1, w2 16 | if z { 17 | ld a, 69 // E 18 | call chrout 19 | } else { 20 | ld a, 78 // N 21 | call chrout 22 | } 23 | 24 | copy 4000, w1 25 | copy 4001, w2 26 | 27 | cmp w1, w2 28 | if z { 29 | ld a, 69 // E 30 | call chrout 31 | } else { 32 | ld a, 78 // N 33 | call chrout 34 | } 35 | 36 | copy 20002, w1 37 | copy 20001, w2 38 | 39 | cmp w1, w2 // 20002 >= 20001 40 | if c { 41 | ld a, 71 // G 42 | call chrout 43 | } else { 44 | ld a, 76 // L 45 | call chrout 46 | } 47 | 48 | copy 20001, w1 49 | 50 | cmp w1, w2 // 20001 >= 20001 51 | if c { 52 | ld a, 71 // G 53 | call chrout 54 | } else { 55 | ld a, 76 // L 56 | call chrout 57 | } 58 | 59 | copy 20000, w1 60 | 61 | cmp w1, w2 // 20000 < 20001 62 | if c { 63 | ld a, 71 // G 64 | call chrout 65 | } else { 66 | ld a, 76 // L 67 | call chrout 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /eg/rudiments/conditional.60p: -------------------------------------------------------------------------------- 1 | // Should print YN 2 | 3 | include "chrout.60p" 4 | 5 | define main routine 6 | trashes a, x, y, z, n, c, v 7 | { 8 | ld a, 0 9 | if z { 10 | ld a, 89 11 | call chrout 12 | } else { 13 | ld a, 78 14 | call chrout 15 | } 16 | 17 | ld a, 1 18 | if z { 19 | ld a, 89 20 | call chrout 21 | } else { 22 | ld a, 78 23 | call chrout 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /eg/rudiments/conditional2.60p: -------------------------------------------------------------------------------- 1 | // Should print YA 2 | 3 | include "chrout.60p" 4 | 5 | define main routine 6 | trashes a, x, y, z, n, c, v 7 | { 8 | ld a, 0 9 | if z { 10 | ld a, 89 11 | call chrout 12 | ld a, 1 13 | } 14 | 15 | ld a, 65 16 | call chrout 17 | 18 | ld a, 1 19 | if z { 20 | ld a, 89 21 | call chrout 22 | ld a, 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /eg/rudiments/errorful/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | This directory contains example SixtyPical sources which 8 | are intentionally invalid (for demonstration purposes) 9 | and are expected to elicit an error message from a 10 | SixtyPical implementation. 11 | -------------------------------------------------------------------------------- /eg/rudiments/errorful/add.60p: -------------------------------------------------------------------------------- 1 | // should fail analysis with an UnmeaningfulReadError 2 | // because adding 4 to the accumulator reads the carry 3 | // but the carry has neither been provided as input 4 | // nor set to anything in particular by this routine. 5 | 6 | define add_four routine 7 | inputs a 8 | outputs a 9 | { 10 | add a, 4 11 | } 12 | -------------------------------------------------------------------------------- /eg/rudiments/errorful/range.60p: -------------------------------------------------------------------------------- 1 | // should fail analysis with a RangeExceededError 2 | // because the index is detected to fall outside the 3 | // allowable range of the table it is indexing. 4 | 5 | byte table[8] message : "WHAT?" 6 | 7 | define main routine 8 | inputs message 9 | outputs x, a, z, n 10 | { 11 | ld x, 9 12 | ld a, message + x 13 | } 14 | -------------------------------------------------------------------------------- /eg/rudiments/errorful/vector.60p: -------------------------------------------------------------------------------- 1 | // should fail analysis with a ConstantConstraintError 2 | // because it cannot copy the address of `foo` into `vec` 3 | // because it has incompatible constraints. 4 | 5 | vector routine 6 | inputs y 7 | outputs y 8 | trashes z, n 9 | vec 10 | 11 | define foo routine 12 | inputs x 13 | outputs x 14 | trashes z, n 15 | { 16 | inc x 17 | } 18 | 19 | define main routine 20 | inputs foo 21 | outputs vec 22 | trashes a, z, n 23 | { 24 | copy foo, vec 25 | } 26 | -------------------------------------------------------------------------------- /eg/rudiments/example.60p: -------------------------------------------------------------------------------- 1 | // Should print 01 2 | 3 | include "chrout.60p" 4 | include "prbyte.60p" 5 | 6 | byte lives 7 | 8 | define main routine 9 | inputs lives, hexchars 10 | outputs lives 11 | trashes a, x, z, n, c, v 12 | { 13 | ld a, 0 14 | st a, lives 15 | ld x, lives 16 | st off, c 17 | inc x 18 | st x, lives 19 | ld a, lives 20 | call prbyte 21 | } 22 | -------------------------------------------------------------------------------- /eg/rudiments/forever.60p: -------------------------------------------------------------------------------- 1 | // This program is expected to loop forever. 2 | // Be prepared to forcibly terminate your emulator. 3 | 4 | define main routine 5 | trashes a, y, z, n, c 6 | { 7 | ld y, 65 8 | repeat { 9 | inc y 10 | } forever 11 | } 12 | -------------------------------------------------------------------------------- /eg/rudiments/goto.60p: -------------------------------------------------------------------------------- 1 | // Should print AB 2 | 3 | include "chrout.60p" 4 | 5 | define bar routine trashes a, z, n { 6 | ld a, 66 7 | call chrout 8 | } 9 | 10 | define main routine trashes a, z, n { 11 | ld a, 65 12 | call chrout 13 | goto bar 14 | } 15 | -------------------------------------------------------------------------------- /eg/rudiments/loop.60p: -------------------------------------------------------------------------------- 1 | // Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ 2 | 3 | include "chrout.60p" 4 | 5 | define main routine 6 | trashes a, y, z, n, c 7 | { 8 | ld y, 65 9 | repeat { 10 | ld a, y 11 | call chrout 12 | inc y 13 | cmp y, 91 14 | } until z 15 | } 16 | -------------------------------------------------------------------------------- /eg/rudiments/memloc.60p: -------------------------------------------------------------------------------- 1 | // Should print AB 2 | 3 | include "chrout.60p" 4 | 5 | byte foo 6 | 7 | define print routine 8 | inputs foo 9 | trashes a, z, n 10 | { 11 | ld a, foo 12 | call chrout 13 | } 14 | 15 | define main routine 16 | trashes a, y, z, n, foo 17 | { 18 | ld y, 65 19 | st y, foo 20 | call print 21 | inc foo 22 | call print 23 | } 24 | -------------------------------------------------------------------------------- /eg/rudiments/nested-for.60p: -------------------------------------------------------------------------------- 1 | // Should print H (being ASCII 72 = 8 * 9) 2 | 3 | include "chrout.60p" 4 | 5 | // Increase y by 7, circuitously 6 | // 7 | define foo routine 8 | inputs y 9 | outputs y, n, z 10 | trashes a, c 11 | { 12 | save x { 13 | ld x, 0 14 | for x up to 6 { 15 | inc y 16 | } 17 | } 18 | } 19 | 20 | // Each iteration increases y by 8; there are 9 iterations 21 | // 22 | define main routine 23 | outputs x, y, n, z 24 | trashes a, c 25 | { 26 | ld x, 0 27 | ld y, 0 28 | for x up to 8 { 29 | inc y 30 | call foo 31 | } 32 | ld a, y 33 | call chrout 34 | } 35 | -------------------------------------------------------------------------------- /eg/rudiments/print.60p: -------------------------------------------------------------------------------- 1 | // Should print A 2 | 3 | include "chrout.60p" 4 | 5 | define main routine 6 | inputs a 7 | trashes a, z, n 8 | { 9 | ld a, 65 10 | call chrout 11 | } 12 | -------------------------------------------------------------------------------- /eg/rudiments/vector-table.60p: -------------------------------------------------------------------------------- 1 | // Should print AABAB 2 | 3 | // Demonstrates vector tables. 4 | 5 | include "chrout.60p" 6 | 7 | vector routine 8 | trashes a, z, n 9 | print 10 | 11 | vector (routine 12 | trashes a, z, n) 13 | table[32] vectors 14 | 15 | define printa routine 16 | trashes a, z, n 17 | { 18 | ld a, 65 19 | call chrout 20 | } 21 | 22 | define printb routine 23 | trashes a, z, n 24 | { 25 | ld a, 66 26 | call chrout 27 | } 28 | 29 | define main routine 30 | inputs vectors 31 | outputs vectors 32 | trashes print, a, x, z, n, c 33 | { 34 | ld x, 0 35 | copy printa, vectors + x 36 | inc x 37 | copy printa, print 38 | copy print, vectors + x 39 | inc x 40 | copy printb, print 41 | copy print, vectors + x 42 | inc x 43 | copy printa, vectors + x 44 | inc x 45 | copy printb, vectors + x 46 | 47 | ld x, 0 48 | for x up to 4 { 49 | copy vectors + x, print 50 | call print 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /eg/rudiments/vector.60p: -------------------------------------------------------------------------------- 1 | // Should print AB 2 | 3 | include "chrout.60p" 4 | 5 | vector routine 6 | trashes a, z, n 7 | print 8 | 9 | define printa routine 10 | trashes a, z, n 11 | { 12 | ld a, 65 13 | call chrout 14 | } 15 | 16 | define printb routine 17 | trashes a, z, n 18 | { 19 | ld a, 66 20 | call chrout 21 | } 22 | 23 | define main routine 24 | trashes print, a, z, n 25 | { 26 | copy printa, print 27 | call print 28 | copy printb, print 29 | call print 30 | } 31 | -------------------------------------------------------------------------------- /eg/rudiments/word-table.60p: -------------------------------------------------------------------------------- 1 | // Should print YY 2 | 3 | include "chrout.60p" 4 | 5 | word one 6 | word table[256] many 7 | 8 | define main routine 9 | inputs one, many 10 | outputs one, many 11 | trashes a, x, y, c, n, z 12 | { 13 | ld x, 0 14 | ld y, 1 15 | copy 777, one 16 | copy one, many + x 17 | copy 888, one 18 | copy one, many + y 19 | 20 | ld x, 1 21 | ld y, 0 22 | 23 | copy many + x, one 24 | cmp one, 888 25 | if z { 26 | ld a, 89 27 | call chrout 28 | } else { 29 | ld a, 78 30 | call chrout 31 | } 32 | 33 | copy many + y, one 34 | cmp one, 777 35 | if z { 36 | ld a, 89 37 | call chrout 38 | } else { 39 | ld a, 78 40 | call chrout 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /eg/vic20/hearts.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | // Displays 256 hearts at the top of the VIC-20's screen. 6 | 7 | // Define where the screen starts in memory: 8 | byte table[256] screen @ 7680 9 | 10 | define main routine 11 | // These are the values that will be written to by this routine: 12 | trashes a, x, z, n, screen 13 | { 14 | ld x, 0 15 | ld a, 83 // 83 = screen code for heart 16 | repeat { 17 | st a, screen + x 18 | inc x 19 | } until z // this flag will be set when x wraps around from 255 to 0 20 | } 21 | -------------------------------------------------------------------------------- /images/hearts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catseye/SixtyPical/df610e639c4a16855258c51e857b0376d42566a9/images/hearts.png -------------------------------------------------------------------------------- /include/c64/chrout.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | // Implementation of `chrout` for the Commodore 64 platform. 6 | 7 | define chrout routine 8 | inputs a 9 | trashes a 10 | @ 65490 11 | -------------------------------------------------------------------------------- /include/c64/joystick.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | byte joy2 @ $dc00 6 | 7 | word delta 8 | 9 | // Read the joystick and compute the delta it represents 10 | // in a row-based 40-column grid like the C64's screen. 11 | 12 | define read_stick routine 13 | inputs joy2 14 | outputs delta 15 | trashes a, x, z, n 16 | { 17 | ld x, joy2 18 | ld a, x 19 | and a, 1 // up 20 | if z { 21 | copy $ffd8, delta // -40 22 | } else { 23 | ld a, x 24 | and a, 2 // down 25 | if z { 26 | copy word 40, delta 27 | } else { 28 | ld a, x 29 | and a, 4 // left 30 | if z { 31 | copy $ffff, delta // -1 32 | } else { 33 | ld a, x 34 | and a, 8 // right 35 | if z { 36 | copy word 1, delta 37 | } else { 38 | copy word 0, delta 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | // You can repeatedly (i.e. as part of actor logic or an IRQ handler) 46 | // call this routine. 47 | // Upon return, if carry is set, the button was pressed then released. 48 | 49 | define check_button routine 50 | inputs joy2 51 | outputs c 52 | trashes a, z, n 53 | static byte button_down : 0 54 | { 55 | ld a, button_down 56 | if z { 57 | ld a, joy2 58 | and a, $10 59 | if z { 60 | ld a, 1 61 | st a, button_down 62 | } 63 | st off, c 64 | } else { 65 | ld a, joy2 66 | and a, $10 67 | if not z { 68 | ld a, 0 69 | st a, button_down 70 | st on, c 71 | } else { 72 | st off, c 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /include/stdlib/prbyte.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | byte table[16] hexchars : "0123456789ABCDEF" 6 | 7 | define prbyte routine 8 | inputs a, hexchars 9 | trashes a, z, n, c, v 10 | { 11 | save x { 12 | save a { 13 | st off, c 14 | shr a 15 | shr a 16 | shr a 17 | shr a 18 | and a, 15 19 | ld x, a 20 | ld a, hexchars + x 21 | call chrout 22 | } 23 | save a { 24 | and a, 15 25 | ld x, a 26 | ld a, hexchars + x 27 | call chrout 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /include/vic20/chrout.60p: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain. 2 | // For more information, please refer to 3 | // SPDX-License-Identifier: Unlicense 4 | 5 | // Implementation of `chrout` for the Commodore VIC-20 platform. 6 | 7 | define chrout routine 8 | inputs a 9 | trashes a 10 | @ 65490 11 | -------------------------------------------------------------------------------- /src/sixtypical/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catseye/SixtyPical/df610e639c4a16855258c51e857b0376d42566a9/src/sixtypical/__init__.py -------------------------------------------------------------------------------- /src/sixtypical/ast.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | # encoding: UTF-8 6 | 7 | class AST(object): 8 | children_attrs = () 9 | child_attrs = () 10 | value_attrs = () 11 | 12 | def __init__(self, line_number, **kwargs): 13 | self.line_number = line_number 14 | self.attrs = {} 15 | for attr in self.children_attrs: 16 | self.attrs[attr] = kwargs.pop(attr, []) 17 | for child in self.attrs[attr]: 18 | assert child is None or isinstance(child, AST), \ 19 | "child %s=%r of %r is not an AST node" % (attr, child, self) 20 | for attr in self.child_attrs: 21 | self.attrs[attr] = kwargs.pop(attr, None) 22 | child = self.attrs[attr] 23 | assert child is None or isinstance(child, AST), \ 24 | "child %s=%r of %r is not an AST node" % (attr, child, self) 25 | for attr in self.value_attrs: 26 | self.attrs[attr] = kwargs.pop(attr, None) 27 | assert (not kwargs), "extra arguments supplied to {} node: {}".format(self.type, kwargs) 28 | 29 | @property 30 | def type(self): 31 | return self.__class__.__name__ 32 | 33 | def __repr__(self): 34 | return "%s(%r)" % (self.__class__.__name__, self.attrs) 35 | 36 | def __getattr__(self, name): 37 | if name in self.attrs: 38 | return self.attrs[name] 39 | raise AttributeError(name) 40 | 41 | def all_children(self): 42 | for attr in self.children_attrs: 43 | for child in self.attrs[attr]: 44 | if child is not None: 45 | yield child 46 | for subchild in child.all_children(): 47 | yield subchild 48 | for attr in self.child_attrs: 49 | child = self.attrs[attr] 50 | if child is not None: 51 | yield child 52 | for subchild in child.all_children(): 53 | yield subchild 54 | 55 | 56 | class Program(AST): 57 | children_attrs = ('defns', 'routines',) 58 | 59 | 60 | class Defn(AST): 61 | value_attrs = ('name', 'addr', 'initial',) 62 | 63 | 64 | class Routine(AST): 65 | value_attrs = ('name', 'addr', 'initial',) 66 | children_attrs = ('locals',) 67 | child_attrs = ('block',) 68 | 69 | 70 | class Block(AST): 71 | children_attrs = ('instrs',) 72 | 73 | 74 | class Instr(AST): 75 | pass 76 | 77 | 78 | class SingleOp(Instr): 79 | value_attrs = ('opcode', 'dest', 'src',) 80 | 81 | 82 | class Reset(Instr): 83 | value_attrs = ('pointer', 'offset',) 84 | 85 | 86 | class Call(Instr): 87 | value_attrs = ('location',) 88 | 89 | 90 | class GoTo(Instr): 91 | value_attrs = ('location',) 92 | 93 | 94 | class If(Instr): 95 | value_attrs = ('src', 'inverted',) 96 | child_attrs = ('block1', 'block2',) 97 | 98 | 99 | class Repeat(Instr): 100 | value_attrs = ('src', 'inverted',) 101 | child_attrs = ('block',) 102 | 103 | 104 | class For(Instr): 105 | value_attrs = ('dest', 'direction', 'final',) 106 | child_attrs = ('block',) 107 | 108 | 109 | class WithInterruptsOff(Instr): 110 | child_attrs = ('block',) 111 | 112 | 113 | class Save(Instr): 114 | value_attrs = ('locations',) 115 | child_attrs = ('block',) 116 | 117 | 118 | class PointInto(Instr): 119 | value_attrs = ('pointer', 'table',) 120 | child_attrs = ('block',) 121 | -------------------------------------------------------------------------------- /src/sixtypical/callgraph.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | from sixtypical.ast import Program 6 | from sixtypical.model import RoutineType, VectorType 7 | 8 | 9 | def find_routines_matching_type(program, type_): 10 | """Return the subset of routines of the program whose value 11 | may be assigned to a location of the given type_ (typically 12 | a vector).""" 13 | return [r for r in program.routines if RoutineType.executable_types_compatible(r.routine_type, type_)] 14 | 15 | 16 | def mark_as_reachable(graph, routine_name): 17 | node = graph[routine_name] 18 | if node.get('reachable', False): 19 | return 20 | node['reachable'] = True 21 | for next_routine_name in node['potentially-calls']: 22 | mark_as_reachable(graph, next_routine_name) 23 | 24 | 25 | def construct_callgraph(program): 26 | graph = {} 27 | 28 | for routine in program.routines: 29 | potentially_calls = [] 30 | for (called_routine, called_routine_type) in routine.called_routines: 31 | if isinstance(called_routine_type, RoutineType): 32 | potentially_calls.append(called_routine.name) 33 | elif isinstance(called_routine_type, VectorType): 34 | for potentially_called in find_routines_matching_type(program, called_routine_type): 35 | potentially_calls.append(potentially_called.name) 36 | else: 37 | raise NotImplementedError 38 | graph[routine.name] = { 39 | 'potentially-calls': potentially_calls, 40 | } 41 | 42 | # Symmetric closure 43 | # (Note, this information isn't used anywhere yet) 44 | 45 | for routine in program.routines: 46 | potentially_called_by = [] 47 | for (name, node) in graph.items(): 48 | potential_calls = node['potentially-calls'] 49 | if routine.name in potential_calls: 50 | potentially_called_by.append(name) 51 | graph[routine.name]['potentially-called-by'] = potentially_called_by 52 | 53 | # Root set 54 | 55 | root_set = set() 56 | 57 | for routine in program.routines: 58 | if getattr(routine, 'preserved', False) or routine.name == 'main': 59 | root_set.add(routine) 60 | 61 | # Reachability 62 | 63 | for routine in root_set: 64 | mark_as_reachable(graph, routine.name) 65 | 66 | return graph 67 | 68 | 69 | def prune_unreachable_routines(program, callgraph): 70 | return Program(1, defns=program.defns, routines=[ 71 | r for r in program.routines if callgraph[r.name].get('reachable', False) 72 | ]) 73 | -------------------------------------------------------------------------------- /src/sixtypical/context.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | # encoding: UTF-8 6 | 7 | from sixtypical.model import ( 8 | RoutineType, ConstantRef, LocationRef, IndirectRef, IndexedRef, 9 | ) 10 | 11 | 12 | class AnalysisContext(object): 13 | """ 14 | A location is touched if it was changed (or even potentially 15 | changed) during this routine, or some routine called by this routine. 16 | 17 | A location is meaningful if it was an input to this routine, 18 | or if it was set to a meaningful value by some operation in this 19 | routine (or some routine called by this routine). 20 | 21 | If a location is meaningful, it has a range. This range represents 22 | the lowest and highest values that it might possibly be (i.e. we know 23 | it cannot possibly be below the lowest or above the highest.) In the 24 | absence of any usage information, the range of a byte, is 0..255 and 25 | the range of a word is 0..65535. 26 | 27 | A location is writeable if it was listed in the outputs and trashes 28 | lists of this routine. A location can also be temporarily marked 29 | unwriteable in certain contexts, such as `for` loops. 30 | """ 31 | def __init__(self, symtab, routine, inputs, outputs, trashes): 32 | from sixtypical.analyzer import ConstantConstraintError, InconsistentConstraintsError 33 | 34 | self.symtab = symtab 35 | self.routine = routine # Routine (AST node) 36 | self._touched = set() # {LocationRef} 37 | self._range = dict() # LocationRef -> (Int, Int) 38 | self._writeable = set() # {LocationRef} 39 | self._terminated = False 40 | self._gotos_encountered = set() 41 | self._pointer_assoc = dict() 42 | 43 | for ref in inputs: 44 | if self.is_constant(ref): 45 | raise ConstantConstraintError(self.routine, ref.name) 46 | self._range[ref] = self.max_range(ref) 47 | output_names = set() 48 | for ref in outputs: 49 | if self.is_constant(ref): 50 | raise ConstantConstraintError(self.routine, ref.name) 51 | output_names.add(ref.name) 52 | self._writeable.add(ref) 53 | for ref in trashes: 54 | if self.is_constant(ref): 55 | raise ConstantConstraintError(self.routine, ref.name) 56 | if ref.name in output_names: 57 | raise InconsistentConstraintsError(self.routine, ref.name) 58 | self._writeable.add(ref) 59 | 60 | def __str__(self): 61 | return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format( 62 | self.__class__.__name__, 63 | LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable) 64 | ) 65 | 66 | def to_json_data(self): 67 | type_ = self.symtab.fetch_global_type(self.routine.name) 68 | return { 69 | 'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)), 70 | 'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)), 71 | 'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)), 72 | 'touched': ','.join(sorted(loc.name for loc in self._touched)), 73 | 'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()), 74 | 'writeable': ','.join(sorted(loc.name for loc in self._writeable)), 75 | 'terminated': self._terminated, 76 | 'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)), 77 | } 78 | 79 | def clone(self): 80 | c = AnalysisContext(self.symtab, self.routine, [], [], []) 81 | c._touched = set(self._touched) 82 | c._range = dict(self._range) 83 | c._writeable = set(self._writeable) 84 | c._pointer_assoc = dict(self._pointer_assoc) 85 | c._gotos_encountered = set(self._gotos_encountered) 86 | return c 87 | 88 | def update_from(self, other): 89 | """Replaces the information in this context, with the information from the other context. 90 | This is an overwriting action - it does not attempt to merge the contexts. 91 | 92 | We do not replace the gotos_encountered for technical reasons. (In `analyze_if`, 93 | we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the 94 | set of contexts we are updating from, and we want to retain our own.)""" 95 | self.routine = other.routine 96 | self._touched = set(other._touched) 97 | self._range = dict(other._range) 98 | self._writeable = set(other._writeable) 99 | self._terminated = other._terminated 100 | self._pointer_assoc = dict(other._pointer_assoc) 101 | 102 | def each_meaningful(self): 103 | for ref in self._range.keys(): 104 | yield ref 105 | 106 | def each_touched(self): 107 | for ref in self._touched: 108 | yield ref 109 | 110 | def each_writeable(self): 111 | for ref in self._writeable: 112 | yield ref 113 | 114 | def assert_meaningful(self, *refs, **kwargs): 115 | from sixtypical.analyzer import UnmeaningfulReadError 116 | 117 | exception_class = kwargs.get('exception_class', UnmeaningfulReadError) 118 | for ref in refs: 119 | if self.symtab.has_local(self.routine.name, ref.name): 120 | if ref not in self._range: 121 | message = ref.name 122 | if kwargs.get('message'): 123 | message += ' (%s)' % kwargs['message'] 124 | raise exception_class(self.routine, message) 125 | else: 126 | continue 127 | if self.is_constant(ref): 128 | pass 129 | elif isinstance(ref, LocationRef): 130 | if ref not in self._range: 131 | message = ref.name 132 | if kwargs.get('message'): 133 | message += ' (%s)' % kwargs['message'] 134 | raise exception_class(self.routine, message) 135 | elif isinstance(ref, IndexedRef): 136 | self.assert_meaningful(ref.ref, **kwargs) 137 | self.assert_meaningful(ref.index, **kwargs) 138 | else: 139 | raise NotImplementedError(ref) 140 | 141 | def assert_writeable(self, *refs, **kwargs): 142 | from sixtypical.analyzer import ForbiddenWriteError 143 | 144 | exception_class = kwargs.get('exception_class', ForbiddenWriteError) 145 | for ref in refs: 146 | # locals are always writeable 147 | if self.symtab.has_local(self.routine.name, ref.name): 148 | continue 149 | if ref not in self._writeable: 150 | message = ref.name 151 | if kwargs.get('message'): 152 | message += ' (%s)' % kwargs['message'] 153 | raise exception_class(self.routine, message) 154 | 155 | def assert_in_range(self, inside, outside, offset): 156 | """Given two locations, assert that the first location, offset by the given offset, 157 | is contained 'inside' the second location.""" 158 | from sixtypical.analyzer import RangeExceededError 159 | 160 | assert isinstance(inside, LocationRef) 161 | assert isinstance(outside, LocationRef) 162 | 163 | # inside should always be meaningful 164 | inside_range = self._range[inside] 165 | 166 | # outside might not be meaningful, so default to max range if necessary 167 | if outside in self._range: 168 | outside_range = self._range[outside] 169 | else: 170 | outside_range = self.max_range(outside) 171 | 172 | if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]: 173 | raise RangeExceededError(self.routine, 174 | "Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format( 175 | inside, inside_range, offset, outside, outside_range 176 | ) 177 | ) 178 | 179 | def set_touched(self, *refs): 180 | for ref in refs: 181 | self._touched.add(ref) 182 | # TODO: it might be possible to invalidate the range here 183 | 184 | def set_meaningful(self, *refs): 185 | for ref in refs: 186 | if ref not in self._range: 187 | self._range[ref] = self.max_range(ref) 188 | 189 | def set_top_of_range(self, ref, top): 190 | self.assert_meaningful(ref) 191 | (bottom, _) = self._range[ref] 192 | self._range[ref] = (bottom, top) 193 | 194 | def set_bottom_of_range(self, ref, bottom): 195 | self.assert_meaningful(ref) 196 | (top, _) = self._range[ref] 197 | self._range[ref] = (bottom, top) 198 | 199 | def set_range(self, ref, bottom, top): 200 | self.assert_meaningful(ref) 201 | self._range[ref] = (bottom, top) 202 | 203 | def get_top_of_range(self, ref): 204 | if isinstance(ref, ConstantRef): 205 | return ref.value 206 | self.assert_meaningful(ref) 207 | (_, top) = self._range[ref] 208 | return top 209 | 210 | def get_bottom_of_range(self, ref): 211 | if isinstance(ref, ConstantRef): 212 | return ref.value 213 | self.assert_meaningful(ref) 214 | (bottom, _) = self._range[ref] 215 | return bottom 216 | 217 | def get_range(self, ref): 218 | if isinstance(ref, ConstantRef): 219 | return (ref.value, ref.value) 220 | self.assert_meaningful(ref) 221 | (bottom, top) = self._range[ref] 222 | return bottom, top 223 | 224 | def copy_range(self, src, dest): 225 | self.assert_meaningful(src) 226 | if src in self._range: 227 | src_range = self._range[src] 228 | else: 229 | src_range = self.max_range(src) 230 | self._range[dest] = src_range 231 | 232 | def invalidate_range(self, ref): 233 | self.assert_meaningful(ref) 234 | self._range[ref] = self.max_range(ref) 235 | 236 | def set_unmeaningful(self, *refs): 237 | for ref in refs: 238 | if ref in self._range: 239 | del self._range[ref] 240 | 241 | def set_written(self, *refs): 242 | """A "helper" method which does the following common sequence for 243 | the given refs: asserts they're all writable, and sets them all 244 | as touched and meaningful.""" 245 | self.assert_writeable(*refs) 246 | self.set_touched(*refs) 247 | self.set_meaningful(*refs) 248 | 249 | def set_unwriteable(self, *refs): 250 | """Intended to be used for implementing analyzing `for`.""" 251 | for ref in refs: 252 | self._writeable.remove(ref) 253 | 254 | def set_writeable(self, *refs): 255 | """Intended to be used for implementing analyzing `for`, but also used in `save`.""" 256 | for ref in refs: 257 | self._writeable.add(ref) 258 | 259 | def encounter_gotos(self, gotos): 260 | self._gotos_encountered |= gotos 261 | 262 | def encountered_gotos(self): 263 | return self._gotos_encountered 264 | 265 | def set_terminated(self): 266 | # Having a terminated context and having encountered gotos is not the same thing. 267 | self._terminated = True 268 | 269 | def has_terminated(self): 270 | return self._terminated 271 | 272 | def extract(self, location): 273 | """Sets the given location as writeable in the context, and returns a 'baton' representing 274 | the previous state of context for that location. This 'baton' can be used to later restore 275 | this state of context.""" 276 | # Used in `save`. 277 | baton = ( 278 | location, 279 | location in self._touched, 280 | self._range.get(location, None), 281 | location in self._writeable, 282 | ) 283 | self.set_writeable(location) 284 | return baton 285 | 286 | def re_introduce(self, baton): 287 | """Given a 'baton' produced by `extract()`, restores the context for that saved location 288 | to what it was before `extract()` was called.""" 289 | # Used in `save`. 290 | location, was_touched, was_range, was_writeable = baton 291 | 292 | if was_touched: 293 | self._touched.add(location) 294 | elif location in self._touched: 295 | self._touched.remove(location) 296 | 297 | if was_range is not None: 298 | self._range[location] = was_range 299 | elif location in self._range: 300 | del self._range[location] 301 | 302 | if was_writeable: 303 | self._writeable.add(location) 304 | elif location in self._writeable: 305 | self._writeable.remove(location) 306 | 307 | def get_assoc(self, pointer): 308 | return self._pointer_assoc.get(pointer) 309 | 310 | def set_assoc(self, pointer, table): 311 | self._pointer_assoc[pointer] = table 312 | 313 | def is_constant(self, ref): 314 | """read-only means that the program cannot change the value 315 | of a location. constant means that the value of the location 316 | will not change during the lifetime of the program.""" 317 | if isinstance(ref, ConstantRef): 318 | return True 319 | if isinstance(ref, (IndirectRef, IndexedRef)): 320 | return False 321 | if isinstance(ref, LocationRef): 322 | type_ = self.symtab.fetch_global_type(ref.name) 323 | return isinstance(type_, RoutineType) 324 | raise NotImplementedError 325 | 326 | def max_range(self, ref): 327 | if isinstance(ref, ConstantRef): 328 | return (ref.value, ref.value) 329 | elif self.symtab.has_local(self.routine.name, ref.name): 330 | return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range 331 | else: 332 | return self.symtab.fetch_global_type(ref.name).max_range 333 | -------------------------------------------------------------------------------- /src/sixtypical/emitter.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | """Binary machine code emitter. Used in SixtyPical to emit 6502 machine code, 6 | but not specific to SixtyPical, or 6502. Not even necessarily machine code - 7 | though some parts are written around the assumptions of 8-bit architectures.""" 8 | 9 | 10 | class Emittable(object): 11 | def size(self): 12 | raise NotImplementedError 13 | 14 | def serialize(self, addr): 15 | """Should return an array of unsigned bytes (integers from 0 to 255.) 16 | `addr` is the address the value is being serialized at; for most objects 17 | it makes no difference, but some objects (like relative branches) do care.""" 18 | raise NotImplementedError 19 | 20 | 21 | class Byte(Emittable): 22 | def __init__(self, value): 23 | if isinstance(value, str): 24 | value = ord(value) 25 | if value < -127 or value > 255: 26 | raise IndexError(value) 27 | if value < 0: 28 | value += 256 29 | self.value = value 30 | 31 | def size(self): 32 | return 1 33 | 34 | def serialize(self, addr): 35 | return [self.value] 36 | 37 | def __repr__(self): 38 | return "%s(%r)" % (self.__class__.__name__, self.value) 39 | 40 | 41 | class Word(Emittable): 42 | def __init__(self, value): 43 | # TODO: range-checking 44 | self.value = value 45 | 46 | def size(self): 47 | return 2 48 | 49 | def serialize(self, addr): 50 | word = self.value 51 | low = word & 255 52 | high = (word >> 8) & 255 53 | return [low, high] 54 | 55 | def __repr__(self): 56 | return "%s(%r)" % (self.__class__.__name__, self.value) 57 | 58 | 59 | class Table(Emittable): 60 | def __init__(self, value, size): 61 | """`value` should be an iterable of Emittables.""" 62 | # TODO: range-checking 63 | self.value = value 64 | self._size = size 65 | 66 | def size(self): 67 | return self._size 68 | 69 | def serialize(self, addr): 70 | buf = [] 71 | for emittable in self.value: 72 | buf.extend(emittable.serialize(addr)) # FIXME: addr + offset 73 | while len(buf) < self.size(): 74 | buf.append(0) 75 | return buf 76 | 77 | def __repr__(self): 78 | return "%s()" % (self.__class__.__name__) 79 | 80 | 81 | class Label(Emittable): 82 | def __init__(self, name, addr=None, length=None): 83 | self.name = name 84 | self.addr = addr 85 | self.length = length 86 | 87 | def set_addr(self, addr): 88 | self.addr = addr 89 | 90 | def set_length(self, length): 91 | self.length = length 92 | 93 | def size(self): 94 | return 2 95 | 96 | def serialize(self, addr, offset=0): 97 | assert self.addr is not None, "unresolved label: %s" % self.name 98 | return Word(self.addr + offset).serialize(addr) 99 | 100 | def serialize_relative_to(self, addr): 101 | assert self.addr is not None, "unresolved label: %s" % self.name 102 | return Byte(self.addr - (addr + 2)).serialize(addr) 103 | 104 | def serialize_as_zero_page(self, addr, offset=0): 105 | assert self.addr is not None, "unresolved label: %s" % self.name 106 | return Byte(self.addr + offset).serialize(addr) 107 | 108 | def __repr__(self): 109 | addr_s = ', addr=%r' % self.addr if self.addr is not None else '' 110 | length_s = ', length=%r' % self.length if self.length is not None else '' 111 | return "%s(%r%s%s)" % (self.__class__.__name__, self.name, addr_s, length_s) 112 | 113 | 114 | class Offset(Emittable): 115 | def __init__(self, label, offset): 116 | assert isinstance(label, Label) 117 | self.label = label 118 | self.offset = offset 119 | 120 | def size(self): 121 | self.label.size() 122 | 123 | def serialize(self, addr): 124 | return self.label.serialize(addr, offset=self.offset) 125 | 126 | def serialize_as_zero_page(self, addr, offset=0): 127 | return self.label.serialize_as_zero_page(addr, offset=self.offset) 128 | 129 | def __repr__(self): 130 | return "%s(%r, %r)" % (self.__class__.__name__, self.label, self.offset) 131 | 132 | 133 | class HighAddressByte(Emittable): 134 | def __init__(self, label): 135 | assert isinstance(label, (Label, Offset)) 136 | self.label = label 137 | 138 | def size(self): 139 | return 1 140 | 141 | def serialize(self, addr): 142 | return [self.label.serialize(addr)[0]] 143 | 144 | def __repr__(self): 145 | return "%s(%r)" % (self.__class__.__name__, self.label) 146 | 147 | 148 | class LowAddressByte(Emittable): 149 | def __init__(self, label): 150 | assert isinstance(label, (Label, Offset)) 151 | self.label = label 152 | 153 | def size(self): 154 | return 1 155 | 156 | def serialize(self, addr): 157 | return [self.label.serialize(addr)[1]] 158 | 159 | def __repr__(self): 160 | return "%s(%r)" % (self.__class__.__name__, self.label) 161 | 162 | 163 | # - - - - 164 | 165 | 166 | class Emitter(object): 167 | def __init__(self, addr): 168 | self.accum = [] 169 | self.start_addr = addr 170 | self.addr = addr 171 | self.name_counter = 0 172 | 173 | def emit(self, *things): 174 | for thing in things: 175 | self.accum.append(thing) 176 | self.addr += thing.size() 177 | 178 | def get_tail(self): 179 | if self.accum: 180 | return self.accum[-1] 181 | else: 182 | return None 183 | 184 | def retract(self): 185 | thing = self.accum.pop() 186 | self.addr -= thing.size() 187 | 188 | def serialize_to(self, stream): 189 | """`stream` should be a file opened in binary mode.""" 190 | addr = self.start_addr 191 | for emittable in self.accum: 192 | chunk = emittable.serialize(addr) 193 | stream.write(bytearray(chunk)) 194 | addr += len(chunk) 195 | 196 | def make_label(self, name=None): 197 | if name is None: 198 | name = 'label' + str(self.name_counter) 199 | self.name_counter += 1 200 | return Label(name, addr=self.addr) 201 | 202 | def resolve_label(self, label): 203 | label.set_addr(self.addr) 204 | 205 | def resolve_bss_label(self, label): 206 | """Set the given label to be at the current address and 207 | advance the address for the next label, but don't emit anything.""" 208 | self.resolve_label(label) 209 | self.addr += label.length 210 | 211 | def size(self): 212 | return sum(emittable.size() for emittable in self.accum) 213 | 214 | def pad_to_size(self, size): 215 | self_size = self.size() 216 | if self_size > size: 217 | raise IndexError("Emitter size {} exceeds pad size {}".format(self_size, size)) 218 | num_bytes = size - self_size 219 | if num_bytes > 0: 220 | self.accum.extend([Byte(0)] * num_bytes) 221 | -------------------------------------------------------------------------------- /src/sixtypical/fallthru.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | # encoding: UTF-8 6 | 7 | from copy import copy 8 | 9 | from sixtypical.model import RoutineType 10 | 11 | 12 | class FallthruAnalyzer(object): 13 | 14 | def __init__(self, symtab, debug=False): 15 | self.symtab = symtab 16 | self.debug = debug 17 | 18 | def analyze_program(self, program): 19 | self.program = program 20 | 21 | self.fallthru_map = {} 22 | for routine in program.routines: 23 | encountered_gotos = list(routine.encountered_gotos) 24 | if len(encountered_gotos) == 1 and isinstance(self.symtab.fetch_global_type(encountered_gotos[0].name), RoutineType): 25 | self.fallthru_map[routine.name] = encountered_gotos[0].name 26 | else: 27 | self.fallthru_map[routine.name] = None 28 | 29 | def find_chain(self, routine_name, available): 30 | chain = [routine_name] 31 | seen = set(chain) 32 | while True: 33 | next = self.fallthru_map.get(routine_name) 34 | if next is None or next in seen or next not in available: 35 | return chain 36 | seen.add(next) 37 | chain.append(next) 38 | routine_name = next 39 | 40 | def serialize(self): 41 | pending_routines = copy(self.fallthru_map) 42 | roster = [] 43 | 44 | main_chain = self.find_chain('main', pending_routines) 45 | roster.append(main_chain) 46 | for k in main_chain: 47 | del pending_routines[k] 48 | 49 | while pending_routines: 50 | chains = [self.find_chain(k, pending_routines) for k in pending_routines.keys()] 51 | chains.sort(key=lambda x: (len(x), str(x)), reverse=True) 52 | c = chains[0] 53 | roster.append(c) 54 | for k in c: 55 | del pending_routines[k] 56 | 57 | return roster 58 | -------------------------------------------------------------------------------- /src/sixtypical/gen6502.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | """Emittables for 6502 machine code.""" 6 | 7 | from sixtypical.emitter import Emittable, Byte, Label, Offset, LowAddressByte, HighAddressByte 8 | 9 | 10 | class AddressingMode(Emittable): 11 | def size(self): 12 | """Size of the operand for the mode (not including the opcode)""" 13 | raise NotImplementedError 14 | 15 | def serialize(self, addr): 16 | raise NotImplementedError 17 | 18 | def __repr__(self): 19 | return "%s(%r)" % (self.__class__.__name__, self.value) 20 | 21 | 22 | class Implied(AddressingMode): 23 | def size(self): 24 | return 0 25 | 26 | def serialize(self, addr): 27 | return [] 28 | 29 | def __repr__(self): 30 | return "%s()" % (self.__class__.__name__) 31 | 32 | 33 | class Immediate(AddressingMode): 34 | def __init__(self, value): 35 | assert isinstance(value, (Byte, LowAddressByte, HighAddressByte)) 36 | self.value = value 37 | 38 | def size(self): 39 | return 1 40 | 41 | def serialize(self, addr): 42 | return self.value.serialize(addr) 43 | 44 | 45 | class Absolute(AddressingMode): 46 | def __init__(self, value): 47 | assert isinstance(value, (Label, Offset)) 48 | self.value = value 49 | 50 | def size(self): 51 | return 2 52 | 53 | def serialize(self, addr): 54 | return self.value.serialize(addr) 55 | 56 | 57 | class AbsoluteX(Absolute): 58 | pass 59 | 60 | 61 | class AbsoluteY(Absolute): 62 | pass 63 | 64 | 65 | class ZeroPage(AddressingMode): 66 | def __init__(self, value): 67 | assert isinstance(value, (Label, Offset)) 68 | self.value = value 69 | 70 | def size(self): 71 | return 1 72 | 73 | def serialize(self, addr): 74 | return self.value.serialize_as_zero_page(addr) 75 | 76 | 77 | class Indirect(AddressingMode): 78 | def __init__(self, value): 79 | assert isinstance(value, Label) 80 | self.value = value 81 | 82 | def size(self): 83 | return 2 84 | 85 | def serialize(self, addr): 86 | return self.value.serialize(addr) 87 | 88 | 89 | class IndirectY(ZeroPage): 90 | pass 91 | 92 | 93 | class Relative(AddressingMode): 94 | def __init__(self, value): 95 | assert isinstance(value, Label) 96 | self.value = value 97 | 98 | def size(self): 99 | return 1 100 | 101 | def serialize(self, addr): 102 | return self.value.serialize_relative_to(addr) 103 | 104 | 105 | # - - - - 106 | 107 | 108 | class Instruction(Emittable): 109 | def __init__(self, operand=None): 110 | self.operand = operand or Implied() 111 | 112 | def size(self): 113 | return 1 + self.operand.size() if self.operand else 0 114 | 115 | def serialize(self, addr): 116 | return [self.opcodes[self.operand.__class__]] + self.operand.serialize(addr) 117 | 118 | def __repr__(self): 119 | return "%s(%r)" % (self.__class__.__name__, self.operand) 120 | 121 | 122 | class ADC(Instruction): 123 | opcodes = { 124 | Immediate: 0x69, 125 | Absolute: 0x6d, 126 | AbsoluteX: 0x7d, 127 | AbsoluteY: 0x79, 128 | } 129 | 130 | 131 | class AND(Instruction): 132 | opcodes = { 133 | Immediate: 0x29, 134 | Absolute: 0x2d, 135 | AbsoluteX: 0x3d, 136 | AbsoluteY: 0x39, 137 | ZeroPage: 0x25, 138 | } 139 | 140 | 141 | class BCC(Instruction): 142 | opcodes = { 143 | Relative: 0x90, 144 | } 145 | 146 | 147 | class BCS(Instruction): 148 | opcodes = { 149 | Relative: 0xb0, 150 | } 151 | 152 | 153 | class BEQ(Instruction): 154 | opcodes = { 155 | Relative: 0xf0, 156 | } 157 | 158 | 159 | class BNE(Instruction): 160 | opcodes = { 161 | Relative: 0xd0, 162 | } 163 | 164 | 165 | class BPL(Instruction): 166 | opcodes = { 167 | Relative: 0x10, 168 | } 169 | 170 | 171 | class BMI(Instruction): 172 | opcodes = { 173 | Relative: 0x30, 174 | } 175 | 176 | 177 | class CLC(Instruction): 178 | opcodes = { 179 | Implied: 0x18 180 | } 181 | 182 | 183 | class CLI(Instruction): 184 | opcodes = { 185 | Implied: 0x58, 186 | } 187 | 188 | 189 | class CMP(Instruction): 190 | opcodes = { 191 | Immediate: 0xc9, 192 | Absolute: 0xcd, 193 | AbsoluteX: 0xdd, 194 | AbsoluteY: 0xd9, 195 | } 196 | 197 | 198 | class CPX(Instruction): 199 | opcodes = { 200 | Immediate: 0xe0, 201 | Absolute: 0xec, 202 | } 203 | 204 | 205 | class CPY(Instruction): 206 | opcodes = { 207 | Immediate: 0xc0, 208 | Absolute: 0xcc, 209 | } 210 | 211 | 212 | class DEC(Instruction): 213 | opcodes = { 214 | Absolute: 0xce, 215 | AbsoluteX: 0xde, 216 | } 217 | 218 | 219 | class DEX(Instruction): 220 | opcodes = { 221 | Implied: 0xca, 222 | } 223 | 224 | 225 | class DEY(Instruction): 226 | opcodes = { 227 | Implied: 0x88, 228 | } 229 | 230 | 231 | class EOR(Instruction): 232 | opcodes = { 233 | Immediate: 0x49, 234 | Absolute: 0x4d, 235 | AbsoluteX: 0x5d, 236 | AbsoluteY: 0x59, 237 | ZeroPage: 0x45, 238 | } 239 | 240 | 241 | class INC(Instruction): 242 | opcodes = { 243 | Absolute: 0xee, 244 | AbsoluteX: 0xfe, 245 | } 246 | 247 | 248 | class INX(Instruction): 249 | opcodes = { 250 | Implied: 0xe8, 251 | } 252 | 253 | 254 | class INY(Instruction): 255 | opcodes = { 256 | Implied: 0xc8, 257 | } 258 | 259 | 260 | class JMP(Instruction): 261 | opcodes = { 262 | Absolute: 0x4c, 263 | Indirect: 0x6c, 264 | } 265 | 266 | 267 | class JSR(Instruction): 268 | opcodes = { 269 | Absolute: 0x20, 270 | } 271 | 272 | 273 | class LDA(Instruction): 274 | opcodes = { 275 | Immediate: 0xa9, 276 | Absolute: 0xad, 277 | AbsoluteX: 0xbd, 278 | AbsoluteY: 0xb9, 279 | IndirectY: 0xb1, 280 | ZeroPage: 0xa5, 281 | } 282 | 283 | 284 | class LDX(Instruction): 285 | opcodes = { 286 | Immediate: 0xa2, 287 | Absolute: 0xae, 288 | AbsoluteY: 0xbe, 289 | } 290 | 291 | 292 | class LDY(Instruction): 293 | opcodes = { 294 | Immediate: 0xa0, 295 | Absolute: 0xac, 296 | AbsoluteX: 0xbc, 297 | } 298 | 299 | 300 | class ORA(Instruction): 301 | opcodes = { 302 | Immediate: 0x09, 303 | Absolute: 0x0d, 304 | AbsoluteX: 0x1d, 305 | AbsoluteY: 0x19, 306 | ZeroPage: 0x05, 307 | } 308 | 309 | 310 | class PHA(Instruction): 311 | opcodes = { 312 | Implied: 0x48, 313 | } 314 | 315 | 316 | class PLA(Instruction): 317 | opcodes = { 318 | Implied: 0x68, 319 | } 320 | 321 | 322 | class ROL(Instruction): 323 | opcodes = { 324 | Implied: 0x2a, # Accumulator 325 | Absolute: 0x2e, 326 | AbsoluteX: 0x3e, 327 | } 328 | 329 | 330 | class ROR(Instruction): 331 | opcodes = { 332 | Implied: 0x6a, # Accumulator 333 | Absolute: 0x6e, 334 | AbsoluteX: 0x7e, 335 | } 336 | 337 | 338 | class RTS(Instruction): 339 | opcodes = { 340 | Implied: 0x60, 341 | } 342 | 343 | 344 | class NOP(Instruction): 345 | opcodes = { 346 | Implied: 0xEA, 347 | } 348 | 349 | 350 | class SBC(Instruction): 351 | opcodes = { 352 | Immediate: 0xe9, 353 | Absolute: 0xed, 354 | AbsoluteX: 0xfd, 355 | AbsoluteY: 0xf9, 356 | } 357 | 358 | 359 | class SEC(Instruction): 360 | opcodes = { 361 | Implied: 0x38, 362 | } 363 | 364 | 365 | class SEI(Instruction): 366 | opcodes = { 367 | Implied: 0x78, 368 | } 369 | 370 | 371 | class STA(Instruction): 372 | opcodes = { 373 | Absolute: 0x8d, 374 | AbsoluteX: 0x9d, 375 | AbsoluteY: 0x99, 376 | IndirectY: 0x91, 377 | ZeroPage: 0x85, 378 | } 379 | 380 | 381 | class STX(Instruction): 382 | opcodes = { 383 | Absolute: 0x8e, 384 | } 385 | 386 | 387 | class STY(Instruction): 388 | opcodes = { 389 | Absolute: 0x8c, 390 | } 391 | 392 | 393 | class TAX(Instruction): 394 | opcodes = { 395 | Implied: 0xaa, 396 | } 397 | 398 | 399 | class TAY(Instruction): 400 | opcodes = { 401 | Implied: 0xa8, 402 | } 403 | 404 | 405 | class TXA(Instruction): 406 | opcodes = { 407 | Implied: 0x8a, 408 | } 409 | 410 | 411 | class TYA(Instruction): 412 | opcodes = { 413 | Implied: 0x98, 414 | } 415 | -------------------------------------------------------------------------------- /src/sixtypical/model.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | """Data/storage model for SixtyPical.""" 6 | 7 | from collections import namedtuple 8 | 9 | 10 | class BitType(namedtuple('BitType', ['typename'])): 11 | max_range = (0, 1) 12 | def __new__(cls): 13 | return super(BitType, cls).__new__(cls, 'bit') 14 | TYPE_BIT = BitType() 15 | 16 | 17 | class ByteType(namedtuple('ByteType', ['typename'])): 18 | max_range = (0, 255) 19 | def __new__(cls): 20 | return super(ByteType, cls).__new__(cls, 'byte') 21 | TYPE_BYTE = ByteType() 22 | 23 | 24 | class WordType(namedtuple('WordType', ['typename'])): 25 | max_range = (0, 65535) 26 | def __new__(cls): 27 | return super(WordType, cls).__new__(cls, 'word') 28 | TYPE_WORD = WordType() 29 | 30 | 31 | class RoutineType(namedtuple('RoutineType', ['typename', 'inputs', 'outputs', 'trashes'])): 32 | """This memory location contains the code for a routine.""" 33 | max_range = (0, 0) 34 | 35 | def __new__(cls, *args): 36 | return super(RoutineType, cls).__new__(cls, 'routine', *args) 37 | 38 | @classmethod 39 | def executable_types_compatible(cls_, src, dest): 40 | """Returns True iff a value of type `src` can be assigned to a storage location of type `dest`.""" 41 | if isinstance(src, VectorType): 42 | src = src.of_type 43 | if isinstance(dest, VectorType): 44 | dest = dest.of_type 45 | if isinstance(src, RoutineType) and isinstance(dest, RoutineType): 46 | # TODO: I'm sure we can replace some of these with subset-containment, but that requires thought 47 | return ( 48 | src.inputs == dest.inputs and 49 | src.outputs == dest.outputs and 50 | src.trashes == dest.trashes 51 | ) 52 | else: 53 | return False 54 | 55 | 56 | class VectorType(namedtuple('VectorType', ['typename', 'of_type'])): 57 | """This memory location contains the address of some other type (currently, only RoutineType).""" 58 | max_range = (0, 65535) 59 | 60 | def __new__(cls, *args): 61 | return super(VectorType, cls).__new__(cls, 'vector', *args) 62 | 63 | 64 | class TableType(namedtuple('TableType', ['typename', 'of_type', 'size'])): 65 | 66 | def __new__(cls, *args): 67 | return super(TableType, cls).__new__(cls, 'table', *args) 68 | 69 | @property 70 | def max_range(self): 71 | return (0, self.size - 1) 72 | 73 | @classmethod 74 | def is_a_table_type(cls_, x, of_type): 75 | return isinstance(x, TableType) and x.of_type == of_type 76 | 77 | 78 | class PointerType(namedtuple('PointerType', ['typename'])): 79 | max_range = (0, 65535) 80 | 81 | def __new__(cls): 82 | return super(PointerType, cls).__new__(cls, 'pointer') 83 | 84 | 85 | # -------------------------------------------------------- 86 | 87 | 88 | class LocationRef(namedtuple('LocationRef', ['reftype', 'name'])): 89 | def __new__(cls, *args): 90 | return super(LocationRef, cls).__new__(cls, 'location', *args) 91 | 92 | @classmethod 93 | def format_set(cls, location_refs): 94 | return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs, key=lambda x: x.name)]) 95 | 96 | 97 | class IndirectRef(namedtuple('IndirectRef', ['reftype', 'ref'])): 98 | def __new__(cls, *args): 99 | return super(IndirectRef, cls).__new__(cls, 'indirect', *args) 100 | 101 | @property 102 | def name(self): 103 | return '[{}]+y'.format(self.ref.name) 104 | 105 | 106 | class IndexedRef(namedtuple('IndexedRef', ['reftype', 'ref', 'offset', 'index'])): 107 | def __new__(cls, *args): 108 | return super(IndexedRef, cls).__new__(cls, 'indexed', *args) 109 | 110 | @property 111 | def name(self): 112 | return '{}+{}+{}'.format(self.ref.name, self.offset, self.index.name) 113 | 114 | 115 | class ConstantRef(namedtuple('ConstantRef', ['reftype', 'type', 'value'])): 116 | def __new__(cls, *args): 117 | return super(ConstantRef, cls).__new__(cls, 'constant', *args) 118 | 119 | def high_byte(self): 120 | return (self.value >> 8) & 255 121 | 122 | def low_byte(self): 123 | return self.value & 255 124 | 125 | def pred(self): 126 | assert self.type == TYPE_BYTE 127 | value = self.value - 1 128 | while value < 0: 129 | value += 256 130 | return ConstantRef(self.type, value) 131 | 132 | def succ(self): 133 | assert self.type == TYPE_BYTE 134 | value = self.value + 1 135 | while value > 255: 136 | value -= 256 137 | return ConstantRef(self.type, value) 138 | 139 | @property 140 | def name(self): 141 | return 'constant({})'.format(self.value) 142 | 143 | 144 | REG_A = LocationRef('a') 145 | REG_X = LocationRef('x') 146 | REG_Y = LocationRef('y') 147 | 148 | FLAG_Z = LocationRef('z') 149 | FLAG_C = LocationRef('c') 150 | FLAG_N = LocationRef('n') 151 | FLAG_V = LocationRef('v') 152 | -------------------------------------------------------------------------------- /src/sixtypical/outputter.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | """Executable file writer.""" 6 | 7 | from sixtypical.emitter import Emitter, Byte, Word 8 | 9 | 10 | class Outputter(object): 11 | def __init__(self, fh, start_addr=None): 12 | self.start_addr = self.__class__.start_addr 13 | if start_addr is not None: 14 | self.start_addr = start_addr 15 | self.prelude = self.__class__.prelude 16 | self.fh = fh 17 | self.emitter = Emitter(self.start_addr) 18 | 19 | def write_header(self): 20 | pass 21 | 22 | def write_prelude(self): 23 | self.write_header() 24 | for byte in self.prelude: 25 | self.emitter.emit(Byte(byte)) 26 | 27 | def write_postlude(self): 28 | pass 29 | 30 | 31 | class RawOutputter(Outputter): 32 | start_addr = 0x0000 33 | prelude = [] 34 | 35 | 36 | class PrgOutputter(Outputter): 37 | start_addr = 0xc000 38 | prelude = [] 39 | 40 | def write_header(self): 41 | # If we are outputting a .PRG, we output the load address first. 42 | # We don't use the Emitter for this b/c not part of addr space. 43 | self.fh.write(bytearray(Word(self.start_addr).serialize(0))) 44 | 45 | 46 | class C64BasicPrgOutputter(PrgOutputter): 47 | start_addr = 0x0801 48 | prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32, 49 | 0x30, 0x36, 0x31, 0x00, 0x00, 0x00] 50 | 51 | 52 | class Vic20BasicPrgOutputter(PrgOutputter): 53 | start_addr = 0x1001 54 | prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34, 55 | 0x31, 0x30, 0x39, 0x00, 0x00, 0x00] 56 | 57 | 58 | class Atari2600CartOutputter(Outputter): 59 | start_addr = 0xf000 60 | prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9, 61 | 0x00, 0x95, 0x00, 0xca, 0xd0, 0xfb] 62 | 63 | def write_postlude(self): 64 | # If we are outputting a cartridge with boot and BRK address 65 | # at the end, pad to ROM size minus 4 bytes, and emit addresses. 66 | self.emitter.pad_to_size(4096 - 4) 67 | self.emitter.emit(Word(self.start_addr)) 68 | self.emitter.emit(Word(self.start_addr)) 69 | 70 | 71 | def outputter_class_for(output_format): 72 | return { 73 | 'raw': RawOutputter, 74 | 'prg': PrgOutputter, 75 | 'c64-basic-prg': C64BasicPrgOutputter, 76 | 'vic20-basic-prg': Vic20BasicPrgOutputter, 77 | 'atari2600-cart': Atari2600CartOutputter, 78 | }[output_format] 79 | -------------------------------------------------------------------------------- /src/sixtypical/scanner.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | # encoding: UTF-8 6 | 7 | import re 8 | 9 | 10 | class SixtyPicalSyntaxError(ValueError): 11 | def __init__(self, filename, line_number, message): 12 | super(SixtyPicalSyntaxError, self).__init__(filename, line_number, message) 13 | 14 | def __str__(self): 15 | return "{}, line {}: {}".format(self.args[0], self.args[1], self.args[2]) 16 | 17 | 18 | class Scanner(object): 19 | def __init__(self, text, filename): 20 | self.text = text 21 | self.filename = filename 22 | self.token = None 23 | self.type = None 24 | self.pos = 0 25 | self.line_number = 1 26 | self.scan() 27 | 28 | def scan_pattern(self, pattern, type, token_group=1): 29 | pattern = r'(' + pattern + r')' 30 | regexp = re.compile(pattern, flags=re.DOTALL) 31 | match = regexp.match(self.text, pos=self.pos) 32 | if not match: 33 | return False 34 | else: 35 | self.type = type 36 | self.token = match.group(token_group) 37 | self.pos += len(match.group(0)) 38 | self.line_number += self.token.count('\n') 39 | return True 40 | 41 | def scan(self): 42 | self.scan_pattern(r'[ \t\n\r]*', 'whitespace') 43 | while self.scan_pattern(r'\/\/.*?[\n\r]', 'comment'): 44 | self.scan_pattern(r'[ \t\n\r]*', 'whitespace') 45 | if self.pos >= len(self.text): 46 | self.token = None 47 | self.type = 'EOF' 48 | return 49 | if self.scan_pattern(r'\,|\@|\+|\:|\<|\>|\{|\}|\[|\]|\^', 'operator'): 50 | return 51 | if self.scan_pattern(r'\d+', 'integer literal'): 52 | return 53 | if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal', token_group=2): 54 | # ecch 55 | self.token = str(eval('0x' + self.token)) 56 | return 57 | if self.scan_pattern(r'\"(.*?)\"', 'string literal', token_group=2): 58 | return 59 | if self.scan_pattern(r'\w+', 'identifier'): 60 | return 61 | if self.scan_pattern(r'.', 'unknown character'): 62 | return 63 | else: 64 | raise AssertionError("this should never happen, self.text=({}), self.pos=({})".format(self.text, self.pos)) 65 | 66 | def expect(self, token): 67 | if self.token == token: 68 | self.scan() 69 | else: 70 | self.syntax_error("Expected '{}', but found '{}'".format(token, self.token)) 71 | 72 | def on(self, *tokens): 73 | return self.token in tokens 74 | 75 | def on_type(self, type): 76 | return self.type == type 77 | 78 | def check_type(self, type): 79 | if not self.type == type: 80 | self.syntax_error("Expected {}, but found '{}'".format(self.type, self.token)) 81 | 82 | def consume(self, token): 83 | if self.token == token: 84 | self.scan() 85 | return True 86 | else: 87 | return False 88 | 89 | def syntax_error(self, msg): 90 | raise SixtyPicalSyntaxError(self.filename, self.line_number, msg) 91 | -------------------------------------------------------------------------------- /src/sixtypical/symtab.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies. 2 | # This file is distributed under a 2-clause BSD license. See LICENSES/ dir. 3 | # SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical 4 | 5 | # encoding: UTF-8 6 | 7 | from sixtypical.model import ( 8 | TYPE_BIT, TYPE_BYTE, LocationRef, 9 | ) 10 | 11 | 12 | class SymEntry(object): 13 | def __init__(self, ast_node, type_): 14 | self.ast_node = ast_node 15 | self.type_ = type_ 16 | 17 | def __repr__(self): 18 | return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_) 19 | 20 | 21 | class SymbolTable(object): 22 | def __init__(self): 23 | self.symbols = {} # symbol name -> SymEntry 24 | self.locals = {} # routine name -> (symbol name -> SymEntry) 25 | self.typedefs = {} # type name -> Type AST 26 | self.consts = {} # const name -> ConstantRef 27 | 28 | for name in ('a', 'x', 'y'): 29 | self.symbols[name] = SymEntry(None, TYPE_BYTE) 30 | for name in ('c', 'z', 'n', 'v'): 31 | self.symbols[name] = SymEntry(None, TYPE_BIT) 32 | 33 | def __str__(self): 34 | return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts) 35 | 36 | def has_local(self, routine_name, name): 37 | return name in self.locals.get(routine_name, {}) 38 | 39 | def fetch_global_type(self, name): 40 | return self.symbols[name].type_ 41 | 42 | def fetch_local_type(self, routine_name, name): 43 | return self.locals[routine_name][name].type_ 44 | 45 | def fetch_global_ref(self, name): 46 | if name in self.symbols: 47 | return LocationRef(name) 48 | return None 49 | 50 | def fetch_local_ref(self, routine_name, name): 51 | routine_locals = self.locals.get(routine_name, {}) 52 | if name in routine_locals: 53 | return LocationRef(name) 54 | return None 55 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This currently represents a lot of tests! If you only want to run a subset, 4 | # it's probably best to run `falderal` manually on the file(s) you want to test. 5 | # Note also that the `sixtypical-py2.7.md` appliance, in the same directory as 6 | # `sixtypical.md`, can be used to run the tests under Python 2.7. 7 | 8 | falderal --substring-error \ 9 | "tests/appliances/sixtypical.md" \ 10 | "tests/SixtyPical Syntax.md" \ 11 | "tests/SixtyPical Analysis.md" \ 12 | "tests/SixtyPical Storage.md" \ 13 | "tests/SixtyPical Control Flow.md" \ 14 | "tests/SixtyPical Fallthru.md" \ 15 | "tests/SixtyPical Callgraph.md" \ 16 | "tests/SixtyPical Compilation.md" 17 | -------------------------------------------------------------------------------- /tests/SixtyPical Callgraph.md: -------------------------------------------------------------------------------- 1 | SixtyPical Callgraph 2 | ==================== 3 | 4 | 9 | 10 | This is a test suite, written in [Falderal][] format, for the ability of 11 | a SixtyPical analyzer to construct a callgraph of which routines call which 12 | other routines, and its ability to discover which routines will never be 13 | called. 14 | 15 | [Falderal]: http://catseye.tc/node/Falderal 16 | 17 | -> Tests for functionality "Dump callgraph info for SixtyPical program" 18 | 19 | The `main` routine is always called. The thing that it will 20 | be called by is the system, but the callgraph analyzer simply 21 | considers it to be "reachable". 22 | 23 | | define main routine 24 | | { 25 | | } 26 | = { 27 | = "main": { 28 | = "potentially-called-by": [], 29 | = "potentially-calls": [], 30 | = "reachable": true 31 | = } 32 | = } 33 | 34 | If a routine is called by another routine, this fact will be noted. 35 | If it is reachable (directly or indirectly) from `main`, this will 36 | be noted as well. 37 | 38 | | define main routine 39 | | { 40 | | call other 41 | | } 42 | | 43 | | define other routine 44 | | { 45 | | } 46 | = { 47 | = "main": { 48 | = "potentially-called-by": [], 49 | = "potentially-calls": [ 50 | = "other" 51 | = ], 52 | = "reachable": true 53 | = }, 54 | = "other": { 55 | = "potentially-called-by": [ 56 | = "main" 57 | = ], 58 | = "potentially-calls": [], 59 | = "reachable": true 60 | = } 61 | = } 62 | 63 | If a routine is not potentially called by any other routine that is 64 | ultimately potentially called by `main`, this absence will be noted 65 | — the routine will not be considered reachable — and a compiler or 66 | linker will be permitted to omit it from the final executable. 67 | 68 | | define main routine 69 | | { 70 | | } 71 | | 72 | | define other routine 73 | | { 74 | | } 75 | = { 76 | = "main": { 77 | = "potentially-called-by": [], 78 | = "potentially-calls": [], 79 | = "reachable": true 80 | = }, 81 | = "other": { 82 | = "potentially-called-by": [], 83 | = "potentially-calls": [] 84 | = } 85 | = } 86 | 87 | If a routine is not called by another routine, but it is declared 88 | explicitly as `preserved`, then it will still be considered 89 | reachable, and a compiler or linker will not be permitted to omit it 90 | from the final executable. This is useful for interrupt routines 91 | and such that really are used by some part of the system, even if 92 | not directly by another SixtyPical routine. 93 | 94 | | define main routine 95 | | { 96 | | } 97 | | 98 | | define other preserved routine 99 | | { 100 | | } 101 | = { 102 | = "main": { 103 | = "potentially-called-by": [], 104 | = "potentially-calls": [], 105 | = "reachable": true 106 | = }, 107 | = "other": { 108 | = "potentially-called-by": [], 109 | = "potentially-calls": [], 110 | = "reachable": true 111 | = } 112 | = } 113 | 114 | If a routine is called from a preserved routine, that routine is 115 | reachable. 116 | 117 | | define main routine 118 | | { 119 | | } 120 | | 121 | | define other1 preserved routine 122 | | { 123 | | call other2 124 | | } 125 | | 126 | | define other2 preserved routine 127 | | { 128 | | } 129 | = { 130 | = "main": { 131 | = "potentially-called-by": [], 132 | = "potentially-calls": [], 133 | = "reachable": true 134 | = }, 135 | = "other1": { 136 | = "potentially-called-by": [], 137 | = "potentially-calls": [ 138 | = "other2" 139 | = ], 140 | = "reachable": true 141 | = }, 142 | = "other2": { 143 | = "potentially-called-by": [ 144 | = "other1" 145 | = ], 146 | = "potentially-calls": [], 147 | = "reachable": true 148 | = } 149 | = } 150 | 151 | If a group of routines potentially call each other, but neither is 152 | found to be reachable (directly or indirectly) from `main` or a 153 | `preserved` routine, the routines in the group will not be considered 154 | reachable. 155 | 156 | | define main routine 157 | | { 158 | | } 159 | | 160 | | define other1 routine 161 | | { 162 | | call other2 163 | | } 164 | | 165 | | define other2 routine 166 | | { 167 | | call other1 168 | | } 169 | = { 170 | = "main": { 171 | = "potentially-called-by": [], 172 | = "potentially-calls": [], 173 | = "reachable": true 174 | = }, 175 | = "other1": { 176 | = "potentially-called-by": [ 177 | = "other2" 178 | = ], 179 | = "potentially-calls": [ 180 | = "other2" 181 | = ] 182 | = }, 183 | = "other2": { 184 | = "potentially-called-by": [ 185 | = "other1" 186 | = ], 187 | = "potentially-calls": [ 188 | = "other1" 189 | = ] 190 | = } 191 | = } 192 | 193 | -> Tests for functionality "Compile SixtyPical program with unreachable routine removal" 194 | 195 | Basic test for actually removing unreachable routines from the resulting 196 | executable when compiling SixtyPical programs. 197 | 198 | | define main routine outputs a trashes z, n 199 | | { 200 | | ld a, 100 201 | | } 202 | | 203 | | define other1 routine 204 | | { 205 | | call other2 206 | | } 207 | | 208 | | define other2 routine 209 | | { 210 | | call other1 211 | | } 212 | = $080D LDA #$64 213 | = $080F RTS 214 | 215 | Test that marking routine as `preserved` preserves it in the output. 216 | 217 | | define main routine outputs a trashes z, n 218 | | { 219 | | ld a, 100 220 | | } 221 | | 222 | | define other preserved routine outputs a trashes z, n 223 | | { 224 | | ld a, 5 225 | | } 226 | = $080D LDA #$64 227 | = $080F RTS 228 | = $0810 LDA #$05 229 | = $0812 RTS 230 | -------------------------------------------------------------------------------- /tests/SixtyPical Fallthru.md: -------------------------------------------------------------------------------- 1 | SixtyPical Fallthru 2 | =================== 3 | 4 | 9 | 10 | This is a test suite, written in [Falderal][] format, for SixtyPical's 11 | ability to detect which routines make tail calls to other routines, 12 | and thus can be re-arranged to simply "fall through" to them. 13 | 14 | The theory is as follows. 15 | 16 | SixtyPical supports a `goto`, but it can only appear in tail position. 17 | If a routine r1 ends with a unique `goto` to a fixed routine r2 it is said 18 | to *potentially fall through* to r2. 19 | 20 | A *unique* `goto` means that there are not multiple different `goto`s in 21 | tail position (which can happen if, for example, an `if` is the last thing 22 | in a routine, and each branch of that `if` ends with a different `goto`.) 23 | 24 | A *fixed* routine means, a routine which is known at compile time, not a 25 | `goto` through a vector. 26 | 27 | Consider the set R of all available routines in the program. 28 | 29 | Every routine either potentially falls through to a single other routine 30 | or it does not potentially fall through to any routine. 31 | 32 | More formally, we can say 33 | 34 | > fall : R → R ∪ {nil}, fall(r) ≠ r 35 | 36 | where `nil` is an atom that represents no routine. 37 | 38 | Now consider an operation chain() vaguely similar to a transitive closure 39 | on fall(). Starting with r, we construct a list of r, fall(r), 40 | fall(fall(r)), ... with the following restrictions: 41 | 42 | - we stop when we reach `nil` (because fall(`nil`) is not defined) 43 | - we stop when we see an element that is not in R. 44 | - we stop when we see an element that we have already added to the 45 | list (this is to prevent infinite lists due to cycles.) 46 | 47 | With these definitions, our algorithm is something like this. 48 | 49 | Treat R as a mutable set and start with an empty list of lists L. Then, 50 | 51 | - For all r ∈ R, find all chain(r). 52 | - Pick a longest such chain. Call it C. 53 | - Append C to L. 54 | - Remove all elements occurring in C, from R. 55 | - Repeat until R is empty. 56 | 57 | When time comes to generate code, generate it in the order given by L. 58 | In addition, each sublist in L represents a number of routines to 59 | generate; all except the final routine in such a sublist need not have 60 | any jump instruction generated for its final `goto`. 61 | 62 | The tests in this document test against the list L. 63 | 64 | Note that this optimization is a feature of the SixtyPical's reference 65 | compiler, not the language. So an implementation is not required 66 | to pass these tests to be considered an implementation of SixtyPical. 67 | 68 | [Falderal]: http://catseye.tc/node/Falderal 69 | 70 | -> Tests for functionality "Dump fallthru info for SixtyPical program" 71 | 72 | A single routine, obviously, falls through to nothing and has nothing fall 73 | through to it. 74 | 75 | | define main routine 76 | | { 77 | | } 78 | = [ 79 | = [ 80 | = "main" 81 | = ] 82 | = ] 83 | 84 | If `main` does a `goto foo`, then it can fall through to `foo`. 85 | 86 | | define foo routine trashes a, z, n 87 | | { 88 | | ld a, 0 89 | | } 90 | | 91 | | define main routine trashes a, z, n 92 | | { 93 | | goto foo 94 | | } 95 | = [ 96 | = [ 97 | = "main", 98 | = "foo" 99 | = ] 100 | = ] 101 | 102 | More than one routine can fall through to a routine. We pick one 103 | of them to fall through, when selecting the order of routines. 104 | 105 | | define foo routine trashes a, z, n 106 | | { 107 | | ld a, 0 108 | | } 109 | | 110 | | define bar routine trashes a, z, n 111 | | { 112 | | ld a, 0 113 | | goto foo 114 | | } 115 | | 116 | | define main routine trashes a, z, n 117 | | { 118 | | goto foo 119 | | } 120 | = [ 121 | = [ 122 | = "main", 123 | = "foo" 124 | = ], 125 | = [ 126 | = "bar" 127 | = ] 128 | = ] 129 | 130 | Because `main` is always serialized first (so that the entry 131 | point of the entire program appears at the beginning of the code), 132 | nothing ever falls through to `main`. 133 | 134 | | define foo routine trashes a, z, n 135 | | { 136 | | ld a, 0 137 | | goto main 138 | | } 139 | | 140 | | define main routine trashes a, z, n 141 | | { 142 | | ld a, 1 143 | | } 144 | = [ 145 | = [ 146 | = "main" 147 | = ], 148 | = [ 149 | = "foo" 150 | = ] 151 | = ] 152 | 153 | There is nothing stopping two routines from tail-calling each 154 | other, but we will only be able to make one of them, at most, 155 | fall through to the other. 156 | 157 | | define foo routine trashes a, z, n 158 | | { 159 | | ld a, 0 160 | | goto bar 161 | | } 162 | | 163 | | define bar routine trashes a, z, n 164 | | { 165 | | ld a, 0 166 | | goto foo 167 | | } 168 | | 169 | | define main routine trashes a, z, n 170 | | { 171 | | } 172 | = [ 173 | = [ 174 | = "main" 175 | = ], 176 | = [ 177 | = "foo", 178 | = "bar" 179 | = ] 180 | = ] 181 | 182 | If a routine does two tail calls (which is possible because they 183 | can be in different branches of an `if`) it cannot fall through to another 184 | routine. 185 | 186 | | define foo routine trashes a, z, n 187 | | { 188 | | ld a, 0 189 | | } 190 | | 191 | | define bar routine trashes a, z, n 192 | | { 193 | | ld a, 0 194 | | } 195 | | 196 | | define main routine inputs z trashes a, z, n 197 | | { 198 | | if z { 199 | | goto foo 200 | | } else { 201 | | goto bar 202 | | } 203 | | } 204 | = [ 205 | = [ 206 | = "main" 207 | = ], 208 | = [ 209 | = "foo" 210 | = ], 211 | = [ 212 | = "bar" 213 | = ] 214 | = ] 215 | 216 | If, however, they are the same goto, one can be optimized away. 217 | 218 | | define foo routine trashes a, z, n 219 | | { 220 | | ld a, 0 221 | | if z { 222 | | ld a, 1 223 | | goto bar 224 | | } else { 225 | | ld a, 2 226 | | goto bar 227 | | } 228 | | } 229 | | 230 | | define bar routine trashes a, z, n 231 | | { 232 | | ld a, 255 233 | | } 234 | | 235 | | define main routine trashes a, z, n 236 | | { 237 | | } 238 | = [ 239 | = [ 240 | = "main" 241 | = ], 242 | = [ 243 | = "foo", 244 | = "bar" 245 | = ] 246 | = ] 247 | 248 | Similarly, a tail call to a vector can't be turned into a fallthru, 249 | because we don't necessarily know what actual routine the vector contains. 250 | 251 | | vector routine trashes a, z, n 252 | | vec 253 | | 254 | | define foo routine trashes a, z, n 255 | | { 256 | | ld a, 0 257 | | } 258 | | 259 | | define bar routine trashes a, z, n 260 | | { 261 | | ld a, 0 262 | | } 263 | | 264 | | define main routine outputs vec trashes a, z, n 265 | | { 266 | | copy bar, vec 267 | | goto vec 268 | | } 269 | = [ 270 | = [ 271 | = "main" 272 | = ], 273 | = [ 274 | = "foo" 275 | = ], 276 | = [ 277 | = "bar" 278 | = ] 279 | = ] 280 | 281 | Our algorithm might not be strictly optimal, but it does a good job. 282 | 283 | | define r1 routine trashes a, z, n 284 | | { 285 | | ld a, 0 286 | | goto r2 287 | | } 288 | | 289 | | define r2 routine trashes a, z, n 290 | | { 291 | | ld a, 0 292 | | goto r3 293 | | } 294 | | 295 | | define r3 routine trashes a, z, n 296 | | { 297 | | ld a, 0 298 | | goto r4 299 | | } 300 | | 301 | | define r4 routine trashes a, z, n 302 | | { 303 | | ld a, 0 304 | | } 305 | | 306 | | define r5 routine trashes a, z, n 307 | | { 308 | | ld a, 0 309 | | goto r6 310 | | } 311 | | 312 | | define r6 routine trashes a, z, n 313 | | { 314 | | ld a, 0 315 | | goto r3 316 | | } 317 | | 318 | | define main routine trashes a, z, n 319 | | { 320 | | goto r1 321 | | } 322 | = [ 323 | = [ 324 | = "main", 325 | = "r1", 326 | = "r2", 327 | = "r3", 328 | = "r4" 329 | = ], 330 | = [ 331 | = "r5", 332 | = "r6" 333 | = ] 334 | = ] 335 | 336 | -> Tests for functionality "Compile SixtyPical program with fallthru optimization" 337 | 338 | Basic test for actually applying this optimization when compiling SixtyPical programs. 339 | 340 | | define foo routine trashes a, z, n 341 | | { 342 | | ld a, 0 343 | | } 344 | | 345 | | define bar routine trashes a, z, n 346 | | { 347 | | ld a, 255 348 | | goto foo 349 | | } 350 | | 351 | | define main routine trashes a, z, n 352 | | { 353 | | goto foo 354 | | } 355 | = $080D LDA #$00 356 | = $080F RTS 357 | = $0810 LDA #$FF 358 | = $0812 JMP $080D 359 | 360 | It can optimize out one of the `goto`s if they are the same. 361 | 362 | | define foo routine trashes a, z, n 363 | | { 364 | | ld a, 0 365 | | if z { 366 | | ld a, 1 367 | | goto bar 368 | | } else { 369 | | ld a, 2 370 | | goto bar 371 | | } 372 | | } 373 | | 374 | | define bar routine trashes a, z, n 375 | | { 376 | | ld a, 255 377 | | } 378 | | 379 | | define main routine trashes a, z, n 380 | | { 381 | | } 382 | = $080D RTS 383 | = $080E LDA #$00 384 | = $0810 BNE $0817 385 | = $0812 LDA #$01 386 | = $0814 JMP $0819 387 | = $0817 LDA #$02 388 | = $0819 LDA #$FF 389 | = $081B RTS 390 | 391 | It cannot optimize out the `goto`s if they are different. 392 | 393 | | define foo routine trashes a, z, n 394 | | { 395 | | ld a, 0 396 | | if z { 397 | | ld a, 1 398 | | goto bar 399 | | } else { 400 | | ld a, 2 401 | | goto main 402 | | } 403 | | } 404 | | 405 | | define bar routine trashes a, z, n 406 | | { 407 | | ld a, 255 408 | | } 409 | | 410 | | define main routine trashes a, z, n 411 | | { 412 | | } 413 | = $080D RTS 414 | = $080E LDA #$00 415 | = $0810 BNE $0817 416 | = $0812 LDA #$01 417 | = $0814 JMP $081C 418 | = $0817 LDA #$02 419 | = $0819 JMP $080D 420 | = $081C LDA #$FF 421 | = $081E RTS 422 | -------------------------------------------------------------------------------- /tests/appliances/bin/dcc6502-adapter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # script that allows the binary output of sixtypical --output-format=c64-basic-prg --compile to be 4 | # disassembled by https://github.com/tcarmelveilleux/dcc6502 5 | 6 | import sys 7 | import re 8 | from subprocess import check_output 9 | from tempfile import NamedTemporaryFile 10 | 11 | try: 12 | bytes = sys.stdin.buffer.read() 13 | except AttributeError: 14 | bytes = sys.stdin.read() 15 | 16 | bytes = bytes[14:] 17 | 18 | f = NamedTemporaryFile(delete=False) 19 | filename = f.name 20 | f.write(bytes) 21 | f.close() 22 | 23 | output = check_output("dcc6502 -o 2061 {}".format(filename), shell=True) 24 | output_lines = output.decode('utf-8').split('\n') 25 | lines = [line for line in output_lines if line and not line.startswith(';')] 26 | lines = [re.sub(r'\s*\;.*$', '', line) for line in lines] 27 | sys.stdout.write('\n'.join(lines)) 28 | -------------------------------------------------------------------------------- /tests/appliances/sixtypical-py2.7.md: -------------------------------------------------------------------------------- 1 | This file contains only the [Falderal][] directives that define the different 2 | functionalities tested by the test suite, assuming that it's the reference 3 | implementation, `sixtypical`, that is going to implement these functionalities, 4 | and additionally that `sixtypical` is running under Python 2.7. 5 | 6 | NOTE that this is not well-supported anymore, given that Python 2.7 is past 7 | end-of-life. 8 | 9 | [Falderal]: http://catseye.tc/node/Falderal 10 | 11 | -> Functionality "Check syntax of SixtyPical program" is implemented by 12 | -> shell command "python2.7 bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok" 13 | 14 | -> Functionality "Analyze SixtyPical program" is implemented by 15 | -> shell command "python2.7 bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok" 16 | 17 | -> Functionality "Compile SixtyPical program" is implemented by 18 | -> shell command "python2.7 bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && python2.7 tests/appliances/bin/dcc6502-adapter Functionality "Dump callgraph info for SixtyPical program" is implemented by 21 | -> shell command "python2.7 bin/sixtypical --dump-callgraph --analyze-only --traceback %(test-body-file)" 22 | 23 | -> Functionality "Compile SixtyPical program with unreachable routine removal" is implemented by 24 | -> shell command "python2.7 bin/sixtypical --output-format=c64-basic-prg --prune-unreachable-routines --traceback %(test-body-file) --output /tmp/foo && python2.7 tests/appliances/bin/dcc6502-adapter Functionality "Dump fallthru info for SixtyPical program" is implemented by 27 | -> shell command "python2.7 bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)" 28 | 29 | -> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by 30 | -> shell command "python2.7 bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && python2.7 tests/appliances/bin/dcc6502-adapter Functionality "Check syntax of SixtyPical program" is implemented by 8 | -> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok" 9 | 10 | -> Functionality "Analyze SixtyPical program" is implemented by 11 | -> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok" 12 | 13 | -> Functionality "Compile SixtyPical program" is implemented by 14 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter Functionality "Dump callgraph info for SixtyPical program" is implemented by 17 | -> shell command "bin/sixtypical --dump-callgraph --analyze-only --traceback %(test-body-file)" 18 | 19 | -> Functionality "Compile SixtyPical program with unreachable routine removal" is implemented by 20 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --prune-unreachable-routines --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter Functionality "Dump fallthru info for SixtyPical program" is implemented by 23 | -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)" 24 | 25 | -> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by 26 | -> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter