├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── apple2rom.cfg ├── crt0.s ├── examples ├── circle.bas ├── gr_fill_screen.bas ├── gr_speed_test.bas ├── mandelbrot.bas ├── stars.bas ├── stars.py ├── text_fill_screen_for.bas ├── text_fill_screen_goto.bas └── too_many_loops.bas ├── exporter.h ├── exporter.s ├── interrupt.s ├── main.c ├── platform.c ├── platform.h ├── rom_usage.awk ├── runtime.c ├── runtime.h ├── supervision.lib └── vectors.s /.gitignore: -------------------------------------------------------------------------------- 1 | tmp.lib 2 | main.s 3 | platform.s 4 | runtime.s 5 | *.o 6 | *.dbg 7 | *.map 8 | *.rom 9 | *.bin 10 | a.out 11 | *.lst 12 | apple2rom.lib 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(USER),lk) 2 | TREES ?= $(HOME)/others 3 | else 4 | TREES ?= $(HOME)/trees 5 | endif 6 | 7 | CC65 ?= $(TREES)/cc65/bin 8 | APPLE2E ?= $(TREES)/apple2e/apple2e 9 | 10 | CPU = 6502 11 | BIN = apple2a.bin 12 | ROM = apple2a.rom 13 | LIB = apple2rom.lib 14 | 15 | CC65_FLAGS = -t none --cpu $(CPU) --register-vars 16 | 17 | $(ROM): $(BIN) 18 | (dd count=5 bs=4096 if=/dev/zero 2> /dev/null; cat $(BIN)) > $(ROM) 19 | 20 | .PHONY: run 21 | run: $(ROM) 22 | $(APPLE2E) -mute -map main.map $(ROM) 23 | 24 | .PHONY: debug 25 | debug: $(ROM) 26 | lldb -- $(APPLE2E) -mute -map main.map $(ROM) 27 | 28 | $(BIN): main.o interrupt.o vectors.o exporter.o platform.o runtime.o apple2rom.cfg $(LIB) 29 | $(CC65)/ld65 -o $(BIN) -C apple2rom.cfg -m main.map --dbgfile main.dbg interrupt.o vectors.o exporter.o platform.o runtime.o main.o $(LIB) 30 | awk -f rom_usage.awk < main.map 31 | 32 | clean: 33 | rm -f *.o *.lst $(BIN) $(ROM) platform.s runtime.s main.s $(LIB) tmp.lib 34 | 35 | main.s: main.c exporter.h platform.h runtime.h 36 | $(CC65)/cc65 $(CC65_FLAGS) -O $< 37 | 38 | runtime.s: runtime.c runtime.h 39 | $(CC65)/cc65 $(CC65_FLAGS) -O $< 40 | 41 | %.o: %.s 42 | $(CC65)/ca65 -l $(<:.s=.lst) --cpu $(CPU) $< 43 | 44 | # platform.c contains inline assembly and code that must not be optimized 45 | platform.s: platform.c 46 | $(CC65)/cc65 $(CC65_FLAGS) $< 47 | 48 | platform.o: platform.s 49 | interrupt.o: interrupt.s 50 | vectors.o: vectors.s 51 | exporter.o: exporter.s 52 | crt0.o: crt0.s 53 | 54 | $(LIB): crt0.o supervision.lib 55 | cp supervision.lib tmp.lib 56 | $(CC65)/ar65 a tmp.lib crt0.o 57 | mv tmp.lib $(LIB) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apple IIa 2 | 3 | Custom ROM for the Apple IIe. Looks mostly like a real Apple IIe, but 4 | when you type `RUN`, the code is compiled instead of interpreted. 5 | Runs between 5 and 30 times faster. 6 | 7 | Supported features: The classic way to enter programs with 8 | line numbers, 16-bit integer variables, `HOME`, `PRINT`, `IF/THEN`, 9 | `FOR/NEXT`, `GOTO`, low-res graphics (`GR`, `PLOT`, `COLOR=`, `TEXT`), `REM`, 10 | `DIM` (single-dimensional arrays), `POKE`, and integer and boolean arithmetic. 11 | 12 | Not supported: Floating point, strings, 13 | high-res graphics, `DATA/READ/RESUME`, `GOSUB/RETURN/POP`, 14 | multi-dimensional arrays, keyboard input, exponentiation (`A^B`), and cassette I/O. 15 | 16 | [Full write-up](https://www.teamten.com/lawrence/projects/apple2a/) 17 | 18 | # Dependencies 19 | 20 | * [cc65](https://github.com/cc65/cc65) 21 | * [Apple IIe emulator](https://github.com/bradgrantham/apple2e) 22 | 23 | # Running 24 | 25 | ``` 26 | TREES=$HOME/path/to/github/trees make run 27 | ``` 28 | 29 | # License 30 | 31 | Copyright 2018 Lawrence Kesteloot and Brad Grantham 32 | 33 | Licensed under the Apache License, Version 2.0 (the "License"); 34 | you may not use this file except in compliance with the License. 35 | You may obtain a copy of the License at 36 | 37 | http://www.apache.org/licenses/LICENSE-2.0 38 | 39 | Unless required by applicable law or agreed to in writing, software 40 | distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and 43 | limitations under the License. 44 | -------------------------------------------------------------------------------- /apple2rom.cfg: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | ZP: start = $0, size = $100, type = rw, define = yes; 3 | RAM: start = $4000, size = $4000, define = yes; 4 | ROM: start = $D000, size = $3000, file = %O; 5 | } 6 | 7 | SEGMENTS { 8 | ZEROPAGE: load = ZP, type = zp, define = yes; 9 | DATA: load = ROM, type = rw, define = yes, run = RAM; 10 | BSS: load = RAM, type = bss, define = yes; 11 | HEAP: load = RAM, type = bss, optional = yes; 12 | STARTUP: load = ROM, type = ro; 13 | ONCE: load = ROM, type = ro, optional = yes; 14 | CODE: load = ROM, type = ro; 15 | RODATA: load = ROM, type = ro; 16 | VECTORS: load = ROM, type = ro, start = $FFFA; 17 | } 18 | 19 | FEATURES { 20 | CONDES: segment = STARTUP, 21 | type = constructor, 22 | label = __CONSTRUCTOR_TABLE__, 23 | count = __CONSTRUCTOR_COUNT__; 24 | CONDES: segment = STARTUP, 25 | type = destructor, 26 | label = __DESTRUCTOR_TABLE__, 27 | count = __DESTRUCTOR_COUNT__; 28 | } 29 | 30 | SYMBOLS { 31 | # Define the stack size for the application 32 | __STACKSIZE__: value = $0200, type = weak; 33 | # __STACKSIZE__: type = weak, value = $0800; # 2k stack 34 | } 35 | 36 | -------------------------------------------------------------------------------- /crt0.s: -------------------------------------------------------------------------------- 1 | ; --------------------------------------------------------------------------- 2 | ; crt0.s 3 | ; --------------------------------------------------------------------------- 4 | ; 5 | ; Startup code for cc65 (Single Board Computer version) 6 | 7 | .export _init, _exit 8 | .import _main 9 | 10 | .export __STARTUP__ : absolute = 1 ; Mark as startup 11 | .import __RAM_START__, __RAM_SIZE__ ; Linker generated 12 | 13 | .import copydata, zerobss, initlib, donelib 14 | 15 | .include "zeropage.inc" 16 | 17 | ; --------------------------------------------------------------------------- 18 | ; Place the startup code in a special segment 19 | 20 | .segment "STARTUP" 21 | 22 | ; --------------------------------------------------------------------------- 23 | ; A little light 6502 housekeeping 24 | 25 | _init: LDX #$FF ; Initialize stack pointer to $01FF 26 | TXS 27 | CLD ; Clear decimal mode 28 | 29 | ; --------------------------------------------------------------------------- 30 | ; Set cc65 argument stack pointer 31 | 32 | LDA #<(__RAM_START__ + __RAM_SIZE__) 33 | STA sp 34 | LDA #>(__RAM_START__ + __RAM_SIZE__) 35 | STA sp+1 36 | 37 | ; --------------------------------------------------------------------------- 38 | ; Initialize memory storage 39 | 40 | JSR zerobss ; Clear BSS segment 41 | JSR copydata ; Initialize DATA segment 42 | JSR initlib ; Run constructors 43 | 44 | ; --------------------------------------------------------------------------- 45 | ; Call main() 46 | 47 | JSR _main 48 | 49 | ; --------------------------------------------------------------------------- 50 | ; Back from main (this is also the _exit entry): force a software break 51 | 52 | _exit: JSR donelib ; Run destructors 53 | BRK 54 | 55 | -------------------------------------------------------------------------------- /examples/circle.bas: -------------------------------------------------------------------------------- 1 | 10 GR 2 | 15 COLOR=5 3 | 20 Y = 0 4 | 30 X = 0 5 | 40 IF (20-X)*(20-X) + (20-Y)*(20-Y) < 100 THEN PLOT X, Y 6 | 50 X = X + 1 7 | 60 IF X < 40 GOTO 40 8 | 70 Y = Y + 1 9 | 80 IF Y < 40 GOTO 30 10 | -------------------------------------------------------------------------------- /examples/gr_fill_screen.bas: -------------------------------------------------------------------------------- 1 | 10 GR 2 | 15 COLOR=5 3 | 20 FOR Y = 0 TO 39 4 | 30 FOR X = 0 TO 39 5 | 40 PLOT X,Y 6 | 50 NEXT X 7 | 60 NEXT Y 8 | -------------------------------------------------------------------------------- /examples/gr_speed_test.bas: -------------------------------------------------------------------------------- 1 | 10 GR 2 | 20 Y = 0 3 | 25 C = 1 4 | 30 X = 0 5 | 40 COLOR=C 6 | 45 PLOT X, Y 7 | 50 X = X + 1 8 | 55 C = C + 1 9 | 56 IF C = 16 THEN C = 1 10 | 60 IF X < 40 GOTO 40 11 | 70 Y = Y + 1 12 | 80 IF Y < 40 GOTO 30 13 | -------------------------------------------------------------------------------- /examples/mandelbrot.bas: -------------------------------------------------------------------------------- 1 | 5 GR 2 | 10 C1 = 64 3 | 11 CA = 8 4 | 12 CB = 8 5 | 20 C4 = 4*C1 6 | 30 XR = 40 7 | 40 YR = 40 8 | 50 MC = 15 9 | 60 RN = -2*C1 10 | 70 RX = 1*C1 11 | 80 IN = -5*C1/4 12 | 90 IX = 5*C1/4 13 | 100 RD = (RX - RN)/(XR - 1) 14 | 110 ID = (IX - IN)/(YR - 1) 15 | 120 I = IN 16 | 130 FOR Y = 0 TO YR - 1 17 | 140 R = RN 18 | 150 FOR X = 0 TO XR - 1 19 | 160 ZR = 0 20 | 170 ZI = 0 21 | 180 CT = 0 22 | 190 R2 = 0 23 | 200 I2 = 0 24 | 220 TR = R2 - I2 + R 25 | 230 TI = ZR*2/CA*ZI/CB + I 26 | 240 ZR = TR 27 | 250 ZI = TI 28 | 260 R2 = ZR/CA*ZR/CB 29 | 270 I2 = ZI/CA*ZI/CB 30 | 280 CT = CT + 1 31 | 290 IF CT < MC AND R2 + I2 < C4 GOTO 220 32 | 300 COLOR=15-CT:PLOT X,Y 33 | 310 R = R + RD 34 | 320 NEXT X 35 | 340 I = I + ID 36 | 350 NEXT Y 37 | -------------------------------------------------------------------------------- /examples/stars.bas: -------------------------------------------------------------------------------- 1 | 10 GR 2 | 20 TS = 40 3 | 30 DIM DX(9),DY(9),C(9),X(9),Y(9),T(9) 4 | 1000 DX(0) = -1 5 | 1005 DY(0) = -19 6 | 1010 C(0) = 1 7 | 1015 T(0) = 26 8 | 1100 DX(1) = -14 9 | 1105 DY(1) = -13 10 | 1110 C(1) = 5 11 | 1115 T(1) = 23 12 | 1200 DX(2) = 12 13 | 1205 DY(2) = -15 14 | 1210 C(2) = 7 15 | 1215 T(2) = 3 16 | 1300 DX(3) = 19 17 | 1305 DY(3) = 2 18 | 1310 C(3) = 9 19 | 1315 T(3) = 10 20 | 1400 DX(4) = 17 21 | 1405 DY(4) = -10 22 | 1410 C(4) = 12 23 | 1415 T(4) = 4 24 | 1500 DX(5) = 17 25 | 1505 DY(5) = -9 26 | 1510 C(5) = 9 27 | 1515 T(5) = 5 28 | 1600 DX(6) = 15 29 | 1605 DY(6) = 12 30 | 1610 C(6) = 4 31 | 1615 T(6) = 22 32 | 1700 DX(7) = -18 33 | 1705 DY(7) = -8 34 | 1710 C(7) = 5 35 | 1715 T(7) = 23 36 | 1800 DX(8) = -2 37 | 1805 DY(8) = -19 38 | 1810 C(8) = 1 39 | 1815 T(8) = 29 40 | 1900 DX(9) = -7 41 | 1905 DY(9) = 18 42 | 1910 C(9) = 10 43 | 1915 T(9) = 28 44 | 2200 I = 0 45 | 2210 OX = X(I) : OY = Y(I) 46 | 2220 X(I) = 20 + DX(I) * T(I) / 40 47 | 2230 Y(I) = 20 + DY(I) * T(I) / 40 48 | 2235 T(I) = T(I) + 1 : IF T(I) = TS THEN T(I) = 0 49 | 2240 COLOR=0 : PLOT OX, OY 50 | 2250 COLOR=C(I) : PLOT X(I), Y(I) 51 | 2260 I = I + 1 : IF I < 10 GOTO 2210 52 | 2300 GOTO 2200 53 | -------------------------------------------------------------------------------- /examples/stars.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | 4 | str = """%(init)d00 DX(%(which)d) = %(x)d 5 | %(init)d05 DY(%(which)d) = %(y)d 6 | %(init)d10 C(%(which)d) = %(c)d 7 | %(init)d15 T(%(which)d) = %(t)d""" 8 | 9 | num_stars = 10 10 | ts = 40 11 | 12 | print "10 GR" 13 | print "20 TS = %d" % ts 14 | print "30 DIM DX(%(n)d),DY(%(n)d),C(%(n)d),X(%(n)d),Y(%(n)d),T(%(n)d)" % {'n' : num_stars - 1 } 15 | 16 | for i in range(0,num_stars) : 17 | angle = random.uniform(0,.999) 18 | x = int(20 * math.cos(angle * 3.14159 * 2)) 19 | y = int(20 * math.sin(angle * 3.14159 * 2)) 20 | c = random.randrange(1,15) 21 | t = random.randrange(0,ts - 1) 22 | print str % {'init' : 10 + i, 'x' : x, 'y' : y, 'c' : c, 'which' : i, 't' : t} 23 | 24 | print "2200 I = 0" 25 | print "2210 OX = X(I) : OY = Y(I)" 26 | print "2220 X(I) = 20 + DX(I) * T(I) / %d" % ts 27 | print "2230 Y(I) = 20 + DY(I) * T(I) / %d" % ts 28 | print "2235 T(I) = T(I) + 1 : IF T(I) = TS THEN T(I) = 0" 29 | print "2240 COLOR=0 : PLOT OX, OY" 30 | print "2250 COLOR=C(I) : PLOT X(I), Y(I)" 31 | print "2260 I = I + 1 : IF I < %d GOTO 2210" % num_stars 32 | print "2300 GOTO 2200" 33 | -------------------------------------------------------------------------------- /examples/text_fill_screen_for.bas: -------------------------------------------------------------------------------- 1 | 10 HOME 2 | 20 FOR CH=65+128 TO 69+128 3 | 30 FOR AD = 1024 TO 2047 4 | 40 POKE AD, CH 5 | 50 NEXT 6 | 60 NEXT 7 | 8 | -------------------------------------------------------------------------------- /examples/text_fill_screen_goto.bas: -------------------------------------------------------------------------------- 1 | 10 HOME 2 | 20 CH = 65+128 3 | 30 AD = 1024 4 | 40 POKE AD,CH 5 | 50 AD = AD + 1 6 | 60 IF AD < 2048 GOTO 40 7 | 70 CH = CH + 1 8 | 80 IF CH < 70+128 GOTO 30 9 | -------------------------------------------------------------------------------- /examples/too_many_loops.bas: -------------------------------------------------------------------------------- 1 | 10 FOR A = 1 TO 10 2 | 20 FOR B = 1 TO 10 3 | 30 FOR C = 1 TO 10 4 | 40 FOR D = 1 TO 10 5 | 50 FOR E = 1 TO 10 6 | 60 FOR F = 1 TO 10 7 | 70 FOR G = 1 TO 10 8 | 80 FOR H = 1 TO 10 9 | 90 FOR I = 1 TO 10 10 | 100 FOR J = 1 TO 10 11 | 12 | 110 FOR K = 1 TO 10 13 | 120 FOR L = 1 TO 10 14 | 130 FOR M = 1 TO 10 15 | -------------------------------------------------------------------------------- /exporter.h: -------------------------------------------------------------------------------- 1 | #ifndef __EXPORTER_H__ 2 | #define __EXPORTER_H__ 3 | 4 | // Defines functions exported in exporter.s. 5 | 6 | extern void pushax(); 7 | extern void popax(); 8 | extern void incsp2(); 9 | extern void tosaddax(); 10 | extern void tossubax(); 11 | extern void tosmulax(); 12 | extern void tosdivax(); 13 | extern void toseqax(); 14 | extern void tosneax(); 15 | extern void tosltax(); 16 | extern void tosgtax(); 17 | extern void tosleax(); 18 | extern void tosgeax(); 19 | extern void bnegax(); 20 | extern void negax(); 21 | extern void aslax1(); 22 | extern void ldaxi(); 23 | extern void staxspidx(); 24 | 25 | // Two bytes each. 26 | extern unsigned int sp; 27 | #pragma zpsym ("sp"); 28 | extern unsigned int ptr1; 29 | #pragma zpsym ("ptr1"); 30 | 31 | // One byte each. 32 | extern unsigned char tmp1; 33 | #pragma zpsym ("tmp1"); 34 | extern unsigned char tmp2; 35 | #pragma zpsym ("tmp2"); 36 | 37 | #endif // __EXPORTER_H__ 38 | -------------------------------------------------------------------------------- /exporter.s: -------------------------------------------------------------------------------- 1 | ; --------------------------------------------------------------------------- 2 | ; exporter.s 3 | ; --------------------------------------------------------------------------- 4 | ; 5 | ; Exports cc65-internal routines (such as "pushax") as C-visible ones ("_pushax"). 6 | ; See the companion header file exporter.h. 7 | 8 | .import pushax 9 | .export _pushax := pushax 10 | .import popax 11 | .export _popax := popax 12 | .import incsp2 13 | .export _incsp2 := incsp2 14 | .import tosaddax 15 | .export _tosaddax := tosaddax 16 | .import tossubax 17 | .export _tossubax := tossubax 18 | .import tosmulax 19 | .export _tosmulax := tosmulax 20 | .import tosdivax 21 | .export _tosdivax := tosdivax 22 | .import toseqax 23 | .export _toseqax := toseqax 24 | .import tosneax 25 | .export _tosneax := tosneax 26 | .import tosltax 27 | .export _tosltax := tosltax 28 | .import tosgtax 29 | .export _tosgtax := tosgtax 30 | .import tosleax 31 | .export _tosleax := tosleax 32 | .import tosgeax 33 | .export _tosgeax := tosgeax 34 | .import bnegax 35 | .export _bnegax := bnegax 36 | .import negax 37 | .export _negax := negax 38 | .import aslax1 39 | .export _aslax1 := aslax1 40 | .import ldaxi 41 | .export _ldaxi := ldaxi 42 | .import staxspidx 43 | .export _staxspidx := staxspidx 44 | 45 | .importzp sp 46 | .exportzp _sp = sp 47 | 48 | .importzp ptr1 49 | .exportzp _ptr1 = ptr1 50 | 51 | .importzp tmp1 52 | .exportzp _tmp1 = tmp1 53 | .importzp tmp2 54 | .exportzp _tmp2 = tmp2 55 | 56 | -------------------------------------------------------------------------------- /interrupt.s: -------------------------------------------------------------------------------- 1 | ; --------------------------------------------------------------------------- 2 | ; interrupt.s 3 | ; --------------------------------------------------------------------------- 4 | ; 5 | ; Interrupt handler. 6 | ; 7 | ; Checks for a BRK instruction and returns from all valid interrupts. 8 | 9 | .import _stop 10 | .export _irq_int, _nmi_int 11 | 12 | .segment "CODE" 13 | 14 | .PC02 ; Force 65C02 assembly mode 15 | 16 | ; --------------------------------------------------------------------------- 17 | ; Non-maskable interrupt (NMI) service routine 18 | 19 | _nmi_int: RTI ; Return from all NMI interrupts 20 | 21 | ; --------------------------------------------------------------------------- 22 | ; Maskable interrupt (IRQ) service routine 23 | 24 | _irq_int: PHX ; Save X register contents to stack 25 | TSX ; Transfer stack pointer to X 26 | PHA ; Save accumulator contents to stack 27 | INX ; Increment X so it points to the status 28 | INX ; register value saved on the stack 29 | LDA $100,X ; Load status register contents 30 | AND #$10 ; Isolate B status bit 31 | BNE break ; If B = 1, BRK detected 32 | 33 | ; --------------------------------------------------------------------------- 34 | ; IRQ detected, return 35 | 36 | irq: PLA ; Restore accumulator contents 37 | PLX ; Restore X register contents 38 | RTI ; Return from all IRQ interrupts 39 | 40 | ; --------------------------------------------------------------------------- 41 | ; BRK detected, stop 42 | 43 | break: JMP break ; If BRK is detected, something very bad 44 | ; has happened, so stop running 45 | 46 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "exporter.h" 4 | #include "platform.h" 5 | #include "runtime.h" 6 | 7 | uint8_t *title = "Apple IIa"; 8 | uint8_t title_length = 9; 9 | 10 | // 6502 instructions. 11 | #define I_ORA_ZPG 0x05 12 | #define I_PHP 0x08 13 | #define I_CLC 0x18 14 | #define I_JSR 0x20 15 | #define I_PLP 0x28 16 | #define I_SEC 0x38 17 | #define I_PHA 0x48 18 | #define I_JMP_ABS 0x4C 19 | #define I_RTS 0x60 20 | #define I_ADC_ZPG 0x65 21 | #define I_PLA 0x68 22 | #define I_JMP_IND 0x6C 23 | #define I_ADC_ZPG_Y 0x71 24 | #define I_STA_ZPG 0x85 25 | #define I_STX_ZPG 0x86 26 | #define I_TXA 0x8A 27 | #define I_STA_IND_Y 0x91 28 | #define I_LDY_IMM 0xA0 29 | #define I_LDX_IMM 0xA2 30 | #define I_LDA_ZPG 0xA5 31 | #define I_LDX_ZPG 0xA6 32 | #define I_LDA_IMM 0xA9 33 | #define I_TAX 0xAA 34 | #define I_INY 0xC8 35 | #define I_BNE_REL 0xD0 36 | #define I_BEQ_REL 0xF0 37 | 38 | // Tokens. 39 | #define T_HOME 0x80 40 | #define T_PRINT 0x81 41 | #define T_LIST 0x82 42 | #define T_POKE 0x83 43 | #define T_RUN 0x84 44 | #define T_NEW 0x85 45 | #define T_PLUS 0x86 46 | #define T_MINUS 0x87 47 | #define T_ASTERISK 0x88 48 | #define T_SLASH 0x89 49 | #define T_CARET 0x8A 50 | #define T_AND 0x8B 51 | #define T_OR 0x8C 52 | #define T_GREATER_THAN 0x8D 53 | #define T_EQUAL 0x8E 54 | #define T_LESS_THAN 0x8F 55 | #define T_GOTO 0x90 56 | #define T_IF 0x91 57 | #define T_THEN 0x92 58 | #define T_GR 0x93 59 | #define T_TEXT 0x94 60 | #define T_COLOR 0x95 61 | #define T_PLOT 0x96 62 | #define T_FOR 0x97 63 | #define T_TO 0x98 64 | #define T_STEP 0x99 65 | #define T_NEXT 0x9A 66 | #define T_NOT 0x9B 67 | #define T_DIM 0x9C 68 | #define T_REM 0x9D 69 | 70 | // Operators. These encode both the operator (high nybble) and the precedence 71 | // (low nybble). Lower precedence has a lower low nybble value. For example, 72 | // OP_ADD (0x99) and OP_SUB (0xA9) have the same precedence (9). By convention 73 | // the precedence is the value of the lowest-valued operator in its class 74 | // (OP_ADD = 0x99), but only the relative values of precedence matter. All 75 | // of these are left-associative, as in AppleSoft BASIC. (Even though 76 | // exponentiation really should be right-associative.) 77 | #define OP_PRECEDENCE(op) ((op) & 0x0F) 78 | #define OP_OR 0x00 79 | #define OP_AND 0x11 80 | #define OP_NOT 0x22 81 | #define OP_LTE 0x33 82 | #define OP_GTE 0x43 83 | #define OP_EQ 0x55 84 | #define OP_NEQ 0x65 85 | #define OP_LT 0x75 86 | #define OP_GT 0x85 87 | #define OP_ADD 0x99 88 | #define OP_SUB 0xA9 89 | #define OP_MULT 0xBB 90 | #define OP_DIV 0xCB 91 | #define OP_NEG 0xDD 92 | #define OP_EXP 0xEE 93 | #define OP_ARRAY_DEREF 0xFB // Ignore precedence. 94 | #define OP_NO_OP 0xFC // Never on the stack. 95 | #define OP_CLOSE_PARENS 0xFD // Never on the stack. 96 | #define OP_OPEN_PARENS 0xFE // Ignore precedence. 97 | #define OP_INVALID 0xFF 98 | 99 | // Maximum number of lines in stored program. 100 | #define MAX_LINES 56 101 | 102 | // Maximum number of operators in the operator stack. 103 | #define MAX_OP_STACK 16 104 | 105 | // Maximum number of forward GOTOs. 106 | #define MAX_FORWARD_GOTO 16 107 | 108 | // Test for whether a character is a digit. 109 | #define IS_DIGIT(ch) ((ch) >= '0' && (ch) <= '9') 110 | 111 | // Test for first and subsequent variable name letters. 112 | #define IS_FIRST_VARIABLE_LETTER(ch) ((ch) >= 'A' && (ch) <= 'Z') 113 | #define IS_SUBSEQUENT_VARIABLE_LETTER(ch) (IS_FIRST_VARIABLE_LETTER(ch) || IS_DIGIT(ch)) 114 | 115 | // Info for each "forward GOTO", which is a GOTO to a line that we've 116 | // not compiled yet. 117 | typedef struct { 118 | // The line number the GOTO is on. This is for error messages. 119 | uint16_t source_line_number; 120 | 121 | // The line number it's trying to jump to. 122 | uint16_t target_line_number; 123 | 124 | // The address of the JMP instructions. This is 0 if this entry is unused. 125 | uint8_t *jmp_address; 126 | } ForwardGoto; 127 | 128 | // Info for each compiled line. 129 | typedef struct { 130 | // The line's number. 131 | uint16_t line_number; 132 | 133 | // The address in memory where its code was compiled. 134 | uint8_t *code; 135 | } LineInfo; 136 | 137 | // List of tokens. The token value is the index plus 0x80. 138 | static uint8_t *TOKEN[] = { 139 | "HOME", 140 | "PRINT", 141 | "LIST", 142 | "POKE", 143 | "RUN", 144 | "NEW", 145 | "+", 146 | "-", 147 | "*", 148 | "/", 149 | "^", 150 | "AND", 151 | "OR", 152 | ">", 153 | "=", 154 | "<", 155 | "GOTO", 156 | "IF", 157 | "THEN", 158 | "GR", 159 | "TEXT", 160 | "COLOR", 161 | "PLOT", 162 | "FOR", 163 | "TO", 164 | "STEP", 165 | "NEXT", 166 | "NOT", 167 | "DIM", 168 | "REM", 169 | }; 170 | static int16_t TOKEN_COUNT = sizeof(TOKEN)/sizeof(TOKEN[0]); 171 | 172 | uint8_t g_input_buffer[80]; 173 | int16_t g_input_buffer_length; 174 | 175 | // Compiled binary. 176 | uint8_t g_compiled[1024*10]; 177 | uint8_t *g_c = g_compiled; 178 | void (*g_compiled_function)() = (void (*)()) g_compiled; 179 | 180 | // Stored program. Each line is: 181 | // - Two bytes for pointer to next line (or zero if none). 182 | // - Two bytes for line number. 183 | // - Program line. 184 | // - Nul. 185 | uint8_t g_program[1024]; 186 | 187 | // Info about each compiled line. 188 | LineInfo g_line_info[MAX_LINES]; 189 | uint8_t g_line_info_count; 190 | 191 | // Operator stack, of the expression-evaluation routines. These are from the 192 | // OP_ constants. 193 | uint8_t g_op_stack[MAX_OP_STACK]; 194 | uint8_t g_op_stack_size; 195 | 196 | // List of all forward GOTOs. These are packed at the beginning, so the 197 | // first invalid (jmp_address == 0) entry marks the end. 198 | ForwardGoto g_forward_goto[MAX_FORWARD_GOTO]; 199 | uint8_t g_forward_goto_count; 200 | 201 | /** 202 | * Print the tokenized string, with tokens displayed as their full text. 203 | * Prints a newline at the end. 204 | */ 205 | static void print_detokenized(uint8_t *s) { 206 | while (*s != '\0') { 207 | if (*s >= 0x80) { 208 | print_char(' '); 209 | print(TOKEN[*s - 0x80]); 210 | print_char(' '); 211 | } else { 212 | print_char(*s); 213 | } 214 | 215 | s += 1; 216 | } 217 | 218 | print_char('\n'); 219 | } 220 | 221 | /** 222 | * Get the pointer to the next line in the stored program. Returns 0 223 | * if we're at the end. 224 | */ 225 | static uint8_t *get_next_line(uint8_t *line) { 226 | return *((uint8_t **) line); 227 | } 228 | 229 | /** 230 | * Get the line number of a stored program line. 231 | */ 232 | static uint16_t get_line_number(uint8_t *line) { 233 | return *((uint16_t *) (line + 2)); 234 | } 235 | 236 | /** 237 | * Return a pointer to the end of the program. This is one byte PAST the 238 | * last bytes in the program, which are two nuls. The "line" parameter is 239 | * an optional starting point, to use as an optimization instead of starting 240 | * from the beginning. 241 | */ 242 | static uint8_t *get_end_of_program(uint8_t *line) { 243 | uint8_t *next_line; 244 | 245 | if (line == 0) { 246 | // Start at the beginning if not specified. 247 | line = g_program; 248 | } 249 | 250 | while ((next_line = get_next_line(line)) != 0) { 251 | line = next_line; 252 | } 253 | 254 | // Skip the null "next" pointer. 255 | return line + 2; 256 | } 257 | 258 | /** 259 | * Clear the stored program. 260 | */ 261 | static void new_statement() { 262 | g_program[0] = '\0'; 263 | g_program[1] = '\0'; 264 | } 265 | 266 | /** 267 | * List the stored program. 268 | */ 269 | static void list_statement() { 270 | uint8_t *line = g_program; 271 | uint8_t *next_line; 272 | 273 | print_newline(); 274 | 275 | while ((next_line = get_next_line(line)) != 0) { 276 | print_uint(get_line_number(line)); 277 | print_char(' '); 278 | print_detokenized(line + 4); 279 | 280 | line = next_line; 281 | } 282 | } 283 | 284 | /** 285 | * If a starts with string b, returns the position in a after b. Else returns null. 286 | */ 287 | static uint8_t *skip_over(uint8_t *a, uint8_t *b) { 288 | while (*a != '\0' && *b != '\0') { 289 | if (*a != *b) { 290 | // Doesn't start with b. 291 | return 0; 292 | } 293 | 294 | a += 1; 295 | b += 1; 296 | } 297 | 298 | // See if we're at the end of b. 299 | return *b == '\0' ? a : 0; 300 | } 301 | 302 | /** 303 | * Add a function call to the compiled buffer. 304 | */ 305 | static void add_call(void *function) { 306 | uint16_t addr = (uint16_t) function; 307 | 308 | g_c[0] = I_JSR; 309 | g_c[1] = addr & 0xFF; 310 | g_c[2] = addr >> 8; 311 | g_c += 3; 312 | } 313 | 314 | /** 315 | * Add a function return to the compiled buffer. 316 | */ 317 | static void add_return() { 318 | *g_c++ = I_RTS; 319 | } 320 | 321 | /** 322 | * Parse an unsigned integer, returning the value and moving the pointer 323 | * past the end of the number. The pointer must already be at the beginning 324 | * of the number. 325 | */ 326 | static uint16_t parse_uint16(uint8_t **s_ptr) { 327 | uint16_t value = 0; 328 | uint8_t *s = *s_ptr; 329 | 330 | while (IS_DIGIT(*s)) { 331 | value = value*10 + (*s - '0'); 332 | s += 1; 333 | } 334 | 335 | *s_ptr = s; 336 | 337 | return value; 338 | } 339 | 340 | /** 341 | * Generate code to put the value into AX. 342 | */ 343 | static void compile_load_ax(uint16_t value) { 344 | g_c[0] = I_LDX_IMM; 345 | g_c[1] = value >> 8; 346 | g_c[2] = I_LDA_IMM; 347 | g_c[3] = value & 0xFF; 348 | g_c += 4; 349 | } 350 | 351 | /** 352 | * Generate code to store AX to a zero-page word. 353 | */ 354 | static void compile_store_zero_page(uint8_t addr) { 355 | g_c[0] = I_STA_ZPG; 356 | g_c[1] = addr; 357 | g_c[2] = I_STX_ZPG; 358 | g_c[3] = addr + 1; 359 | g_c += 4; 360 | } 361 | 362 | /** 363 | * Generate code to load AX from a zero-page word. 364 | */ 365 | static void compile_load_zero_page(uint8_t addr) { 366 | g_c[0] = I_LDA_ZPG; 367 | g_c[1] = addr; 368 | g_c[2] = I_LDX_ZPG; 369 | g_c[3] = addr + 1; 370 | g_c += 4; 371 | } 372 | 373 | /** 374 | * Find a variable by name. The buffer pointer must already be on the 375 | * first letter of a variable. Only the first two letters are considered. 376 | * Advances the pointer past the variable name (including letters after 377 | * the first two). Returns the VarInfo structure, or 0 if we can't find 378 | * the variable or create it. 379 | */ 380 | static VarInfo *find_variable(uint8_t **buffer) { 381 | uint8_t *s = *buffer; 382 | VarInfo *var = g_variables; 383 | uint16_t name; 384 | int16_t i; 385 | uint8_t data_type; 386 | 387 | // Pull out the variable name. 388 | name = *s++; 389 | if (IS_SUBSEQUENT_VARIABLE_LETTER(*s)) { 390 | name |= *s++ << 8; 391 | } 392 | // Skip rest of name. 393 | while (IS_SUBSEQUENT_VARIABLE_LETTER(*s)) { 394 | s++; 395 | } 396 | 397 | // Determine data type based on next letter. Don't skip over the open 398 | // parenthesis. 399 | data_type = *s == '(' ? DT_ARRAY : DT_INT; 400 | 401 | // Look for our variable or the first unused slot. 402 | for (i = 0; i < MAX_VARIABLES; i++, var++) { 403 | if (var->name == 0) { 404 | // First free entry. Allocate it. 405 | var->name = name; 406 | var->data_type = data_type; 407 | break; 408 | } else if (var->name == name && var->data_type == data_type) { 409 | // Found it. 410 | break; 411 | } 412 | } 413 | 414 | if (i == MAX_VARIABLES) { 415 | // Not found and can't create it. 416 | var = 0; 417 | } else { 418 | // Advance pointer. 419 | *buffer = s; 420 | } 421 | 422 | return var; 423 | } 424 | 425 | /** 426 | * Get the zero page address of a VarInfo pointer's variable. 427 | */ 428 | static uint8_t get_var_address(VarInfo *var) { 429 | return FIRST_VARIABLE + 2*(var - g_variables); 430 | } 431 | 432 | /** 433 | * Find the address of a line in the compiled buffer, or 0 if not found. 434 | */ 435 | static uint8_t *find_line_address(uint16_t line_number) { 436 | int i; 437 | 438 | for (i = 0; i < g_line_info_count; i++) { 439 | LineInfo *l = &g_line_info[i]; 440 | 441 | if (l->line_number == line_number) { 442 | return l->code; 443 | } 444 | } 445 | 446 | return 0; 447 | } 448 | 449 | /** 450 | * Pop an operator off the operator stack and compile it. 451 | */ 452 | static void pop_operator_stack() { 453 | uint8_t op = g_op_stack[--g_op_stack_size]; 454 | register uint8_t *c; 455 | 456 | switch (op) { 457 | case OP_ADD: 458 | add_call(tosaddax); 459 | break; 460 | 461 | case OP_SUB: 462 | add_call(tossubax); 463 | break; 464 | 465 | case OP_MULT: 466 | add_call(tosmulax); 467 | break; 468 | 469 | case OP_DIV: 470 | add_call(tosdivax); 471 | break; 472 | 473 | case OP_EQ: 474 | add_call(toseqax); 475 | break; 476 | 477 | case OP_NEQ: 478 | add_call(tosneax); 479 | break; 480 | 481 | case OP_LT: 482 | add_call(tosltax); 483 | break; 484 | 485 | case OP_GT: 486 | add_call(tosgtax); 487 | break; 488 | 489 | case OP_LTE: 490 | add_call(tosleax); 491 | break; 492 | 493 | case OP_GTE: 494 | add_call(tosgeax); 495 | break; 496 | 497 | case OP_AND: 498 | // AppleSoft BASIC does not have short-circuit logical operators. 499 | 500 | // See if second operand is 0. 501 | c = g_c; 502 | c[0] = I_STX_ZPG; 503 | c[1] = (uint8_t) &tmp1; 504 | c[2] = I_ORA_ZPG; 505 | c[3] = (uint8_t) &tmp1; 506 | c[4] = I_PHP; // Save the zero bit. 507 | g_c = c + 5; 508 | add_call(popax); // Pop first parameters. 3 instructions. 509 | c = g_c; 510 | c[0] = I_PLP; // Restore zero bit from second operand. 511 | c[1] = I_BNE_REL; // Check whether second operand was zero. 512 | c[2] = 4; // Not zero, continue to first parameter. 513 | c[3] = I_LDA_IMM; 514 | c[4] = 0; 515 | c[5] = I_BEQ_REL; // Zero bit always true here, will always jump. 516 | c[6] = 8; // Set X to zero and exit. 517 | // See if first operand (we just popped) is 0. 518 | c[7] = I_STX_ZPG; 519 | c[8] = (uint8_t) &tmp1; 520 | c[9] = I_ORA_ZPG; 521 | c[10] = (uint8_t) &tmp1; 522 | // If so, skip setting A to 1. A contains 0. 523 | c[11] = I_BEQ_REL; 524 | c[12] = 2; // The LDA below. 525 | // Set A to 1. 526 | c[13] = I_LDA_IMM; 527 | c[14] = 1; 528 | c[15] = I_LDX_IMM; // The BEQs above arrive here. 529 | c[16] = 0; 530 | g_c = c + 17; 531 | break; 532 | 533 | case OP_OR: 534 | // AppleSoft BASIC does not have short-circuit logical operators. 535 | 536 | // See if second operand is 0. 537 | c = g_c; 538 | c[0] = I_STX_ZPG; 539 | c[1] = (uint8_t) &tmp1; 540 | c[2] = I_ORA_ZPG; 541 | c[3] = (uint8_t) &tmp1; 542 | c[4] = I_STA_ZPG; 543 | c[5] = (uint8_t) &tmp2; // Store OR of bytes in tmp2 for later. 544 | g_c = c + 6; 545 | add_call(popax); // Pop first parameters. 3 instructions. 546 | c = g_c; 547 | // See if first operand is 0. 548 | c[0] = I_STX_ZPG; 549 | c[1] = (uint8_t) &tmp1; 550 | c[2] = I_ORA_ZPG; 551 | c[3] = (uint8_t) &tmp1; 552 | c[4] = I_ORA_ZPG; 553 | c[5] = (uint8_t) &tmp2; // OR with other parameter. 554 | // If so, skip setting A to 1. A contains 0. 555 | c[6] = I_BEQ_REL; 556 | c[7] = 2; // The LDA below. 557 | // Set A to 1. 558 | c[8] = I_LDA_IMM; 559 | c[9] = 1; 560 | c[10] = I_LDX_IMM; // The BEQs above arrive here. 561 | c[11] = 0; 562 | g_c = c + 12; 563 | break; 564 | 565 | case OP_NOT: 566 | add_call(bnegax); 567 | break; 568 | 569 | case OP_NEG: 570 | add_call(negax); 571 | break; 572 | 573 | case OP_ARRAY_DEREF: 574 | // Index is in AX and array address is at the top of the stack. 575 | 576 | // Double the index, since each entry takes two bytes. 577 | add_call(aslax1); 578 | 579 | // Add A to low byte of array address. 580 | c = g_c; 581 | c[0] = I_CLC; 582 | c[1] = I_LDY_IMM; // First entry in stack. 583 | c[2] = 0; 584 | c[3] = I_ADC_ZPG_Y; 585 | c[4] = (uint8_t) &sp; 586 | c[5] = I_PHA; 587 | 588 | // Add X to high byte by array address. 589 | c[6] = I_TXA; 590 | c[7] = I_INY; 591 | c[8] = I_ADC_ZPG_Y; 592 | c[9] = (uint8_t) &sp; 593 | c[10] = I_TAX; 594 | c[11] = I_PLA; 595 | g_c = c + 12; 596 | 597 | // Load word at AX. 598 | add_call(ldaxi); 599 | 600 | // Discard address off stack. 601 | add_call(incsp2); 602 | break; 603 | 604 | case OP_OPEN_PARENS: 605 | // No-op. 606 | break; 607 | 608 | default: 609 | print("Unhandled operator\n"); 610 | break; 611 | } 612 | } 613 | 614 | /** 615 | * Push an operator onto the operator stack. Follow the Shunting-yard 616 | * algorithm so that higher-precedence operators are performed 617 | * first. 618 | * 619 | * https://en.wikipedia.org/wiki/Shunting-yard_algorithm 620 | * http://wcipeg.com/wiki/Shunting_yard_algorithm 621 | */ 622 | static void push_operator_stack(uint8_t op) { 623 | uint8_t top_op; 624 | 625 | // Don't pop anything off if op is unary. 626 | if (op != OP_NOT && op != OP_NEG) { 627 | // All our operators are left-associative, so no special check for the case 628 | // of equal precedence. 629 | while (g_op_stack_size > 0 && 630 | op != OP_OPEN_PARENS && 631 | op != OP_ARRAY_DEREF && 632 | (top_op = g_op_stack[g_op_stack_size - 1]) != OP_OPEN_PARENS && 633 | top_op != OP_ARRAY_DEREF && 634 | OP_PRECEDENCE(top_op) >= OP_PRECEDENCE(op)) { 635 | 636 | pop_operator_stack(); 637 | } 638 | } 639 | 640 | // TODO Check for g_op_stack overflow. 641 | g_op_stack[g_op_stack_size++] = op; 642 | } 643 | 644 | /** 645 | * Parse an expression, generating code to compute it, leaving the 646 | * result in AX. 647 | */ 648 | static uint8_t *compile_expression(uint8_t *s) { 649 | char have_value_in_ax = 0; 650 | uint8_t expect_unary = 1; // Expect unary operator at start of expression. 651 | 652 | while (1) { 653 | if (IS_DIGIT(*s)) { 654 | // Parse number. 655 | if (have_value_in_ax) { 656 | // Push on the number stack. 657 | add_call(pushax); 658 | } 659 | 660 | compile_load_ax(parse_uint16(&s)); 661 | have_value_in_ax = 1; 662 | 663 | // Expect binary operator after operand. 664 | expect_unary = 0; 665 | } else if (IS_FIRST_VARIABLE_LETTER(*s)) { 666 | // Variable reference. 667 | VarInfo *var = find_variable(&s); 668 | 669 | if (have_value_in_ax) { 670 | // Push on the number stack. 671 | add_call(pushax); 672 | } 673 | 674 | if (var == 0) { 675 | // TODO: Not sure how to deal with this. For now just 676 | // fill in with zero, since assigning to this elsewhere 677 | // will cause an error. 678 | compile_load_ax(0); 679 | } else { 680 | uint8_t var_addr = get_var_address(var); 681 | 682 | // Load from var. 683 | compile_load_zero_page(var_addr); 684 | 685 | if (var->data_type == DT_ARRAY) { 686 | // TODO: Check that it's been DIM'ed. The data at var_addr should 687 | // not be zero. 688 | 689 | // Treat the open parenthesis as an array-dereferencing operator. 690 | if (*s == '(') { 691 | s += 1; 692 | push_operator_stack(OP_ARRAY_DEREF); 693 | expect_unary = 1; 694 | } else { 695 | // This is really a programming error, since the 696 | // variable should only be of type DT_ARRAY if it's 697 | // followed by an open parenthesis. 698 | } 699 | } 700 | } 701 | have_value_in_ax = 1; 702 | 703 | // Expect binary operator after operand. 704 | expect_unary = 0; 705 | } else { 706 | // Check if it's an operator. 707 | uint8_t op = OP_INVALID; 708 | 709 | if (*s == T_PLUS) { 710 | op = expect_unary ? OP_NO_OP : OP_ADD; 711 | } else if (*s == T_MINUS) { 712 | op = expect_unary ? OP_NEG : OP_SUB; 713 | } else if (*s == T_ASTERISK) { 714 | op = OP_MULT; 715 | } else if (*s == T_SLASH) { 716 | op = OP_DIV; 717 | } else if (*s == T_AND) { 718 | op = OP_AND; 719 | } else if (*s == T_OR) { 720 | op = OP_OR; 721 | } else if (*s == T_NOT) { 722 | if (expect_unary) { 723 | op = OP_NOT; 724 | } else { 725 | // TODO generate syntax error. 726 | break; 727 | } 728 | } else if (*s == T_EQUAL) { 729 | if (s[1] == T_LESS_THAN) { 730 | s += 1; 731 | op = OP_LTE; 732 | } else if (s[1] == T_GREATER_THAN) { 733 | s += 1; 734 | op = OP_GTE; 735 | } else { 736 | op = OP_EQ; 737 | } 738 | } else if (*s == T_LESS_THAN) { 739 | if (s[1] == T_EQUAL) { 740 | s += 1; 741 | op = OP_LTE; 742 | } else if (s[1] == T_GREATER_THAN) { 743 | s += 1; 744 | op = OP_NEQ; 745 | } else { 746 | op = OP_LT; 747 | } 748 | } else if (*s == T_GREATER_THAN) { 749 | if (s[1] == T_EQUAL) { 750 | s += 1; 751 | op = OP_GTE; 752 | } else if (s[1] == T_LESS_THAN) { 753 | s += 1; 754 | op = OP_NEQ; 755 | } else { 756 | op = OP_GT; 757 | } 758 | } else if (*s == '(') { // Parentheses are not tokenized. 759 | op = OP_OPEN_PARENS; 760 | } else if (*s == ')') { // Parentheses are not tokenized. 761 | uint8_t top_op; 762 | 763 | op = OP_CLOSE_PARENS; 764 | 765 | // Pop until open parenthesis or array dereference. 766 | while (g_op_stack_size > 0 && 767 | (top_op = g_op_stack[g_op_stack_size - 1]) != OP_OPEN_PARENS && 768 | top_op != OP_ARRAY_DEREF) { 769 | 770 | pop_operator_stack(); 771 | } 772 | if (g_op_stack_size == 0) { 773 | // Maybe this close parenthesis wasn't ours. For example, 774 | // "DIM X(5)". Treat it like end of expression. 775 | op = OP_INVALID; 776 | } else { 777 | // Pop open parenthesis or array dereference. 778 | pop_operator_stack(); 779 | } 780 | } 781 | 782 | // Check that we didn't have an inappropriate unary operator. 783 | if (expect_unary && op != OP_NO_OP && op != OP_NEG && op != OP_NOT && 784 | op != OP_OPEN_PARENS && op != OP_ARRAY_DEREF) { 785 | 786 | // TODO we should generate a syntax error here. 787 | print("Unexpected unary\n"); 788 | break; 789 | } 790 | 791 | // Expect unary operator after operators or open parenthesis. Expect 792 | // binary operator after close parenthesis. 793 | expect_unary = op != OP_CLOSE_PARENS; 794 | 795 | if (op != OP_INVALID) { 796 | s += 1; 797 | if (op != OP_CLOSE_PARENS && op != OP_NO_OP) { 798 | push_operator_stack(op); 799 | } 800 | } else { 801 | break; 802 | } 803 | } 804 | } 805 | 806 | if (have_value_in_ax) { 807 | // Empty the operator stack. 808 | while (g_op_stack_size > 0) { 809 | if (g_op_stack[g_op_stack_size - 1] == OP_OPEN_PARENS) { 810 | // TODO we should generate a syntax error here. 811 | print("Extra open parenthesis\n"); 812 | } 813 | pop_operator_stack(); 814 | } 815 | } else { 816 | // Something went wrong, we never got anything. 817 | print("Expression has no content\n"); 818 | compile_load_ax(0); 819 | } 820 | 821 | return s; 822 | } 823 | 824 | /** 825 | * Tokenize a string in place. Returns (and removes) any line number, or 826 | * INVALID_LINE_NUMBER if there's none. 827 | */ 828 | static uint16_t tokenize(uint8_t *s) { 829 | uint8_t *t = s; // Tokenized version. 830 | int16_t line_number; 831 | 832 | // Parse optional line number. 833 | if (IS_DIGIT(*s)) { 834 | line_number = parse_uint16(&s); 835 | } else { 836 | line_number = INVALID_LINE_NUMBER; 837 | } 838 | 839 | // Convert tokens. 840 | while (*s != '\0') { 841 | if (*s == ' ') { 842 | // Skip spaces. 843 | s++; 844 | } else { 845 | int16_t i; 846 | uint8_t *skipped = 0; 847 | 848 | // Try every token. 849 | for (i = 0; i < TOKEN_COUNT; i++) { 850 | // Quick optimization, peek at the first letter. 851 | skipped = s[0] == TOKEN[i][0] ? skip_over(s, TOKEN[i]) : 0; 852 | if (skipped != 0) { 853 | // Record token. 854 | *t++ = 0x80 + i; 855 | s = skipped; 856 | break; 857 | } 858 | } 859 | 860 | if (skipped == 0) { 861 | // Didn't find a token, just copy text. 862 | *t++ = *s++; 863 | } 864 | } 865 | } 866 | 867 | // Terminate string. 868 | *t++ = '\0'; 869 | 870 | return line_number; 871 | } 872 | 873 | /** 874 | * Find the stored program line with the given line number. If the line does 875 | * not exist, returns a pointer to the location where it should be inserted. 876 | */ 877 | static uint8_t *find_line(uint16_t line_number) { 878 | uint8_t *line = g_program; 879 | uint8_t *next_line; 880 | 881 | while ((next_line = get_next_line(line)) != 0) { 882 | // See if we hit it or just blew past it. 883 | if (get_line_number(line) >= line_number) { 884 | break; 885 | } 886 | 887 | line = next_line; 888 | } 889 | 890 | return line; 891 | } 892 | 893 | /** 894 | * Adds a new entry to the list for forward GOTOs. Returns whether successful. 895 | */ 896 | static uint8_t add_forward_goto(uint16_t source_line_number, uint16_t target_line_number, 897 | uint8_t *jmp_address) { 898 | 899 | ForwardGoto *f; 900 | 901 | if (g_forward_goto_count == MAX_FORWARD_GOTO) { 902 | return 0; 903 | } 904 | 905 | f = &g_forward_goto[g_forward_goto_count++]; 906 | f->source_line_number = source_line_number; 907 | f->target_line_number = target_line_number; 908 | f->jmp_address = jmp_address; 909 | 910 | return 1; 911 | } 912 | 913 | /** 914 | * Go through the list of forward GOTOs and set their jumps to this code. 915 | */ 916 | static void fix_up_forward_gotos(uint16_t line_number, uint8_t *code) { 917 | int i; 918 | ForwardGoto *f; 919 | uint16_t addr = (uint16_t) code; 920 | 921 | for (i = 0; i < g_forward_goto_count; i++) { 922 | f = &g_forward_goto[i]; 923 | 924 | if (f->target_line_number == line_number) { 925 | // Fill in jump address. 926 | f->jmp_address[1] = addr & 0xFF; 927 | f->jmp_address[2] = addr >> 8; 928 | 929 | // Swap last entry with this one. It's okay if these 930 | // are the same entry. 931 | *f = g_forward_goto[g_forward_goto_count - 1]; 932 | 933 | // Reduce size of array. 934 | g_forward_goto_count -= 1; 935 | 936 | // Re-process this entry, since we've swapped it. 937 | i -= 1; 938 | } 939 | } 940 | } 941 | 942 | /** 943 | * Adds an entry to the list of line infos. Returns whether successful. 944 | */ 945 | static uint8_t add_line_info(uint16_t line_number, uint8_t *code) { 946 | LineInfo *l; 947 | 948 | if (g_line_info_count == MAX_LINES) { 949 | // TODO not sure what to do here. 950 | print("Program too large"); 951 | return 0; 952 | } 953 | 954 | l = &g_line_info[g_line_info_count++]; 955 | l->line_number = line_number; 956 | l->code = code; 957 | 958 | // Fix up any forward GOTOs to this line. 959 | fix_up_forward_gotos(line_number, code); 960 | 961 | return 1; 962 | } 963 | 964 | /** 965 | * Call to configure the compilation step. 966 | */ 967 | static void set_up_compile(void) { 968 | g_c = g_compiled; 969 | g_line_info_count = 0; 970 | g_forward_goto_count = 0; 971 | } 972 | 973 | /** 974 | * Compile the tokenized line of BASIC, adding it to the g_compiled binary. 975 | */ 976 | static void compile_buffer(uint8_t *buffer, uint16_t line_number) { 977 | uint8_t *s = buffer; 978 | uint8_t done; 979 | // Keep track of addresses that point to the end of the line. 980 | uint8_t **end_of_line_address[4]; 981 | uint8_t end_of_line_count = 0; 982 | register uint8_t *c; 983 | 984 | do { 985 | int8_t error = 0; 986 | int8_t continue_statement = 0; 987 | 988 | // Default to being done after one statement. 989 | done = 1; 990 | 991 | if (*s == '\0' || *s == ':') { 992 | // Empty statement. We skip the colon below. 993 | } else if (IS_FIRST_VARIABLE_LETTER(*s)) { 994 | // Must be variable assignment. 995 | VarInfo *var = find_variable(&s); 996 | if (var == 0) { 997 | // TODO: Nicer error specifically for out of variable space. 998 | error = 1; 999 | } else { 1000 | uint8_t var_addr = get_var_address(var); 1001 | 1002 | if (var->data_type == DT_ARRAY) { 1003 | // Array element assignment. 1004 | 1005 | // Compile index expression. Skip open parenthesis. 1006 | s = compile_expression(s + 1); 1007 | if (*s != ')') { 1008 | error = 1; 1009 | } else { 1010 | s += 1; 1011 | 1012 | // Index is on the stack. Double the index, since each 1013 | // entry takes two bytes. 1014 | add_call(aslax1); 1015 | 1016 | // Add A to low byte of array address. 1017 | c = g_c; 1018 | c[0] = I_CLC; 1019 | c[1] = I_ADC_ZPG; 1020 | c[2] = var_addr; 1021 | c[3] = I_PHA; 1022 | 1023 | // Add X to high byte by array address. 1024 | c[4] = I_TXA; 1025 | c[5] = I_ADC_ZPG; 1026 | c[6] = var_addr + 1; 1027 | c[7] = I_TAX; 1028 | c[8] = I_PLA; 1029 | g_c = c + 9; 1030 | 1031 | // Push element address onto the stack. 1032 | add_call(pushax); 1033 | } 1034 | } 1035 | 1036 | if (*s != T_EQUAL || error) { 1037 | error = 1; 1038 | } else { 1039 | // Parse value. 1040 | s = compile_expression(s + 1); 1041 | 1042 | if (var->data_type == DT_ARRAY) { 1043 | // Value is in AX, address is on top of stack. The staxspidx 1044 | // function uses Y as an index, so must zero it out. 1045 | *g_c++ = I_LDY_IMM; 1046 | *g_c++ = 0; 1047 | add_call(staxspidx); 1048 | } else { 1049 | // Copy to var. 1050 | compile_store_zero_page(var_addr); 1051 | } 1052 | } 1053 | } 1054 | } else if (*s == T_HOME) { 1055 | s += 1; 1056 | add_call(home); 1057 | } else if (*s == T_PRINT) { 1058 | s += 1; 1059 | 1060 | if (*s != '\0' && *s != ':') { 1061 | // Parse expression. 1062 | s = compile_expression(s); 1063 | add_call(print_int); 1064 | } 1065 | 1066 | if (*s == ';') { 1067 | // Ends with a semicolon, don't print newline. 1068 | s += 1; 1069 | } else { 1070 | add_call(print_newline); 1071 | } 1072 | } else if (*s == T_LIST) { 1073 | s += 1; 1074 | add_call(list_statement); 1075 | } else if (*s == T_POKE) { 1076 | s += 1; 1077 | // Parse address. 1078 | s = compile_expression(s); 1079 | // Copy from AX to ptr1. 1080 | compile_store_zero_page((uint8_t) &ptr1); 1081 | if (*s != ',') { 1082 | error = 1; 1083 | } else { 1084 | s++; 1085 | // Parse value. LSB is in A. 1086 | s = compile_expression(s); 1087 | c = g_c; 1088 | c[0] = I_LDY_IMM; // Zero out Y. 1089 | c[1] = 0; 1090 | c[2] = I_STA_IND_Y; // Store at *ptr1. 1091 | c[3] = (uint8_t) &ptr1; 1092 | g_c = c + 4; 1093 | } 1094 | } else if (*s == T_GOTO) { 1095 | s += 1; 1096 | 1097 | if (!IS_DIGIT(*s)) { 1098 | error = 1; 1099 | } else { 1100 | uint16_t target_line_number = parse_uint16(&s); 1101 | uint16_t addr = (uint16_t) find_line_address(target_line_number); 1102 | 1103 | if (addr == 0) { 1104 | // Line not found. Must be a forward GOTO. Record it 1105 | // and keep going. 1106 | uint8_t success = add_forward_goto(line_number, target_line_number, 1107 | g_c); 1108 | if (!success) { 1109 | // TODO handle error. 1110 | } 1111 | } 1112 | 1113 | c = g_c; 1114 | c[0] = I_JMP_ABS; 1115 | c[1] = addr & 0xFF; 1116 | c[2] = addr >> 8; 1117 | g_c = c + 3; 1118 | } 1119 | } else if (*s == T_IF) { 1120 | // Save where we are in case we need to roll back. 1121 | uint8_t *saved_c = g_c; 1122 | 1123 | s += 1; 1124 | // Parse conditional expression. 1125 | s = compile_expression(s); 1126 | // Check if AX is zero. Or the two bytes together, through the zero page. 1127 | c = g_c; 1128 | c[0] = I_STX_ZPG; 1129 | c[1] = (uint8_t) &tmp1; 1130 | c[2] = I_ORA_ZPG; 1131 | c[3] = (uint8_t) &tmp1; 1132 | // If so, skip to end of this line. 1133 | c[4] = I_BNE_REL; 1134 | c[5] = 3; // Skip over absolute jump. 1135 | c[6] = I_JMP_ABS; 1136 | c += 7; 1137 | // TODO Check for overflow of end_of_line_address: 1138 | end_of_line_address[end_of_line_count++] = (uint8_t **) c; 1139 | c[0] = 0; // Address of next line. 1140 | c[1] = 0; // Address of next line. 1141 | g_c = c + 2; 1142 | 1143 | if (*s == T_THEN) { 1144 | // Skip THEN and continue 1145 | s += 1; 1146 | continue_statement = 1; 1147 | } else if (*s == T_GOTO) { 1148 | // Just continue, we'll pick it up after the loop. 1149 | continue_statement = 1; 1150 | } else { 1151 | // Must be THEN or GOTO. Erase what we've done. 1152 | g_c = saved_c; 1153 | error = 1; 1154 | } 1155 | } else if (*s == T_FOR) { 1156 | uint8_t *loop_top_addr_addr = 0; 1157 | 1158 | s += 1; 1159 | 1160 | // We'll set this to 0 if we succeed. 1161 | error = 1; 1162 | 1163 | if (IS_FIRST_VARIABLE_LETTER(*s)) { 1164 | VarInfo *var; 1165 | 1166 | // For the error message. 1167 | compile_load_ax(line_number); 1168 | add_call(pushax); 1169 | 1170 | var = find_variable(&s); 1171 | if (var == 0) { 1172 | // TODO: Nicer error specifically for out of variable space. 1173 | } else if (var->data_type == DT_ARRAY) { 1174 | // Syntax error, can't use array index for FOR loop variable. 1175 | } else { 1176 | uint16_t var_addr = get_var_address(var); 1177 | 1178 | compile_load_ax(var_addr); 1179 | add_call(pushax); 1180 | 1181 | if (*s == T_EQUAL) { 1182 | s += 1; 1183 | 1184 | // Parse initial value. 1185 | s = compile_expression(s); 1186 | 1187 | // Copy to var. 1188 | compile_store_zero_page(var_addr); 1189 | 1190 | if (*s == T_TO) { 1191 | s += 1; 1192 | 1193 | // Parse end value. 1194 | s = compile_expression(s); 1195 | add_call(pushax); 1196 | 1197 | if (*s == T_STEP) { 1198 | s += 1; 1199 | 1200 | // Parse step. 1201 | s = compile_expression(s); 1202 | } else { 1203 | // Default to step of 1. 1204 | compile_load_ax(1); 1205 | } 1206 | add_call(pushax); 1207 | 1208 | // Finally, the address at the top of the FOR loop. 1209 | // We don't have it yet, so we leave space and record 1210 | // the location where the NEXT should jump to. 1211 | // Don't use compile_load_ax() here because a change 1212 | // there would mess up how we fill it in below. Inline 1213 | // it here so we have control over that. 1214 | loop_top_addr_addr = g_c; 1215 | g_c[0] = I_LDX_IMM; 1216 | g_c[2] = I_LDA_IMM; 1217 | g_c += 4; 1218 | 1219 | add_call(for_statement); 1220 | error = 0; 1221 | } 1222 | } 1223 | } 1224 | } 1225 | 1226 | if (loop_top_addr_addr != 0) { 1227 | uint16_t loop_top_addr = (uint16_t) g_c; 1228 | loop_top_addr_addr[1] = loop_top_addr >> 8; // X 1229 | loop_top_addr_addr[3] = loop_top_addr & 0xFF; // A 1230 | } 1231 | } else if (*s == T_NEXT) { 1232 | s += 1; 1233 | 1234 | // For the error message. 1235 | compile_load_ax(line_number); 1236 | add_call(pushax); 1237 | 1238 | // See if there's the optional variable. We don't support multiple 1239 | // variables ("NEXT I,J"). 1240 | if (IS_FIRST_VARIABLE_LETTER(*s)) { 1241 | VarInfo *var = find_variable(&s); 1242 | if (var == 0) { 1243 | // TODO: Nicer error specifically for out of variable space. 1244 | error = 1; 1245 | } else { 1246 | compile_load_ax(get_var_address(var)); 1247 | } 1248 | } else { 1249 | // Zero means find the most recent FOR loop. 1250 | compile_load_ax(0); 1251 | } 1252 | 1253 | // Process the NEXT instruction. 1254 | add_call(next_statement); 1255 | 1256 | // The next_statement() function returns the address to jump 1257 | // to if we're looping, or 0 if we're not. 1258 | 1259 | // Copy from AX to ptr1. We must save it because checking it destroys it. 1260 | c = g_c; 1261 | c[0] = I_STA_ZPG; 1262 | c[1] = (uint8_t) &ptr1; 1263 | c[2] = I_STX_ZPG; 1264 | c[3] = (uint8_t) &ptr1 + 1; 1265 | // Check if AX is zero. Destroys AX. 1266 | c[4] = I_ORA_ZPG; 1267 | c[5] = (uint8_t) &ptr1 + 1; // OR X into A. 1268 | // If zero, skip over jump. 1269 | c[6] = I_BEQ_REL; 1270 | c[7] = 3; // Skip over indirect jump. 1271 | // Jump to top of loop, indirectly through ptr1, which has the address. 1272 | c[8] = I_JMP_IND; 1273 | c[9] = (uint8_t) (&ptr1 & 0xFF); 1274 | c[10] = (uint8_t) (&ptr1 >> 8); 1275 | g_c = c + 11; 1276 | } else if (*s == T_DIM) { 1277 | s += 1; 1278 | 1279 | while (1) { 1280 | // Expect variable name. 1281 | if (!IS_FIRST_VARIABLE_LETTER(*s)) { 1282 | error = 1; 1283 | } else { 1284 | VarInfo *var = find_variable(&s); 1285 | 1286 | if (var == 0) { 1287 | // TODO handle error. 1288 | error = 1; 1289 | } else { 1290 | // Must be an array variable. 1291 | if (var->data_type != DT_ARRAY) { 1292 | // TODO handle error. 1293 | error = 1; 1294 | } else { 1295 | uint8_t var_addr = get_var_address(var); 1296 | 1297 | // Put array address in AX. 1298 | compile_load_zero_page(var_addr); 1299 | 1300 | // See if we have an address. If yes, then we've been 1301 | // dimensioned before. 1302 | c = g_c; 1303 | c[0] = I_STX_ZPG; 1304 | c[1] = (uint8_t) &tmp1; 1305 | c[2] = I_ORA_ZPG; 1306 | c[3] = (uint8_t) &tmp1; 1307 | c[4] = I_BEQ_REL; // If zero, branch to actual work. 1308 | c[5] = 8; // Load, call, and return. 1309 | g_c = c + 6; 1310 | compile_load_ax(line_number); 1311 | add_call(redimd_array_error); 1312 | add_return(); 1313 | 1314 | // Assume we're followed by an open parenthesis. Parse 1315 | // expression for the size of the array. 1316 | s = compile_expression(s + 1); 1317 | 1318 | if (*s != ')') { 1319 | error = 1; 1320 | } else { 1321 | s += 1; 1322 | 1323 | // AX now holds the size of the array. 1324 | add_call(pushax); 1325 | 1326 | // Push address in zero page where the array address 1327 | // should be stored. 1328 | compile_load_ax(var_addr); 1329 | 1330 | // Call a runtime routine to allocate it. 1331 | add_call(allocate_array); 1332 | } 1333 | } 1334 | } 1335 | } 1336 | 1337 | if (*s == ',') { 1338 | // More variables to dim. 1339 | s += 1; 1340 | } else { 1341 | // We're done. 1342 | break; 1343 | } 1344 | } 1345 | } else if (*s == T_REM) { 1346 | // Done with line. 1347 | break; 1348 | } else if (*s == T_GR) { 1349 | s += 1; 1350 | add_call(gr_statement); 1351 | } else if (*s == T_TEXT) { 1352 | s += 1; 1353 | add_call(text_statement); 1354 | } else if (*s == T_COLOR) { 1355 | s += 1; 1356 | if (*s != T_EQUAL) { 1357 | error = 1; 1358 | } else { 1359 | s = compile_expression(s + 1); 1360 | add_call(color_statement); 1361 | } 1362 | } else if (*s == T_PLOT) { 1363 | s += 1; 1364 | s = compile_expression(s); 1365 | add_call(pushax); 1366 | if (*s != ',') { 1367 | error = 1; 1368 | } else { 1369 | s += 1; 1370 | s = compile_expression(s); 1371 | add_call(plot_statement); 1372 | } 1373 | } else { 1374 | error = 1; 1375 | } 1376 | 1377 | // Now we're at the end of our statement. 1378 | if (!error) { 1379 | if (continue_statement) { 1380 | // No problem, just continue from here. 1381 | done = 0; 1382 | } else if (*s == ':') { 1383 | // Skip colon. 1384 | s += 1; 1385 | 1386 | // Next statement. 1387 | done = 0; 1388 | } else if (*s != '\0') { 1389 | // Junk at the end of the statement. 1390 | error = 1; 1391 | } 1392 | } 1393 | 1394 | if (error) { 1395 | end_of_line_count = 0; 1396 | compile_load_ax(line_number); 1397 | add_call(syntax_error); 1398 | 1399 | // Terminate program. 1400 | add_return(); 1401 | } 1402 | } while (!done); 1403 | 1404 | // Fill in the places where we needed the address of the end of the line. 1405 | while (end_of_line_count > 0) { 1406 | *end_of_line_address[--end_of_line_count] = g_c; 1407 | } 1408 | } 1409 | 1410 | /** 1411 | * Complete the compilation buffer and run it. 1412 | */ 1413 | static void complete_compile_and_execute(void) { 1414 | int i; 1415 | uint16_t compiled_length; 1416 | 1417 | // Return from function. 1418 | add_return(); 1419 | 1420 | // Always clear the FOR stack before running. We don't want it 1421 | // either in stored program mode, or in immediate mode. 1422 | clear_for_stack(); 1423 | 1424 | // Forward GOTOs that couldn't be resolved are changed to 1425 | // jumps to error messages. 1426 | for (i = 0; i < g_forward_goto_count; i++) { 1427 | ForwardGoto *f = &g_forward_goto[i]; 1428 | uint16_t addr = (uint16_t) g_c; 1429 | 1430 | // Jump to end of buffer. 1431 | f->jmp_address[1] = addr & 0xFF; 1432 | f->jmp_address[2] = addr >> 8; 1433 | 1434 | // Add code at end of buffer to show error. 1435 | compile_load_ax(f->source_line_number); 1436 | add_call(undefined_statement_error); 1437 | 1438 | // Terminate program. 1439 | add_return(); 1440 | } 1441 | 1442 | // Dump compiled buffer to the terminal. 1443 | compiled_length = g_c - g_compiled; 1444 | if (1) { 1445 | int i; 1446 | uint8_t *debug_port = (uint8_t *) 0xBFFE; 1447 | 1448 | // Start process. Data is ignored. 1449 | debug_port[0] = 0; 1450 | 1451 | // Size of program, little endian. 1452 | debug_port[1] = compiled_length & 0xFF; 1453 | debug_port[1] = compiled_length >> 8; 1454 | // Address of program start, little endian. 1455 | debug_port[1] = ((uint16_t) &g_compiled[0]) & 0xFF; 1456 | debug_port[1] = ((uint16_t) &g_compiled[0]) >> 8; 1457 | // Program bytes. 1458 | for (i = 0; i < compiled_length; i++) { 1459 | debug_port[1] = g_compiled[i]; 1460 | } 1461 | } 1462 | 1463 | if (compiled_length > sizeof(g_compiled)) { 1464 | // TODO: Check while adding bytes, not at the end. 1465 | print("\n?Binary length exceeded"); 1466 | } else { 1467 | // Call it. 1468 | g_compiled_function(); 1469 | } 1470 | } 1471 | 1472 | /** 1473 | * Clear out all variables. This does not clear their value, only our 1474 | * knowledge of them. 1475 | */ 1476 | void clear_variables(void) { 1477 | memset(g_variables, 0, sizeof(g_variables)); 1478 | } 1479 | 1480 | /** 1481 | * Compile the stored program and execute it. 1482 | */ 1483 | static void compile_stored_program(void) { 1484 | uint8_t *line = g_program; 1485 | uint8_t *next_line; 1486 | 1487 | // Clear out all variables. 1488 | clear_variables(); 1489 | 1490 | set_up_compile(); 1491 | 1492 | // Clear runtime state. 1493 | add_call(initialize_runtime); 1494 | 1495 | while ((next_line = get_next_line(line)) != 0) { 1496 | uint16_t line_number = get_line_number(line); 1497 | uint8_t success = add_line_info(line_number, g_c); 1498 | 1499 | // Compile just this line. 1500 | compile_buffer(line + 4, line_number); 1501 | 1502 | line = next_line; 1503 | } 1504 | 1505 | complete_compile_and_execute(); 1506 | } 1507 | 1508 | /** 1509 | * Process the user's line of input, possibly compiling the code. 1510 | * and executing it. 1511 | */ 1512 | static void process_input_buffer() { 1513 | uint16_t line_number; 1514 | 1515 | g_input_buffer[g_input_buffer_length] = '\0'; 1516 | 1517 | // Tokenize in-place. 1518 | line_number = tokenize(g_input_buffer); 1519 | if (line_number == INVALID_LINE_NUMBER) { 1520 | // Immediate mode. 1521 | 1522 | if (g_input_buffer[0] == T_RUN) { 1523 | // We don't compile "RUN". 1524 | compile_stored_program(); 1525 | } else if (g_input_buffer[0] == T_NEW) { 1526 | // We don't compile "NEW". 1527 | new_statement(); 1528 | } else { 1529 | // Compile the immediate mode line. 1530 | set_up_compile(); 1531 | compile_buffer(g_input_buffer, INVALID_LINE_NUMBER); 1532 | complete_compile_and_execute(); 1533 | } 1534 | } else { 1535 | // Stored mode. Add line to program. 1536 | 1537 | // Return line to replace or delete, or location to insert new line. 1538 | uint8_t *line = find_line(line_number); 1539 | uint8_t *next_line = get_next_line(line); 1540 | uint8_t *end_of_program = get_end_of_program(line); 1541 | int16_t adjustment = 0; 1542 | 1543 | if (next_line == 0 || get_line_number(line) != line_number) { 1544 | // Didn't find line. Insert it here. 1545 | 1546 | // Next pointer, line number, line, and nul. 1547 | uint8_t buffer_length = strlen(g_input_buffer); 1548 | adjustment = 4 + buffer_length + 1; 1549 | 1550 | // Shift rest of program over. 1551 | memmove(line + adjustment, line, end_of_program - line); 1552 | 1553 | // Next line. Point to yourself initially, we'll adjust below. 1554 | *((uint8_t **) line) = line; 1555 | 1556 | // Line number. 1557 | *((uint16_t *) (line + 2)) = line_number; 1558 | 1559 | // Buffer and nul. 1560 | memmove(line + 4, g_input_buffer, buffer_length + 1); 1561 | } else { 1562 | // Found line. 1563 | 1564 | if (g_input_buffer[0] == '\0') { 1565 | // Empty line, delete old one. 1566 | 1567 | // Adjustment is negative. 1568 | adjustment = line - next_line; 1569 | memmove(line, next_line, end_of_program - next_line); 1570 | } else { 1571 | // Replace line. 1572 | 1573 | // Compute adjustment. 1574 | uint8_t buffer_length = strlen(g_input_buffer); 1575 | adjustment = line - next_line + 4 + buffer_length + 1; 1576 | memmove(next_line + adjustment, next_line, end_of_program - next_line); 1577 | 1578 | // Buffer and nul. 1579 | memmove(line + 4, g_input_buffer, buffer_length + 1); 1580 | } 1581 | } 1582 | 1583 | if (adjustment != 0) { 1584 | // Adjust all the next pointers. 1585 | while ((next_line = get_next_line(line)) != 0) { 1586 | // Adjust by the amount we inserted or deleted. 1587 | next_line += adjustment; 1588 | 1589 | *((uint8_t **) line) = next_line; 1590 | line = next_line; 1591 | } 1592 | } 1593 | } 1594 | } 1595 | 1596 | int16_t main(void) 1597 | { 1598 | int16_t blink; 1599 | 1600 | // Clear stored program. 1601 | new_statement(); 1602 | 1603 | // Clear out all variables. 1604 | clear_variables(); 1605 | 1606 | // Initialize UI. 1607 | home(); 1608 | 1609 | // Print title. 1610 | move_cursor((40 - title_length) / 2, 0); 1611 | print(title); 1612 | 1613 | // Prompt. 1614 | print("\n\n]"); 1615 | 1616 | // Keyboard input. 1617 | blink = 0; 1618 | g_input_buffer_length = 0; 1619 | show_cursor(); 1620 | while(1) { 1621 | // Blink cursor. 1622 | blink += 1; 1623 | if (blink == 3000) { 1624 | if (g_showing_cursor) { 1625 | hide_cursor(); 1626 | } else { 1627 | show_cursor(); 1628 | } 1629 | blink = 0; 1630 | } 1631 | 1632 | if(keyboard_test()) { 1633 | hide_cursor(); 1634 | 1635 | while(keyboard_test()) { 1636 | uint8_t key; 1637 | 1638 | key = keyboard_get(); 1639 | if (key == 8) { 1640 | // Backspace. 1641 | if (g_input_buffer_length > 0) { 1642 | move_cursor(g_cursor_x - 1, g_cursor_y); 1643 | g_input_buffer_length -= 1; 1644 | } 1645 | } else if (key == 13) { 1646 | // Return. 1647 | clear_to_eol(); 1648 | print_char('\n'); 1649 | 1650 | process_input_buffer(); 1651 | 1652 | print("\n]"); 1653 | g_input_buffer_length = 0; 1654 | } else { 1655 | if (g_input_buffer_length < sizeof(g_input_buffer) - 1) { 1656 | print_char(key); 1657 | g_input_buffer[g_input_buffer_length++] = key; 1658 | } 1659 | } 1660 | } 1661 | 1662 | show_cursor(); 1663 | } 1664 | } 1665 | 1666 | return 0; 1667 | } 1668 | -------------------------------------------------------------------------------- /platform.c: -------------------------------------------------------------------------------- 1 | #include "platform.h" 2 | 3 | #define KBD_STATUS_OR_KEY ((unsigned char *)0xC000) 4 | #define KBD_CLEAR_CURRENT_KEY ((unsigned char *)0xC010) 5 | 6 | #define KEY_READY_MASK 0x80 7 | #define KEY_VALUE_MASK 0x7F 8 | 9 | // Returns non-zero if a key is ready to be read. 10 | int keyboard_test(void) 11 | { 12 | return *KBD_STATUS_OR_KEY & KEY_READY_MASK; 13 | } 14 | 15 | // Clears current key, sets up next one for test or get 16 | void keyboard_clear(void) 17 | { 18 | unsigned char clear_key; 19 | 20 | // On 6502, a write cycle READS AND THEN WRITES, so just read the 21 | // "latch next key" strobe. 22 | clear_key = *KBD_CLEAR_CURRENT_KEY; 23 | } 24 | 25 | // Wait until a key is ready and then return it without high bit set 26 | unsigned char keyboard_get(void) 27 | { 28 | unsigned char key; 29 | 30 | while(!((key = *KBD_STATUS_OR_KEY) & KEY_READY_MASK)); 31 | 32 | keyboard_clear(); 33 | 34 | return key & KEY_VALUE_MASK; 35 | } 36 | -------------------------------------------------------------------------------- /platform.h: -------------------------------------------------------------------------------- 1 | #ifndef __PLATFORM_H__ 2 | #define __PLATFORM_H__ 3 | 4 | #define TEXT_PAGE1_BASE ((unsigned char *)0x400) 5 | #define TEXT_PAGE2_BASE ((unsigned char *)0x800) 6 | 7 | // Standard types. 8 | typedef signed char int8_t; 9 | typedef unsigned char uint8_t; 10 | typedef signed int int16_t; 11 | typedef unsigned int uint16_t; 12 | 13 | // Returns non-zero if a key is ready to be read. 14 | extern int keyboard_test(void); 15 | 16 | // Clears current key, sets up next one for test or get 17 | extern void keyboard_next(void); 18 | 19 | // Wait until a key is ready and then return it without high bit set 20 | extern unsigned char keyboard_get(void); 21 | 22 | #endif // __PLATFORM_H__ 23 | -------------------------------------------------------------------------------- /rom_usage.awk: -------------------------------------------------------------------------------- 1 | # Pipe main.map into this to get ROM size info. 2 | 3 | # https://stackoverflow.com/a/32437561/211234 4 | function parse_hex(hex_string) { 5 | if (hex_string ~ /^0x/) { 6 | hex_string = substr(hex_string, 3) 7 | } 8 | 9 | value = 0 10 | for (i = 1; i <= length(hex_string); i++) { 11 | value = value*16 + H[substr(hex_string, i, 1)] 12 | } 13 | 14 | return value 15 | } 16 | 17 | BEGIN { 18 | # Build map from hex digit to value. 19 | for (i = 0; i < 16; i++) { 20 | H[sprintf("%x",i)] = i 21 | H[sprintf("%X",i)] = i 22 | } 23 | } 24 | 25 | /^CODE/ { code_start = parse_hex($2) } 26 | 27 | /^RODATA/ { rodata_end = parse_hex($3) } 28 | 29 | /^VECTORS/ { vectors_start = parse_hex($2) } 30 | 31 | END { 32 | code = rodata_end - code_start + 1 33 | all = vectors_start - code_start 34 | printf "%d of %d ROM bytes (%d%%) used\n", code, all, code*100/all 35 | } 36 | 37 | -------------------------------------------------------------------------------- /runtime.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "runtime.h" 4 | 5 | // Max number of nested FOR loops. This value matches AppleSoft BASIC. 6 | #define MAX_FOR 10 7 | 8 | // Max words for arrays. 9 | #define MAX_ARRAY_WORDS 2048 10 | 11 | #define CURSOR_GLYPH 127 12 | #define SCREEN_HEIGHT 24 13 | #define SCREEN_WIDTH 40 14 | #define SCREEN_STRIDE (3*SCREEN_WIDTH + 8) 15 | #define MIXED_TEXT_HEIGHT 4 16 | #define MIXED_GRAPHICS_HEIGHT (SCREEN_HEIGHT - MIXED_TEXT_HEIGHT) 17 | #define CLEAR_CHAR (' ' | 0x80) 18 | 19 | #define TEXT_OFF_SWITCH ((uint8_t *) 49232U) 20 | #define TEXT_ON_SWITCH ((uint8_t *) 49233U) 21 | #define MIXED_OFF_SWITCH ((uint8_t *) 49234U) 22 | #define MIXED_ON_SWITCH ((uint8_t *) 49235U) 23 | #define HIRES_OFF_SWITCH ((uint8_t *) 49238U) 24 | #define HIRES_ON_SWITCH ((uint8_t *) 49239U) 25 | 26 | /** 27 | * Run-time stack of FOR loops. 28 | */ 29 | typedef struct { 30 | // Address (in the zero page) of the loop variable. 31 | uint8_t var_address; 32 | 33 | // End value. 34 | int16_t end_value; 35 | 36 | // Step. 37 | int16_t step; 38 | 39 | // Address of the top of the loop to jump back to. 40 | uint16_t loop_top_addr; 41 | } ForInfo; 42 | 43 | // Location of cursor in logical screen space. 44 | uint16_t g_cursor_x; 45 | uint16_t g_cursor_y; 46 | // Whether the cursor is being displayed. 47 | uint16_t g_showing_cursor; 48 | // Character at the cursor location. 49 | uint8_t g_cursor_ch; 50 | 51 | // Whether in low-res graphics mode. 52 | uint8_t g_gr_mode; 53 | 54 | // 4-bit low-res color. 55 | uint8_t g_gr_color_high; // High nybble. 56 | uint8_t g_gr_color_low; // Low nybble. 57 | 58 | // List of variable, in the same order they are in the zero page (starting at 59 | // FIRST_VARIABLE). 60 | VarInfo g_variables[MAX_VARIABLES]; 61 | 62 | // Stack of FOR loops. 63 | ForInfo g_for_info[MAX_FOR]; 64 | uint8_t g_for_count; 65 | 66 | // Space for arrays. 67 | uint16_t g_arrays[MAX_ARRAY_WORDS]; 68 | uint16_t g_arrays_size; 69 | 70 | /** 71 | * Clear the FOR stack. 72 | */ 73 | void clear_for_stack(void) { 74 | g_for_count = 0; 75 | } 76 | 77 | /** 78 | * Clear out the values of all variables and generally initialize runtime 79 | * state. 80 | */ 81 | void initialize_runtime(void) { 82 | memset((void *) FIRST_VARIABLE, 0, MAX_VARIABLES*2); 83 | clear_for_stack(); 84 | g_arrays_size = 0; 85 | } 86 | 87 | /** 88 | * Return the memory location of the zero-based (x,y) position on the screen. 89 | */ 90 | static uint8_t *screen_pos(uint16_t x, uint16_t y) { 91 | int16_t block = y >> 3; 92 | int16_t line = y & 0x07; 93 | 94 | return TEXT_PAGE1_BASE + line*SCREEN_STRIDE + block*SCREEN_WIDTH + x; 95 | } 96 | 97 | /** 98 | * Return the memory location of the cursor. 99 | */ 100 | uint8_t *cursor_pos(void) { 101 | return screen_pos(g_cursor_x, g_cursor_y); 102 | } 103 | 104 | /** 105 | * Shows the cursor. Safe to call if it's already showing. 106 | */ 107 | void show_cursor(void) { 108 | if (!g_showing_cursor) { 109 | uint8_t *pos = cursor_pos(); 110 | g_cursor_ch = *pos; 111 | *pos = CURSOR_GLYPH | 0x80; 112 | g_showing_cursor = 1; 113 | } 114 | } 115 | 116 | /** 117 | * Hides the cursor. Safe to call if it's not already shown. 118 | */ 119 | void hide_cursor(void) { 120 | if (g_showing_cursor) { 121 | uint8_t *pos = cursor_pos(); 122 | *pos = g_cursor_ch; 123 | g_showing_cursor = 0; 124 | } 125 | } 126 | 127 | /** 128 | * Moves the cursor to the specified location, where X 129 | * is 0 to 39 inclusive, Y is 0 to 23 inclusive. 130 | */ 131 | void move_cursor(int16_t x, int16_t y) { 132 | hide_cursor(); 133 | g_cursor_x = x; 134 | g_cursor_y = y; 135 | } 136 | 137 | /** 138 | * Blanks out the rest of the line, from the cursor (inclusive) on. 139 | * Does not move the cursor. 140 | */ 141 | void clear_to_eol(void) { 142 | uint8_t *pos = cursor_pos(); 143 | 144 | hide_cursor(); 145 | memset(pos, CLEAR_CHAR, SCREEN_WIDTH - g_cursor_x); 146 | } 147 | 148 | /** 149 | * Clear the screen with non-reversed spaces. 150 | */ 151 | void home(void) { 152 | if (g_gr_mode) { 153 | int i; 154 | 155 | for (i = MIXED_GRAPHICS_HEIGHT; i < SCREEN_HEIGHT; i++) { 156 | memset(screen_pos(0, i), CLEAR_CHAR, SCREEN_WIDTH); 157 | } 158 | 159 | move_cursor(0, MIXED_GRAPHICS_HEIGHT); 160 | } else { 161 | memset(TEXT_PAGE1_BASE, CLEAR_CHAR, SCREEN_STRIDE*8); 162 | move_cursor(0, 0); 163 | } 164 | } 165 | 166 | /** 167 | * Screen the screen up one line, blanking out the bottom 168 | * row. Does not affect the cursor. 169 | */ 170 | static void scroll_up(void) { 171 | int i; 172 | int first_line = g_gr_mode ? MIXED_GRAPHICS_HEIGHT : 0; 173 | uint8_t *previous_line = 0; 174 | 175 | for (i = first_line; i < SCREEN_HEIGHT; i++) { 176 | uint8_t *this_line = screen_pos(0, i); 177 | if (i > first_line) { 178 | memmove(previous_line, this_line, SCREEN_WIDTH); 179 | } 180 | previous_line = this_line; 181 | } 182 | 183 | memset(previous_line, CLEAR_CHAR, SCREEN_WIDTH); 184 | } 185 | 186 | /** 187 | * Print a single newline. 188 | */ 189 | void print_newline(void) { 190 | if (g_cursor_y == SCREEN_HEIGHT - 1) { 191 | // Scroll. 192 | hide_cursor(); 193 | scroll_up(); 194 | move_cursor(0, g_cursor_y); 195 | } else { 196 | move_cursor(0, g_cursor_y + 1); 197 | } 198 | } 199 | 200 | /** 201 | * Prints the character and advances the cursor. Handles newlines. 202 | */ 203 | void print_char(uint8_t c) { 204 | uint8_t *loc = cursor_pos(); 205 | 206 | if (c == '\n') { 207 | print_newline(); 208 | } else { 209 | // Print character. 210 | *loc = c | 0x80; 211 | 212 | // Advance cursor or wrap. 213 | if (g_cursor_x == SCREEN_WIDTH - 1) { 214 | print_newline(); 215 | } else { 216 | move_cursor(g_cursor_x + 1, g_cursor_y); 217 | } 218 | } 219 | } 220 | 221 | /** 222 | * Print a string at the cursor. 223 | */ 224 | void print(uint8_t *s) { 225 | while (*s != '\0') { 226 | print_char(*s++); 227 | } 228 | } 229 | 230 | /** 231 | * Print an unsigned integer. 232 | */ 233 | void print_uint(uint16_t i) { 234 | // Is this the best way to do this? I've seen it done backwards, where 235 | // digits are added to a buffer least significant digit first, then reversed, 236 | // but this seems faster. 237 | char printed = 0; 238 | 239 | if (i >= 10000) { 240 | int16_t r = i / 10000; 241 | print_char('0' + r); 242 | i -= r*10000; 243 | printed = 1; 244 | } 245 | if (i >= 1000 || printed) { 246 | int16_t r = i / 1000; 247 | print_char('0' + r); 248 | i -= r*1000; 249 | printed = 1; 250 | } 251 | if (i >= 100 || printed) { 252 | int16_t r = i / 100; 253 | print_char('0' + r); 254 | i -= r*100; 255 | printed = 1; 256 | } 257 | if (i >= 10 || printed) { 258 | int16_t r = i / 10; 259 | print_char('0' + r); 260 | i -= r*10; 261 | } 262 | print_char('0' + i); 263 | } 264 | 265 | /** 266 | * Print a signed integer. 267 | */ 268 | void print_int(int16_t i) { 269 | if ((i & 0x8000) != 0) { 270 | print_char('-'); 271 | i = -i; 272 | } 273 | 274 | print_uint((uint16_t) i); 275 | } 276 | 277 | /** 278 | * Print an error message, optionally with a line number if it's 279 | * not INVALID_LINE_NUMBER. 280 | */ 281 | static void generic_error_message(uint8_t *message, uint16_t line_number) { 282 | print("\n?"); 283 | print(message); 284 | print(" ERROR"); 285 | 286 | if (line_number != INVALID_LINE_NUMBER) { 287 | print(" IN "); 288 | print_uint(line_number); 289 | } 290 | } 291 | 292 | /** 293 | * Display a syntax error message. 294 | */ 295 | void syntax_error(uint16_t line_number) { 296 | generic_error_message("SYNTAX", line_number); 297 | } 298 | 299 | /** 300 | * Display an error for a GOTO that went to a line that doesn't exist. 301 | */ 302 | void undefined_statement_error(uint16_t line_number) { 303 | generic_error_message("UNDEF'D STATEMENT", line_number); 304 | } 305 | 306 | /** 307 | * Display an out-of-memory error, which could also mean that various 308 | * stacks have been overflowed. 309 | */ 310 | void out_of_memory_error(uint16_t line_number) { 311 | generic_error_message("OUT OF MEMORY", line_number); 312 | } 313 | 314 | /** 315 | * Display an error for when the user does a NEXT without a matching FOR. 316 | */ 317 | void next_without_for_error(uint16_t line_number) { 318 | generic_error_message("NEXT WITHOUT FOR", line_number); 319 | } 320 | 321 | /** 322 | * Display an error for when the user does a DIM on a variable a second time. 323 | */ 324 | void redimd_array_error(uint16_t line_number) { 325 | generic_error_message("REDIM'D ARRAY", line_number); 326 | } 327 | 328 | /** 329 | * Switch to graphics mode. 330 | */ 331 | void gr_statement(void) { 332 | if (!g_gr_mode) { 333 | int i; 334 | // Mixed text and lo-res graphics mode. 335 | 336 | hide_cursor(); 337 | 338 | *TEXT_OFF_SWITCH = 0; 339 | *MIXED_ON_SWITCH = 0; 340 | 341 | // Clear the graphics area. 342 | for (i = 0; i < MIXED_GRAPHICS_HEIGHT; i++) { 343 | memset(screen_pos(0, i), 0, SCREEN_WIDTH); 344 | } 345 | 346 | // Move the cursor to the text window. 347 | if (g_cursor_y < MIXED_GRAPHICS_HEIGHT) { 348 | move_cursor(0, MIXED_GRAPHICS_HEIGHT); 349 | } 350 | 351 | g_gr_mode = 1; 352 | } 353 | } 354 | 355 | /** 356 | * Switch to text mode. 357 | */ 358 | void text_statement(void) { 359 | if (g_gr_mode) { 360 | // Text mode. 361 | *TEXT_ON_SWITCH = 0; 362 | 363 | hide_cursor(); 364 | 365 | g_gr_mode = 0; 366 | } 367 | } 368 | 369 | /** 370 | * Set the low-res color. 371 | */ 372 | void color_statement(uint16_t color) { 373 | g_gr_color_high = (uint8_t) ((color << 4) & 0xF0); 374 | g_gr_color_low = (uint8_t) (color & 0x0F); 375 | } 376 | 377 | /** 378 | * Plot a pixel in low-res graphics mode. 379 | */ 380 | void plot_statement(uint16_t x, uint16_t y) { 381 | uint8_t *pos = screen_pos(x, y >> 1); 382 | 383 | if ((y & 1) == 0) { 384 | // Even, bottom pixel. 385 | *pos = (*pos & 0xF0) | g_gr_color_low; 386 | } else { 387 | // Odd, top pixel. 388 | *pos = (*pos & 0x0F) | g_gr_color_high; 389 | } 390 | } 391 | 392 | /** 393 | * Find a FOR loop info structure by variable address, or null if not found. 394 | */ 395 | static ForInfo *find_for_info(uint16_t var_address) { 396 | int i; 397 | ForInfo *f; 398 | 399 | // Work backwards through the stack, since the variable we're looking 400 | // for is likely to be the most recent one. 401 | f = &g_for_info[g_for_count - 1]; 402 | for (i = g_for_count - 1; i >= 0; i--) { 403 | if (f->var_address == var_address) { 404 | // Found it. 405 | return f; 406 | } 407 | 408 | f -= 1; 409 | } 410 | 411 | return 0; 412 | } 413 | 414 | /** 415 | * Remove any FOR loop for this variable anywhere in the stack, if any. 416 | */ 417 | static void remove_for_info(uint16_t var_address) { 418 | ForInfo *f = find_for_info(var_address); 419 | 420 | if (f != 0) { 421 | // Compute the index of this entry. 422 | int index = f - g_for_info; 423 | 424 | // Shift the rest over. 425 | memmove(f, f + 1, sizeof(ForInfo)*(g_for_count - index - 1)); 426 | g_for_count -= 1; 427 | } 428 | } 429 | 430 | /** 431 | * Push a FOR statement on the stack. 432 | */ 433 | void for_statement(uint16_t line_number, uint16_t var_address, int16_t end_value, int16_t step, 434 | uint16_t loop_top_addr) { 435 | 436 | // First, kill any existing loop for this variable. 437 | remove_for_info(var_address); 438 | 439 | // Add the loop to our stack. 440 | if (g_for_count == MAX_FOR) { 441 | // TODO should quit program. Return a failure value, and have called return. 442 | out_of_memory_error(line_number); 443 | } else { 444 | ForInfo *f = &g_for_info[g_for_count++]; 445 | 446 | f->var_address = var_address; 447 | f->end_value = end_value; 448 | f->step = step; 449 | f->loop_top_addr = loop_top_addr; 450 | } 451 | } 452 | 453 | /** 454 | * Handle a NEXT statement. Returns the address to jump to at the top of the loop, 455 | * or zero to not jump. 456 | */ 457 | uint16_t next_statement(uint16_t line_number, uint16_t var_address) { 458 | ForInfo *f; 459 | uint16_t jump_addr = 0; 460 | 461 | if (var_address == 0) { 462 | // Pick top of stack. 463 | if (g_for_count == 0) { 464 | // Stack is empty. 465 | f = 0; 466 | } else { 467 | f = &g_for_info[g_for_count - 1]; 468 | } 469 | } else { 470 | f = find_for_info(var_address); 471 | } 472 | 473 | if (f == 0) { 474 | next_without_for_error(line_number); 475 | } else { 476 | uint16_t *var; 477 | 478 | // Pop off every loop below us in the stack. 479 | g_for_count = f - g_for_info + 1; 480 | 481 | // Step the loop variable. 482 | var = (uint16_t *) f->var_address; 483 | *var += f->step; 484 | // TODO if the step is negative, switch inequality here: 485 | if (*var > f->end_value) { 486 | // We're done, remove our FOR loop. 487 | g_for_count -= 1; 488 | 489 | // Continue after the NEXT statement. 490 | } else { 491 | // Loop back to the top of the NEXT statement. 492 | jump_addr = f->loop_top_addr; 493 | } 494 | } 495 | 496 | return jump_addr; 497 | } 498 | 499 | /** 500 | * Allocate an array. Size is in words, and var_addr points to 501 | * the variable that will store the array location. 502 | */ 503 | void allocate_array(uint16_t size, uint16_t var_addr) { 504 | // Actual size is one more. DIM X(10) allocates 11-entry array. 505 | size += 1; 506 | 507 | // Check for overflow. 508 | if (g_arrays_size + size > MAX_ARRAY_WORDS) { 509 | print("Too many arrays.\n"); 510 | } else { 511 | // Allocate next chunk. 512 | *(uint16_t *) var_addr = (uint16_t) (g_arrays + g_arrays_size); 513 | g_arrays_size += size; 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /runtime.h: -------------------------------------------------------------------------------- 1 | #ifndef __RUNTIME_H__ 2 | #define __RUNTIME_H__ 3 | 4 | #include "platform.h" 5 | 6 | // Line number used for "no line number". 7 | #define INVALID_LINE_NUMBER 0xFFFF 8 | 9 | // Maximum number of variables. These fit in the zero page. 10 | #define MAX_VARIABLES 32 11 | 12 | // Location of first variable in zero page. See zeropage.s and zeropage.inc 13 | // in the compiler tree. They seem to use 26 bytes, so we start after that. 14 | // Each variable takes two bytes (int16_t). 15 | #define FIRST_VARIABLE 26 16 | 17 | // Data types for variables. 18 | #define DT_INT 0 19 | #define DT_ARRAY 1 20 | 21 | typedef struct { 22 | // The name of the variable, with the first letter in the lower byte 23 | // and the second letter (or nul) in the higher byte. Zero indicates 24 | // that this entry is unused. 25 | uint16_t name; 26 | 27 | // Data type of the variable. See DT_ constants above. This is also the 28 | // namespace that the variable name is in. 29 | uint8_t data_type; 30 | } VarInfo; 31 | 32 | extern uint16_t g_cursor_x; 33 | extern uint16_t g_cursor_y; 34 | extern uint16_t g_showing_cursor; 35 | extern uint8_t g_cursor_ch; 36 | extern VarInfo g_variables[MAX_VARIABLES]; 37 | 38 | void initialize_runtime(void); 39 | void clear_for_stack(void); 40 | 41 | uint8_t *cursor_pos(void); 42 | void show_cursor(void); 43 | void hide_cursor(void); 44 | void move_cursor(int16_t x, int16_t y); 45 | 46 | void clear_to_eol(void); 47 | 48 | void home(void); 49 | 50 | void allocate_array(uint16_t size, uint16_t var_addr); 51 | 52 | void print(uint8_t *s); 53 | void print_char(uint8_t c); 54 | void print_uint(uint16_t i); 55 | void print_int(int16_t i); 56 | void print_newline(void); 57 | 58 | void for_statement(uint16_t line_number, uint16_t var_address, int16_t end_value, int16_t step, 59 | uint16_t loop_top_addr); 60 | uint16_t next_statement(uint16_t line_number, uint16_t var_address); 61 | 62 | void syntax_error(uint16_t line_number); 63 | void syntax_error_in_line(uint16_t line_number); 64 | void undefined_statement_error(uint16_t line_number); 65 | void redimd_array_error(uint16_t line_number); 66 | 67 | void gr_statement(void); 68 | void text_statement(void); 69 | void color_statement(uint16_t color); 70 | void plot_statement(uint16_t x, uint16_t y); 71 | 72 | #endif // __RUNTIME_H__ 73 | -------------------------------------------------------------------------------- /supervision.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradgrantham/apple2a/d041ebd995612493f604525dac813aad7f83621e/supervision.lib -------------------------------------------------------------------------------- /vectors.s: -------------------------------------------------------------------------------- 1 | ; --------------------------------------------------------------------------- 2 | ; vectors.s 3 | ; --------------------------------------------------------------------------- 4 | ; 5 | ; Defines the interrupt vector table. 6 | 7 | .import _init 8 | .import _nmi_int, _irq_int 9 | 10 | .segment "VECTORS" 11 | 12 | .addr _nmi_int ; NMI vector 13 | .addr _init ; Reset vector 14 | .addr _irq_int ; IRQ/BRK vector 15 | 16 | --------------------------------------------------------------------------------