Acorn BBC BASIC II

Updated 9 Jul 2026

← All Acorn BBC BASIC versions

BASIC v&01

Sideways ROM header — language-entry slot (3 bytes)

MOS dispatches JMP &8000 on language startup.

Byte 0 is &c9 — the language entry is inline code, not a JMP abs. This ROM declares itself a language (rom_type bit 6 set) and the MOS enters by calling &8000 directly, so the bytes are executed in place (e.g. BBC BASIC's CMP #1 / BEQ / RTS).

Reason code in A on entry:

A Meaning
0 No language available — MOS calling Tube ROM
1 Normal startup
2 Request next byte of softkey expansion (Electron)
3 Request length of softkey expansion (Electron)
8000 .language_entry
CMP #1 ; A=1: language start?
8002 BEQ language_startup ; yes: start BASIC
8004 RTS ; otherwise return
8005 EQUB &EA
8006 .rom_type
; ROM type byte
; ┌─────┬───────┬─────────────────────────────────┐
; │ Bit │ Value │ Meaning │
; ├─────┼───────┼─────────────────────────────────┤
; │ 7 │ 0 │ No service entry │
; │ 6 │ 1 │ Language entry present │
; │ 5 │ 1 │ Tube relocation address present │
; │ 4 │ 0 │ No Electron firmkey │
; │ 3-0 │ 0000 │ Processor: 6502 BASIC │
; └─────┴───────┴─────────────────────────────────┘
EQUB %01100000 ; ROM type
8007 .copyright_offset
EQUB copyright - language_entry ; Offset of NUL preceding copyright (= &0e → copyright at &800e)
8008 .binary_version
EQUB &01 ; Binary version: &01 (informational, not used by MOS)
8009 .title
EQUS "BASIC"
800E .copyright
EQUB &00 ; NUL preceding copyright string
800F .copyright_string
EQUS "(C)1982 Acorn", &0A, &0D
801E EQUB &00 ; NUL terminator
801F .tube_reloc_addr
EQUB &00, &80, ; Tube relocation address (32-bit LE) — &00, &00 ; where the ROM body relocates on a Tube ; co-processor

Language startup

Reached from the language entry when the MOS starts BASIC (A = 1). Reads HIMEM and PAGE from the MOS, clears the print and formatting state, seeds the random-number generator if it is cold, installs the BASIC error handler in BRKV, and jumps to start_new_program, which clears any program and enters the immediate ("> ") loop.

On EntryA1 (MOS language-start reason code)
On ExitZP_HIMEM (&06/&07) / ZP_PAGE (&18)read from the MOS
CONTROLjumps to start_new_program (does not return)
8023 .language_startup←1← 8002 BEQ
LDA #osbyte_read_himem ; OSBYTE &84: read HIMEM
8025 JSR osbyte ; Read top of available user RAM (HIMEM)
8028 STX zp_himem ; X and Y contain the address of HIMEM (low, high)
802A STY zp_himem_1 ; HIMEM high byte
802C LDA #osbyte_read_oshwm ; OSBYTE &83: read OSHWM (PAGE)
802E JSR osbyte ; Read top of operating system RAM address (OSHWM)
8031 STY zp_page ; X and Y contain the address of OSHWM (low, high)
8033 LDX #0 ; X = 0
8035 STX zp_listo ; LISTO = 0: no LIST indentation
8037 STX resint_at_2 ; @% high two bytes = 0
803A STX resint_at_3 ; @% byte 3 = 0
803D DEX ; X = &FF
803E STX zp_width ; WIDTH = &FF: no automatic line wrap
8040 LDX #&0a ; @% = &0000090A: byte 0 = &0A
8042 STX resint_at ; @% = &0000090A: default PRINT format
8045 DEX ; X = &09
8046 STX resint_at_1 ; @% byte 1 = &09
8049 LDA #1 ; Test the LFSR state for all-zero...
804B AND zp_rnd_seed_4 ; ...only bit 0 of &11 belongs to the register
804D ORA zp_rnd_seed ; OR bits 0-7
804F ORA zp_rnd_seed_1 ; bits 8-15
8051 ORA zp_rnd_seed_2 ; bits 16-23
8053 ORA zp_rnd_seed_3 ; bits 24-31
8055 BNE lang_install_brkv ; Non-zero: keep the existing state
; The generator needs a non-zero state. At power-on the work
; area is zero, so a fixed seed is installed here. The three
; bytes &41/&52/&57 are the ASCII codes of Wilson's initials,
; a nod to BBC BASIC's author. Nothing else re-seeds unless the
; program calls RND(-n), so a sequence is deterministic from a
; cold start.
8057 LDA #'A' ; Cold seed: state becomes &00575241; load &41
8059 STA zp_rnd_seed ; byte 0
805B LDA #'R' ; load &52
805D STA zp_rnd_seed_1 ; byte 1
805F LDA #'W' ; load &57
8061 STA zp_rnd_seed_2 ; byte 2 (bytes 3 and 4 stay zero)
8063 .lang_install_brkv←1← 8055 BNE
LDA #2 ; Install brk_handler (&B402) into BRKV
8065 STA brkv ; (BRKV low)
8068 LDA #&b4 ; high byte &B4
806A STA brkv+1 ; (BRKV high)
806D CLI ; Enable IRQs and enter the immediate loop
806E JMP start_new_program ; Enter via NEW and the immediate loop

Keyword / tokeniser table

Each entry is the keyword in ASCII, then a token byte (bit 7 set), then a flag byte that drives tokenising. The flag bits are:

Bit Meaning
0 Conditional: do not tokenise if followed by a name char (0-9 A-Z a-z _)
1 Enter "middle of statement" mode
2 Enter "start of statement" mode
3 FN/PROC: do not tokenise the following name
4 Start tokenising a line number (after GOTO etc.)
5 Do not tokenise the rest of the line (REM, DATA)
6 Pseudo-variable: add &40 to the token in a statement

Bit 6 is why a pseudo-variable like PTR reads as &8F in a function position but &CF as an assignment target. Entries are ordered so that the first acceptable abbreviation of each keyword is unambiguous.

The tokeniser scan stops at WIDTH's token &FE, which doubles as the end-of-table sentinel (see the cmp #&fe in the keyword search). The final five entries — the PTR/PAGE/TIME/LOMEM/HIMEM assignment forms &CF-&D3 — sit past that sentinel, so the tokeniser never matches them (it produces those tokens arithmetically via bit 6). They exist only for the de-tokeniser (print_token, which has no sentinel and scans by token value) to render PTR= etc. back to text. The data then runs on to the action-address tables at action_table_lo.

8071 .keyword_table
EQUS "AND"
8074 EQUB &80, &00
8076 EQUS "ABS"
8079 EQUB &94, &00
807B EQUS "ACS"
807E EQUB &95, &00
8080 EQUS "ADVAL"
8085 EQUB &96, &00
8087 EQUS "ASC"
808A EQUB &97, &00
808C EQUS "ASN"
808F EQUB &98, &00
8091 EQUS "ATN"
8094 EQUB &99, &00
8096 EQUS "AUTO"
809A EQUB &C6, &10
809C EQUS "BGET"
80A0 EQUB &9A, &01
80A2 EQUS "BPUT"
80A6 EQUB &D5, &03
80A8 EQUS "COLOUR"
80AE EQUB &FB, &02
80B0 EQUS "CALL"
80B4 EQUB &D6, &02
80B6 EQUS "CHAIN"
80BB EQUB &D7, &02
80BD EQUS "CHR$"
80C1 EQUB &BD, &00
80C3 EQUS "CLEAR"
80C8 EQUB &D8, &01
80CA EQUS "CLOSE"
80CF EQUB &D9, &03
80D1 EQUS "CLG"
80D4 EQUB &DA, &01
80D6 EQUS "CLS"
80D9 EQUB &DB, &01
80DB EQUS "COS"
80DE EQUB &9B, &00
80E0 EQUS "COUNT"
80E5 EQUB &9C, &01
80E7 EQUS "DATA"
80EB EQUB &DC
80EC EQUS " DEG"
80F0 EQUB &9D, &00
80F2 EQUS "DEF"
80F5 EQUB &DD, &00
80F7 EQUS "DELETE"
80FD EQUB &C7, &10
80FF EQUS "DIV"
8102 EQUB &81, &00
8104 EQUS "DIM"
8107 EQUB &DE, &02
8109 EQUS "DRAW"
810D EQUB &DF, &02
810F EQUS "ENDPROC"
8116 EQUB &E1, &01
8118 EQUS "END"
811B EQUB &E0, &01
811D EQUS "ENVELOPE"
8125 EQUB &E2, &02
8127 EQUS "ELSE"
812B EQUB &8B, &14
812D EQUS "EVAL"
8131 EQUB &A0, &00
8133 EQUS "ERL"
8136 EQUB &9E, &01
8138 EQUS "ERROR"
813D EQUB &85, &04
813F EQUS "EOF"
8142 EQUB &C5, &01
8144 EQUS "EOR"
8147 EQUB &82, &00
8149 EQUS "ERR"
814C EQUB &9F, &01
814E EQUS "EXP"
8151 EQUB &A1, &00
8153 EQUS "EXT"
8156 EQUB &A2, &01
8158 EQUS "FOR"
815B EQUB &E3, &02
815D EQUS "FALSE"
8162 EQUB &A3, &01, &46, &4E, &A4, &08
8168 EQUS "GOTO"
816C EQUB &E5, &12
816E EQUS "GET$"
8172 EQUB &BE, &00
8174 EQUS "GET"
8177 EQUB &A5, &00
8179 EQUS "GOSUB"
817E EQUB &E4, &12
8180 EQUS "GCOL"
8184 EQUB &E6, &02
8186 EQUS "HIMEM"
818B EQUB &93
818C EQUS "CINPUT"
8192 EQUB &E8, &02, &49, &46, &E7, &02
8198 EQUS "INKEY$"
819E EQUB &BF, &00
81A0 EQUS "INKEY"
81A5 EQUB &A6, &00
81A7 EQUS "INT"
81AA EQUB &A8, &00
81AC EQUS "INSTR("
81B2 EQUB &A7, &00
81B4 EQUS "LIST"
81B8 EQUB &C9, &10
81BA EQUS "LINE"
81BE EQUB &86, &00
81C0 EQUS "LOAD"
81C4 EQUB &C8, &02
81C6 EQUS "LOMEM"
81CB EQUB &92
81CC EQUS "CLOCAL"
81D2 EQUB &EA, &02
81D4 EQUS "LEFT$("
81DA EQUB &C0, &00
81DC EQUS "LEN"
81DF EQUB &A9, &00
81E1 EQUS "LET"
81E4 EQUB &E9, &04
81E6 EQUS "LOG"
81E9 EQUB &AB, &00, &4C, &4E, &AA, &00
81EF EQUS "MID$("
81F4 EQUB &C1, &00
81F6 EQUS "MODE"
81FA EQUB &EB, &02
81FC EQUS "MOD"
81FF EQUB &83, &00
8201 EQUS "MOVE"
8205 EQUB &EC, &02
8207 EQUS "NEXT"
820B EQUB &ED, &02
820D EQUS "NEW"
8210 EQUB &CA, &01
8212 EQUS "NOT"
8215 EQUB &AC, &00
8217 EQUS "OLD"
821A EQUB &CB, &01, &4F, &4E, &EE, &02
8220 EQUS "OFF"
8223 EQUB &87, &00, &4F, &52, &84, &00
8229 EQUS "OPENIN"
822F EQUB &8E, &00
8231 EQUS "OPENOUT"
8238 EQUB &AE, &00
823A EQUS "OPENUP"
8240 EQUB &AD, &00
8242 EQUS "OSCLI"
8247 EQUB &FF, &02
8249 EQUS "PRINT"
824E EQUB &F1, &02
8250 EQUS "PAGE"
8254 EQUB &90
8255 EQUS "CPTR"
8259 EQUB &8F
825A EQUS "CPI"
825D EQUB &AF, &01
825F EQUS "PLOT"
8263 EQUB &F0, &02
8265 EQUS "POINT("
826B EQUB &B0, &00
826D EQUS "PROC"
8271 EQUB &F2, &0A
8273 EQUS "POS"
8276 EQUB &B1, &01
8278 EQUS "RETURN"
827E EQUB &F8, &01
8280 EQUS "REPEAT"
8286 EQUB &F5, &00
8288 EQUS "REPORT"
828E EQUB &F6, &01
8290 EQUS "READ"
8294 EQUB &F3, &02
8296 EQUS "REM"
8299 EQUB &F4
829A EQUS " RUN"
829E EQUB &F9, &01
82A0 EQUS "RAD"
82A3 EQUB &B2, &00
82A5 EQUS "RESTORE"
82AC EQUB &F7, &12
82AE EQUS "RIGHT$("
82B5 EQUB &C2, &00
82B7 EQUS "RND"
82BA EQUB &B3, &01
82BC EQUS "RENUMBER"
82C4 EQUB &CC, &10
82C6 EQUS "STEP"
82CA EQUB &88, &00
82CC EQUS "SAVE"
82D0 EQUB &CD, &02
82D2 EQUS "SGN"
82D5 EQUB &B4, &00
82D7 EQUS "SIN"
82DA EQUB &B5, &00
82DC EQUS "SQR"
82DF .action_lo_by_token←1Used as index base by← 8BB2 LDA
EQUB &B6, &00
82E1 EQUS "SPC"
82E4 EQUB &89, &00
82E6 EQUS "STR$"
82EA EQUB &C3, &00
82EC EQUS "STRING$("
82F4 EQUB &C4, &00
82F6 EQUS "SOUND"
82FB EQUB &D4, &02
82FD EQUS "STOP"
8301 EQUB &FA, &01
8303 EQUS "TAN"
8306 EQUB &B7, &00
8308 EQUS "THEN"
830C EQUB &8C, &14, &54, &4F, &B8, &00
8312 EQUS "TAB("
8316 EQUB &8A, &00
8318 EQUS "TRACE"
831D EQUB &FC, &12
831F EQUS "TIME"
8323 EQUB &91
8324 EQUS "CTRUE"
8329 EQUB &B9, &01
832B EQUS "UNTIL"
8330 EQUB &FD, &02
8332 EQUS "USR"
8335 EQUB &BA, &00
8337 EQUS "VDU"
833A EQUB &EF, &02
833C EQUS "VAL"
833F EQUB &BB, &00
8341 EQUS "VPOS"
8345 EQUB &BC, &01
8347 EQUS "WIDTH"
834C EQUB &FE, &02
834E EQUS "PAG"
8351 .action_hi_by_token←1Used as index base by← 8BB7 LDA
EQUB &45, &D0, &00
8354 EQUS "PTR"
8357 EQUB &CF, &00
8359 EQUS "TIME"
835D EQUB &D1, &00
835F EQUS "LOMEM"
8364 EQUB &D2, &00
8366 EQUS "HIMEM"
836B EQUB &D3, &00
836D .action_table_lo
EQUB <(fn_openin)
836E EQUB <(fn_ptr)
836F EQUB <(fn_page)
8370 EQUB <(fn_time)
8371 EQUB <(fn_lomem)
8372 EQUB <(fn_himem)
8373 EQUB <(fn_abs)
8374 EQUB <(fn_acs)
8375 EQUB <(fn_adval)
8376 EQUB <(fn_asc)
8377 EQUB <(fn_asn)
8378 EQUB <(fn_atn)
8379 EQUB <(fn_bget)
837A EQUB <(fn_cos)
837B EQUB <(fn_count)
837C EQUB <(fn_deg)
837D EQUB <(fn_erl)
837E EQUB <(fn_err)
837F EQUB <(fn_eval)
8380 EQUB <(fn_exp)
8381 EQUB <(fn_ext)
8382 EQUB <(fn_false)
8383 EQUB <(fn_fn)
8384 EQUB <(fn_get)
8385 EQUB <(fn_inkey)
8386 EQUB <(fn_instr)
8387 EQUB <(fn_int)
8388 EQUB <(fn_len)
8389 EQUB <(fn_ln)
838A EQUB <(fn_log)
838B EQUB <(fn_not)
838C EQUB <(fn_openup)
838D EQUB <(fn_openout)
838E EQUB <(fn_pi)
838F EQUB <(fn_point)
8390 EQUB <(fn_pos)
8391 EQUB <(fn_rad)
8392 EQUB <(fn_rnd)
8393 EQUB <(fn_sgn)
8394 EQUB <(fn_sin)
8395 EQUB <(fn_sqr)
8396 EQUB <(fn_tan)
8397 EQUB <(fn_to)
8398 EQUB <(fn_true)
8399 EQUB <(fn_usr)
839A EQUB <(fn_val)
839B EQUB <(fn_vpos)
839C EQUB <(fn_chrs)
839D EQUB <(fn_gets)
839E EQUB <(fn_inkeys)
839F EQUB <(fn_lefts)
83A0 EQUB <(fn_mids)
83A1 EQUB <(fn_rights)
83A2 EQUB <(fn_strs)
83A3 EQUB <(fn_strings)
83A4 EQUB <(fn_eof)
83A5 EQUB <(stmt_auto)
83A6 EQUB <(stmt_delete)
83A7 EQUB <(stmt_load)
83A8 EQUB <(stmt_list)
83A9 EQUB <(stmt_new)
83AA EQUB <(stmt_old)
83AB EQUB <(stmt_renumber)
83AC EQUB <(stmt_save)
83AD EQUB <(syntax_error)
83AE EQUB <(stmt_ptr)
83AF EQUB <(stmt_page)
83B0 EQUB <(stmt_time)
83B1 EQUB <(stmt_lomem)
83B2 EQUB <(stmt_himem)
83B3 EQUB <(stmt_sound)
83B4 EQUB <(stmt_bput)
83B5 EQUB <(stmt_call)
83B6 EQUB <(stmt_chain)
83B7 EQUB <(stmt_clear)
83B8 EQUB <(stmt_close)
83B9 EQUB <(stmt_clg)
83BA EQUB <(stmt_cls)
83BB EQUB <(stmt_data)
83BC EQUB <(stmt_data)
83BD EQUB <(stmt_dim)
83BE EQUB <(stmt_draw)
83BF EQUB <(stmt_end)
83C0 EQUB <(stmt_endproc)
83C1 EQUB <(stmt_envelope)
83C2 EQUB <(stmt_for)
83C3 EQUB <(stmt_gosub)
83C4 EQUB <(stmt_goto)
83C5 EQUB <(stmt_gcol)
83C6 EQUB <(stmt_if)
83C7 EQUB <(stmt_input)
83C8 EQUB <(stmt_let)
83C9 EQUB <(stmt_local)
83CA EQUB <(stmt_mode)
83CB EQUB <(stmt_move)
83CC EQUB <(stmt_next)
83CD EQUB <(stmt_on)
83CE EQUB <(stmt_vdu)
83CF EQUB <(stmt_plot)
83D0 EQUB <(stmt_print)
83D1 EQUB <(stmt_proc)
83D2 EQUB <(stmt_read)
83D3 EQUB <(stmt_data)
83D4 EQUB <(stmt_repeat)
83D5 EQUB <(stmt_report)
83D6 EQUB <(stmt_restore)
83D7 EQUB <(stmt_return)
83D8 EQUB <(stmt_run)
83D9 EQUB <(stmt_stop)
83DA EQUB <(stmt_colour)
83DB EQUB <(stmt_trace)
83DC EQUB <(stmt_until)
83DD EQUB <(stmt_width)
83DE EQUB <(stmt_oscli)
83DF .action_table_hi
EQUB >(fn_openin)
83E0 EQUB >(fn_ptr)
83E1 EQUB >(fn_page)
83E2 EQUB >(fn_time)
83E3 EQUB >(fn_lomem)
83E4 EQUB >(fn_himem)
83E5 EQUB >(fn_abs)
83E6 EQUB >(fn_acs)
83E7 EQUB >(fn_adval)
83E8 EQUB >(fn_asc)
83E9 EQUB >(fn_asn)
83EA EQUB >(fn_atn)
83EB EQUB >(fn_bget)
83EC EQUB >(fn_cos)
83ED EQUB >(fn_count)
83EE EQUB >(fn_deg)
83EF EQUB >(fn_erl)
83F0 EQUB >(fn_err)
83F1 EQUB >(fn_eval)
83F2 EQUB >(fn_exp)
83F3 EQUB >(fn_ext)
83F4 EQUB >(fn_false)
83F5 EQUB >(fn_fn)
83F6 EQUB >(fn_get)
83F7 EQUB >(fn_inkey)
83F8 EQUB >(fn_instr)
83F9 EQUB >(fn_int)
83FA EQUB >(fn_len)
83FB EQUB >(fn_ln)
83FC EQUB >(fn_log)
83FD EQUB >(fn_not)
83FE EQUB >(fn_openup)
83FF EQUB >(fn_openout)
8400 EQUB >(fn_pi)
8401 EQUB >(fn_point)
8402 EQUB >(fn_pos)
8403 EQUB >(fn_rad)
8404 EQUB >(fn_rnd)
8405 EQUB >(fn_sgn)
8406 EQUB >(fn_sin)
8407 EQUB >(fn_sqr)
8408 EQUB >(fn_tan)
8409 EQUB >(fn_to)
840A EQUB >(fn_true)
840B EQUB >(fn_usr)
840C EQUB >(fn_val)
840D EQUB >(fn_vpos)
840E EQUB >(fn_chrs)
840F EQUB >(fn_gets)
8410 EQUB >(fn_inkeys)
8411 EQUB >(fn_lefts)
8412 EQUB >(fn_mids)
8413 EQUB >(fn_rights)
8414 EQUB >(fn_strs)
8415 EQUB >(fn_strings)
8416 EQUB >(fn_eof)
8417 EQUB >(stmt_auto)
8418 EQUB >(stmt_delete)
8419 EQUB >(stmt_load)
841A EQUB >(stmt_list)
841B EQUB >(stmt_new)
841C EQUB >(stmt_old)
841D EQUB >(stmt_renumber)
841E EQUB >(stmt_save)
841F EQUB >(syntax_error)
8420 EQUB >(stmt_ptr)
8421 EQUB >(stmt_page)
8422 EQUB >(stmt_time)
8423 EQUB >(stmt_lomem)
8424 EQUB >(stmt_himem)
8425 EQUB >(stmt_sound)
8426 EQUB >(stmt_bput)
8427 EQUB >(stmt_call)
8428 EQUB >(stmt_chain)
8429 EQUB >(stmt_clear)
842A EQUB >(stmt_close)
842B EQUB >(stmt_clg)
842C EQUB >(stmt_cls)
842D EQUB >(stmt_data)
842E EQUB >(stmt_data)
842F EQUB >(stmt_dim)
8430 EQUB >(stmt_draw)
8431 EQUB >(stmt_end)
8432 EQUB >(stmt_endproc)
8433 EQUB >(stmt_envelope)
8434 EQUB >(stmt_for)
8435 EQUB >(stmt_gosub)
8436 EQUB >(stmt_goto)
8437 EQUB >(stmt_gcol)
8438 EQUB >(stmt_if)
8439 EQUB >(stmt_input)
843A EQUB >(stmt_let)
843B EQUB >(stmt_local)
843C EQUB >(stmt_mode)
843D EQUB >(stmt_move)
843E EQUB >(stmt_next)
843F EQUB >(stmt_on)
8440 EQUB >(stmt_vdu)
8441 EQUB >(stmt_plot)
8442 EQUB >(stmt_print)
8443 EQUB >(stmt_proc)
8444 EQUB >(stmt_read)
8445 EQUB >(stmt_data)
8446 EQUB >(stmt_repeat)
8447 EQUB >(stmt_report)
8448 EQUB >(stmt_restore)
8449 EQUB >(stmt_return)
844A EQUB >(stmt_run)
844B EQUB >(stmt_stop)
844C EQUB >(stmt_colour)
844D EQUB >(stmt_trace)
844E EQUB >(stmt_until)
844F EQUB >(stmt_width)

Inline-assembler mnemonic tables

Three parallel byte arrays, all indexed by the same X, that turn a typed mnemonic into a base opcode:

Array Holds
asm_mnemonic_lo low byte of the mnemonic's packed-name hash
asm_mnemonic_hi high byte of the mnemonic's packed-name hash
asm_base_opcode the base ("mode 0") opcode for that mnemonic

asm_parse_mnemonic (&85BA) reads the three mnemonic letters, keeps the low 5 bits of each (A=1..Z=26) and packs them MSB-first into a 15-bit value in &3D (low) / &3E (high). asm_mn_search (&85F1) then scans X from &3A down to 1, comparing the low half against asm_mnemonic_lo,x and, on a hit, the high half against asm_mnemonic_hi,x; the matching index selects asm_base_opcode,x. Tokenised AND/EOR/OR reach the same indices directly via asm_logic_mnemonic (&8607).

Because the two hash tables are pure functions of the mnemonic text, each asm_mnemonic_lo / asm_mnemonic_hi byte is emitted as the beebasm expression that re-derives it from the three letters (the assembled value is shown in the address comment). asm_base_opcode holds real 6502 opcodes and stays a plain byte.

The index order is meaningful: the operand parser keys its addressing-mode handler off cpx thresholds on the matched index.

Index Mnemonics Operand form
&01-&19 BRK..TYA implied (opcode only)
&1A-&21 BCC..BVS relative branch
&22-&28 AND..SBC #/zp/abs/(zp,X)/(zp),Y/,X/,Y
&29-&2C ASL..ROR accumulator (A) or memory
&2D-&2E DEC, INC memory only
&2F-&30 CPX, CPY #/zp/abs
&31 BIT abs/zp
&32-&33 JMP, JSR abs; JMP also (abs)
&34-&36 LDX, LDY, STA with index register
&37-&38 STX, STY two-register form
&39 OPT directive (sets OPT flag)
&3A EQU directive (EQUB/W/D/S)

Base opcodes are each group's column-0 value; the operand parser adds addressing-mode offsets via asm_opcode_add4/8/16, so a few bases (e.g. BIT &20, LDX/LDY-immediate &A2/&A0) are the slot value, not a legal standalone opcode. OPT (&39) and EQU (&3A) are directives and have no asm_base_opcode entry.

Two space-saving quirks. Index 0 is dead: the scan starts at &3A and stops at 1, so index 0 is never tested. And the tables overlap by one byte - asm_mnemonic_lo[&3A] is the first byte of asm_mnemonic_hi, and asm_mnemonic_hi[&3A] is the first byte of asm_base_opcode. The reuse is safe precisely because index 0's own high and opcode bytes, which those shared bytes stand in for, are never read.

8450 .asm_mnemonic_lo←1Used as index base by← 85F5 CMP
EQUB &be ; index &00: unused padding (never tested by the scan)
8451 EQUB (('B' AND &1F) * &400 + ('R' AND &1F) * &20 + ('K' AND &1F)) AND &FF ; [ & a m p ; 0 1 ] B R K : p a c k e d - n a m e l o w b y t e
8452 EQUB (('C' AND &1F) * &400 + ('L' AND &1F) * &20 + ('C' AND &1F)) AND &FF ; [ & a m p ; 0 2 ] C L C : p a c k e d - n a m e l o w b y t e
8453 EQUB (('C' AND &1F) * &400 + ('L' AND &1F) * &20 + ('D' AND &1F)) AND &FF ; [ & a m p ; 0 3 ] C L D : p a c k e d - n a m e l o w b y t e
8454 EQUB (('C' AND &1F) * &400 + ('L' AND &1F) * &20 + ('I' AND &1F)) AND &FF ; [ & a m p ; 0 4 ] C L I : p a c k e d - n a m e l o w b y t e
8455 EQUB (('C' AND &1F) * &400 + ('L' AND &1F) * &20 + ('V' AND &1F)) AND &FF ; [ & a m p ; 0 5 ] C L V : p a c k e d - n a m e l o w b y t e
8456 EQUB (('D' AND &1F) * &400 + ('E' AND &1F) * &20 + ('X' AND &1F)) AND &FF ; [ & a m p ; 0 6 ] D E X : p a c k e d - n a m e l o w b y t e
8457 EQUB (('D' AND &1F) * &400 + ('E' AND &1F) * &20 + ('Y' AND &1F)) AND &FF ; [ & a m p ; 0 7 ] D E Y : p a c k e d - n a m e l o w b y t e
8458 EQUB (('I' AND &1F) * &400 + ('N' AND &1F) * &20 + ('X' AND &1F)) AND &FF ; [ & a m p ; 0 8 ] I N X : p a c k e d - n a m e l o w b y t e
8459 EQUB (('I' AND &1F) * &400 + ('N' AND &1F) * &20 + ('Y' AND &1F)) AND &FF ; [ & a m p ; 0 9 ] I N Y : p a c k e d - n a m e l o w b y t e
845A EQUB (('N' AND &1F) * &400 + ('O' AND &1F) * &20 + ('P' AND &1F)) AND &FF ; [ & a m p ; 0 a ] N O P : p a c k e d - n a m e l o w b y t e
845B EQUB (('P' AND &1F) * &400 + ('H' AND &1F) * &20 + ('A' AND &1F)) AND &FF ; [ & a m p ; 0 b ] P H A : p a c k e d - n a m e l o w b y t e
845C EQUB (('P' AND &1F) * &400 + ('H' AND &1F) * &20 + ('P' AND &1F)) AND &FF ; [ & a m p ; 0 c ] P H P : p a c k e d - n a m e l o w b y t e
845D EQUB (('P' AND &1F) * &400 + ('L' AND &1F) * &20 + ('A' AND &1F)) AND &FF ; [ & a m p ; 0 d ] P L A : p a c k e d - n a m e l o w b y t e
845E EQUB (('P' AND &1F) * &400 + ('L' AND &1F) * &20 + ('P' AND &1F)) AND &FF ; [ & a m p ; 0 e ] P L P : p a c k e d - n a m e l o w b y t e
845F EQUB (('R' AND &1F) * &400 + ('T' AND &1F) * &20 + ('I' AND &1F)) AND &FF ; [ & a m p ; 0 f ] R T I : p a c k e d - n a m e l o w b y t e
8460 EQUB (('R' AND &1F) * &400 + ('T' AND &1F) * &20 + ('S' AND &1F)) AND &FF ; [ & a m p ; 1 0 ] R T S : p a c k e d - n a m e l o w b y t e
8461 EQUB (('S' AND &1F) * &400 + ('E' AND &1F) * &20 + ('C' AND &1F)) AND &FF ; [ & a m p ; 1 1 ] S E C : p a c k e d - n a m e l o w b y t e
8462 EQUB (('S' AND &1F) * &400 + ('E' AND &1F) * &20 + ('D' AND &1F)) AND &FF ; [ & a m p ; 1 2 ] S E D : p a c k e d - n a m e l o w b y t e
8463 EQUB (('S' AND &1F) * &400 + ('E' AND &1F) * &20 + ('I' AND &1F)) AND &FF ; [ & a m p ; 1 3 ] S E I : p a c k e d - n a m e l o w b y t e
8464 EQUB (('T' AND &1F) * &400 + ('A' AND &1F) * &20 + ('X' AND &1F)) AND &FF ; [ & a m p ; 1 4 ] T A X : p a c k e d - n a m e l o w b y t e
8465 EQUB (('T' AND &1F) * &400 + ('A' AND &1F) * &20 + ('Y' AND &1F)) AND &FF ; [ & a m p ; 1 5 ] T A Y : p a c k e d - n a m e l o w b y t e
8466 EQUB (('T' AND &1F) * &400 + ('S' AND &1F) * &20 + ('X' AND &1F)) AND &FF ; [ & a m p ; 1 6 ] T S X : p a c k e d - n a m e l o w b y t e
8467 EQUB (('T' AND &1F) * &400 + ('X' AND &1F) * &20 + ('A' AND &1F)) AND &FF ; [ & a m p ; 1 7 ] T X A : p a c k e d - n a m e l o w b y t e
8468 EQUB (('T' AND &1F) * &400 + ('X' AND &1F) * &20 + ('S' AND &1F)) AND &FF ; [ & a m p ; 1 8 ] T X S : p a c k e d - n a m e l o w b y t e
8469 EQUB (('T' AND &1F) * &400 + ('Y' AND &1F) * &20 + ('A' AND &1F)) AND &FF ; [ & a m p ; 1 9 ] T Y A : p a c k e d - n a m e l o w b y t e
846A EQUB (('B' AND &1F) * &400 + ('C' AND &1F) * &20 + ('C' AND &1F)) AND &FF ; [ & a m p ; 1 a ] B C C : p a c k e d - n a m e l o w b y t e
846B EQUB (('B' AND &1F) * &400 + ('C' AND &1F) * &20 + ('S' AND &1F)) AND &FF ; [ & a m p ; 1 b ] B C S : p a c k e d - n a m e l o w b y t e
846C EQUB (('B' AND &1F) * &400 + ('E' AND &1F) * &20 + ('Q' AND &1F)) AND &FF ; [ & a m p ; 1 c ] B E Q : p a c k e d - n a m e l o w b y t e
846D EQUB (('B' AND &1F) * &400 + ('M' AND &1F) * &20 + ('I' AND &1F)) AND &FF ; [ & a m p ; 1 d ] B M I : p a c k e d - n a m e l o w b y t e
846E EQUB (('B' AND &1F) * &400 + ('N' AND &1F) * &20 + ('E' AND &1F)) AND &FF ; [ & a m p ; 1 e ] B N E : p a c k e d - n a m e l o w b y t e
846F EQUB (('B' AND &1F) * &400 + ('P' AND &1F) * &20 + ('L' AND &1F)) AND &FF ; [ & a m p ; 1 f ] B P L : p a c k e d - n a m e l o w b y t e
8470 EQUB (('B' AND &1F) * &400 + ('V' AND &1F) * &20 + ('C' AND &1F)) AND &FF ; [ & a m p ; 2 0 ] B V C : p a c k e d - n a m e l o w b y t e
8471 EQUB (('B' AND &1F) * &400 + ('V' AND &1F) * &20 + ('S' AND &1F)) AND &FF ; [ & a m p ; 2 1 ] B V S : p a c k e d - n a m e l o w b y t e
8472 EQUB (('A' AND &1F) * &400 + ('N' AND &1F) * &20 + ('D' AND &1F)) AND &FF ; [ & a m p ; 2 2 ] A N D : p a c k e d - n a m e l o w b y t e
8473 EQUB (('E' AND &1F) * &400 + ('O' AND &1F) * &20 + ('R' AND &1F)) AND &FF ; [ & a m p ; 2 3 ] E O R : p a c k e d - n a m e l o w b y t e
8474 EQUB (('O' AND &1F) * &400 + ('R' AND &1F) * &20 + ('A' AND &1F)) AND &FF ; [ & a m p ; 2 4 ] O R A : p a c k e d - n a m e l o w b y t e
8475 EQUB (('A' AND &1F) * &400 + ('D' AND &1F) * &20 + ('C' AND &1F)) AND &FF ; [ & a m p ; 2 5 ] A D C : p a c k e d - n a m e l o w b y t e
8476 EQUB (('C' AND &1F) * &400 + ('M' AND &1F) * &20 + ('P' AND &1F)) AND &FF ; [ & a m p ; 2 6 ] C M P : p a c k e d - n a m e l o w b y t e
8477 EQUB (('L' AND &1F) * &400 + ('D' AND &1F) * &20 + ('A' AND &1F)) AND &FF ; [ & a m p ; 2 7 ] L D A : p a c k e d - n a m e l o w b y t e
8478 EQUB (('S' AND &1F) * &400 + ('B' AND &1F) * &20 + ('C' AND &1F)) AND &FF ; [ & a m p ; 2 8 ] S B C : p a c k e d - n a m e l o w b y t e
8479 EQUB (('A' AND &1F) * &400 + ('S' AND &1F) * &20 + ('L' AND &1F)) AND &FF ; [ & a m p ; 2 9 ] A S L : p a c k e d - n a m e l o w b y t e
847A EQUB (('L' AND &1F) * &400 + ('S' AND &1F) * &20 + ('R' AND &1F)) AND &FF ; [ & a m p ; 2 a ] L S R : p a c k e d - n a m e l o w b y t e
847B EQUB (('R' AND &1F) * &400 + ('O' AND &1F) * &20 + ('L' AND &1F)) AND &FF ; [ & a m p ; 2 b ] R O L : p a c k e d - n a m e l o w b y t e
847C EQUB (('R' AND &1F) * &400 + ('O' AND &1F) * &20 + ('R' AND &1F)) AND &FF ; [ & a m p ; 2 c ] R O R : p a c k e d - n a m e l o w b y t e
847D EQUB (('D' AND &1F) * &400 + ('E' AND &1F) * &20 + ('C' AND &1F)) AND &FF ; [ & a m p ; 2 d ] D E C : p a c k e d - n a m e l o w b y t e
847E EQUB (('I' AND &1F) * &400 + ('N' AND &1F) * &20 + ('C' AND &1F)) AND &FF ; [ & a m p ; 2 e ] I N C : p a c k e d - n a m e l o w b y t e
847F EQUB (('C' AND &1F) * &400 + ('P' AND &1F) * &20 + ('X' AND &1F)) AND &FF ; [ & a m p ; 2 f ] C P X : p a c k e d - n a m e l o w b y t e
8480 EQUB (('C' AND &1F) * &400 + ('P' AND &1F) * &20 + ('Y' AND &1F)) AND &FF ; [ & a m p ; 3 0 ] C P Y : p a c k e d - n a m e l o w b y t e
8481 EQUB (('B' AND &1F) * &400 + ('I' AND &1F) * &20 + ('T' AND &1F)) AND &FF ; [ & a m p ; 3 1 ] B I T : p a c k e d - n a m e l o w b y t e
8482 EQUB (('J' AND &1F) * &400 + ('M' AND &1F) * &20 + ('P' AND &1F)) AND &FF ; [ & a m p ; 3 2 ] J M P : p a c k e d - n a m e l o w b y t e
8483 EQUB (('J' AND &1F) * &400 + ('S' AND &1F) * &20 + ('R' AND &1F)) AND &FF ; [ & a m p ; 3 3 ] J S R : p a c k e d - n a m e l o w b y t e
8484 EQUB (('L' AND &1F) * &400 + ('D' AND &1F) * &20 + ('X' AND &1F)) AND &FF ; [ & a m p ; 3 4 ] L D X : p a c k e d - n a m e l o w b y t e
8485 EQUB (('L' AND &1F) * &400 + ('D' AND &1F) * &20 + ('Y' AND &1F)) AND &FF ; [ & a m p ; 3 5 ] L D Y : p a c k e d - n a m e l o w b y t e
8486 EQUB (('S' AND &1F) * &400 + ('T' AND &1F) * &20 + ('A' AND &1F)) AND &FF ; [ & a m p ; 3 6 ] S T A : p a c k e d - n a m e l o w b y t e
8487 EQUB (('S' AND &1F) * &400 + ('T' AND &1F) * &20 + ('X' AND &1F)) AND &FF ; [ & a m p ; 3 7 ] S T X : p a c k e d - n a m e l o w b y t e
8488 EQUB (('S' AND &1F) * &400 + ('T' AND &1F) * &20 + ('Y' AND &1F)) AND &FF ; [ & a m p ; 3 8 ] S T Y : p a c k e d - n a m e l o w b y t e
8489 EQUB (('O' AND &1F) * &400 + ('P' AND &1F) * &20 + ('T' AND &1F)) AND &FF ; [ & a m p ; 3 9 ] O P T d i r e c t i v e : p a c k e d - n a m e l o w b y t e
848A .asm_mnemonic_hi←1Used as index base by← 85FA LDY
EQUB &35 ; index &00 hi (unused); also asm_mnemonic_lo[&3A] = EQU directive packed-name low byte
848B EQUB (('B' AND &1F) * &400 + ('R' AND &1F) * &20 + ('K' AND &1F)) DIV &100 ; [ & a m p ; 0 1 ] B R K : p a c k e d - n a m e h i g h b y t e
848C EQUB (('C' AND &1F) * &400 + ('L' AND &1F) * &20 + ('C' AND &1F)) DIV &100 ; [ & a m p ; 0 2 ] C L C : p a c k e d - n a m e h i g h b y t e
848D EQUB (('C' AND &1F) * &400 + ('L' AND &1F) * &20 + ('D' AND &1F)) DIV &100 ; [ & a m p ; 0 3 ] C L D : p a c k e d - n a m e h i g h b y t e
848E EQUB (('C' AND &1F) * &400 + ('L' AND &1F) * &20 + ('I' AND &1F)) DIV &100 ; [ & a m p ; 0 4 ] C L I : p a c k e d - n a m e h i g h b y t e
848F EQUB (('C' AND &1F) * &400 + ('L' AND &1F) * &20 + ('V' AND &1F)) DIV &100 ; [ & a m p ; 0 5 ] C L V : p a c k e d - n a m e h i g h b y t e
8490 EQUB (('D' AND &1F) * &400 + ('E' AND &1F) * &20 + ('X' AND &1F)) DIV &100 ; [ & a m p ; 0 6 ] D E X : p a c k e d - n a m e h i g h b y t e
8491 EQUB (('D' AND &1F) * &400 + ('E' AND &1F) * &20 + ('Y' AND &1F)) DIV &100 ; [ & a m p ; 0 7 ] D E Y : p a c k e d - n a m e h i g h b y t e
8492 EQUB (('I' AND &1F) * &400 + ('N' AND &1F) * &20 + ('X' AND &1F)) DIV &100 ; [ & a m p ; 0 8 ] I N X : p a c k e d - n a m e h i g h b y t e
8493 EQUB (('I' AND &1F) * &400 + ('N' AND &1F) * &20 + ('Y' AND &1F)) DIV &100 ; [ & a m p ; 0 9 ] I N Y : p a c k e d - n a m e h i g h b y t e
8494 EQUB (('N' AND &1F) * &400 + ('O' AND &1F) * &20 + ('P' AND &1F)) DIV &100 ; [ & a m p ; 0 a ] N O P : p a c k e d - n a m e h i g h b y t e
8495 EQUB (('P' AND &1F) * &400 + ('H' AND &1F) * &20 + ('A' AND &1F)) DIV &100 ; [ & a m p ; 0 b ] P H A : p a c k e d - n a m e h i g h b y t e
8496 EQUB (('P' AND &1F) * &400 + ('H' AND &1F) * &20 + ('P' AND &1F)) DIV &100 ; [ & a m p ; 0 c ] P H P : p a c k e d - n a m e h i g h b y t e
8497 EQUB (('P' AND &1F) * &400 + ('L' AND &1F) * &20 + ('A' AND &1F)) DIV &100 ; [ & a m p ; 0 d ] P L A : p a c k e d - n a m e h i g h b y t e
8498 EQUB (('P' AND &1F) * &400 + ('L' AND &1F) * &20 + ('P' AND &1F)) DIV &100 ; [ & a m p ; 0 e ] P L P : p a c k e d - n a m e h i g h b y t e
8499 EQUB (('R' AND &1F) * &400 + ('T' AND &1F) * &20 + ('I' AND &1F)) DIV &100 ; [ & a m p ; 0 f ] R T I : p a c k e d - n a m e h i g h b y t e
849A EQUB (('R' AND &1F) * &400 + ('T' AND &1F) * &20 + ('S' AND &1F)) DIV &100 ; [ & a m p ; 1 0 ] R T S : p a c k e d - n a m e h i g h b y t e
849B EQUB (('S' AND &1F) * &400 + ('E' AND &1F) * &20 + ('C' AND &1F)) DIV &100 ; [ & a m p ; 1 1 ] S E C : p a c k e d - n a m e h i g h b y t e
849C EQUB (('S' AND &1F) * &400 + ('E' AND &1F) * &20 + ('D' AND &1F)) DIV &100 ; [ & a m p ; 1 2 ] S E D : p a c k e d - n a m e h i g h b y t e
849D EQUB (('S' AND &1F) * &400 + ('E' AND &1F) * &20 + ('I' AND &1F)) DIV &100 ; [ & a m p ; 1 3 ] S E I : p a c k e d - n a m e h i g h b y t e
849E EQUB (('T' AND &1F) * &400 + ('A' AND &1F) * &20 + ('X' AND &1F)) DIV &100 ; [ & a m p ; 1 4 ] T A X : p a c k e d - n a m e h i g h b y t e
849F EQUB (('T' AND &1F) * &400 + ('A' AND &1F) * &20 + ('Y' AND &1F)) DIV &100 ; [ & a m p ; 1 5 ] T A Y : p a c k e d - n a m e h i g h b y t e
84A0 EQUB (('T' AND &1F) * &400 + ('S' AND &1F) * &20 + ('X' AND &1F)) DIV &100 ; [ & a m p ; 1 6 ] T S X : p a c k e d - n a m e h i g h b y t e
84A1 EQUB (('T' AND &1F) * &400 + ('X' AND &1F) * &20 + ('A' AND &1F)) DIV &100 ; [ & a m p ; 1 7 ] T X A : p a c k e d - n a m e h i g h b y t e
84A2 EQUB (('T' AND &1F) * &400 + ('X' AND &1F) * &20 + ('S' AND &1F)) DIV &100 ; [ & a m p ; 1 8 ] T X S : p a c k e d - n a m e h i g h b y t e
84A3 EQUB (('T' AND &1F) * &400 + ('Y' AND &1F) * &20 + ('A' AND &1F)) DIV &100 ; [ & a m p ; 1 9 ] T Y A : p a c k e d - n a m e h i g h b y t e
84A4 EQUB (('B' AND &1F) * &400 + ('C' AND &1F) * &20 + ('C' AND &1F)) DIV &100 ; [ & a m p ; 1 a ] B C C : p a c k e d - n a m e h i g h b y t e
84A5 EQUB (('B' AND &1F) * &400 + ('C' AND &1F) * &20 + ('S' AND &1F)) DIV &100 ; [ & a m p ; 1 b ] B C S : p a c k e d - n a m e h i g h b y t e
84A6 EQUB (('B' AND &1F) * &400 + ('E' AND &1F) * &20 + ('Q' AND &1F)) DIV &100 ; [ & a m p ; 1 c ] B E Q : p a c k e d - n a m e h i g h b y t e
84A7 EQUB (('B' AND &1F) * &400 + ('M' AND &1F) * &20 + ('I' AND &1F)) DIV &100 ; [ & a m p ; 1 d ] B M I : p a c k e d - n a m e h i g h b y t e
84A8 EQUB (('B' AND &1F) * &400 + ('N' AND &1F) * &20 + ('E' AND &1F)) DIV &100 ; [ & a m p ; 1 e ] B N E : p a c k e d - n a m e h i g h b y t e
84A9 EQUB (('B' AND &1F) * &400 + ('P' AND &1F) * &20 + ('L' AND &1F)) DIV &100 ; [ & a m p ; 1 f ] B P L : p a c k e d - n a m e h i g h b y t e
84AA EQUB (('B' AND &1F) * &400 + ('V' AND &1F) * &20 + ('C' AND &1F)) DIV &100 ; [ & a m p ; 2 0 ] B V C : p a c k e d - n a m e h i g h b y t e
84AB EQUB (('B' AND &1F) * &400 + ('V' AND &1F) * &20 + ('S' AND &1F)) DIV &100 ; [ & a m p ; 2 1 ] B V S : p a c k e d - n a m e h i g h b y t e
84AC EQUB (('A' AND &1F) * &400 + ('N' AND &1F) * &20 + ('D' AND &1F)) DIV &100 ; [ & a m p ; 2 2 ] A N D : p a c k e d - n a m e h i g h b y t e
84AD EQUB (('E' AND &1F) * &400 + ('O' AND &1F) * &20 + ('R' AND &1F)) DIV &100 ; [ & a m p ; 2 3 ] E O R : p a c k e d - n a m e h i g h b y t e
84AE EQUB (('O' AND &1F) * &400 + ('R' AND &1F) * &20 + ('A' AND &1F)) DIV &100 ; [ & a m p ; 2 4 ] O R A : p a c k e d - n a m e h i g h b y t e
84AF EQUB (('A' AND &1F) * &400 + ('D' AND &1F) * &20 + ('C' AND &1F)) DIV &100 ; [ & a m p ; 2 5 ] A D C : p a c k e d - n a m e h i g h b y t e
84B0 EQUB (('C' AND &1F) * &400 + ('M' AND &1F) * &20 + ('P' AND &1F)) DIV &100 ; [ & a m p ; 2 6 ] C M P : p a c k e d - n a m e h i g h b y t e
84B1 EQUB (('L' AND &1F) * &400 + ('D' AND &1F) * &20 + ('A' AND &1F)) DIV &100 ; [ & a m p ; 2 7 ] L D A : p a c k e d - n a m e h i g h b y t e
84B2 EQUB (('S' AND &1F) * &400 + ('B' AND &1F) * &20 + ('C' AND &1F)) DIV &100 ; [ & a m p ; 2 8 ] S B C : p a c k e d - n a m e h i g h b y t e
84B3 EQUB (('A' AND &1F) * &400 + ('S' AND &1F) * &20 + ('L' AND &1F)) DIV &100 ; [ & a m p ; 2 9 ] A S L : p a c k e d - n a m e h i g h b y t e
84B4 EQUB (('L' AND &1F) * &400 + ('S' AND &1F) * &20 + ('R' AND &1F)) DIV &100 ; [ & a m p ; 2 a ] L S R : p a c k e d - n a m e h i g h b y t e
84B5 EQUB (('R' AND &1F) * &400 + ('O' AND &1F) * &20 + ('L' AND &1F)) DIV &100 ; [ & a m p ; 2 b ] R O L : p a c k e d - n a m e h i g h b y t e
84B6 EQUB (('R' AND &1F) * &400 + ('O' AND &1F) * &20 + ('R' AND &1F)) DIV &100 ; [ & a m p ; 2 c ] R O R : p a c k e d - n a m e h i g h b y t e
84B7 EQUB (('D' AND &1F) * &400 + ('E' AND &1F) * &20 + ('C' AND &1F)) DIV &100 ; [ & a m p ; 2 d ] D E C : p a c k e d - n a m e h i g h b y t e
84B8 EQUB (('I' AND &1F) * &400 + ('N' AND &1F) * &20 + ('C' AND &1F)) DIV &100 ; [ & a m p ; 2 e ] I N C : p a c k e d - n a m e h i g h b y t e
84B9 EQUB (('C' AND &1F) * &400 + ('P' AND &1F) * &20 + ('X' AND &1F)) DIV &100 ; [ & a m p ; 2 f ] C P X : p a c k e d - n a m e h i g h b y t e
84BA EQUB (('C' AND &1F) * &400 + ('P' AND &1F) * &20 + ('Y' AND &1F)) DIV &100 ; [ & a m p ; 3 0 ] C P Y : p a c k e d - n a m e h i g h b y t e
84BB EQUB (('B' AND &1F) * &400 + ('I' AND &1F) * &20 + ('T' AND &1F)) DIV &100 ; [ & a m p ; 3 1 ] B I T : p a c k e d - n a m e h i g h b y t e
84BC EQUB (('J' AND &1F) * &400 + ('M' AND &1F) * &20 + ('P' AND &1F)) DIV &100 ; [ & a m p ; 3 2 ] J M P : p a c k e d - n a m e h i g h b y t e
84BD EQUB (('J' AND &1F) * &400 + ('S' AND &1F) * &20 + ('R' AND &1F)) DIV &100 ; [ & a m p ; 3 3 ] J S R : p a c k e d - n a m e h i g h b y t e
84BE EQUB (('L' AND &1F) * &400 + ('D' AND &1F) * &20 + ('X' AND &1F)) DIV &100 ; [ & a m p ; 3 4 ] L D X : p a c k e d - n a m e h i g h b y t e
84BF EQUB (('L' AND &1F) * &400 + ('D' AND &1F) * &20 + ('Y' AND &1F)) DIV &100 ; [ & a m p ; 3 5 ] L D Y : p a c k e d - n a m e h i g h b y t e
84C0 EQUB (('S' AND &1F) * &400 + ('T' AND &1F) * &20 + ('A' AND &1F)) DIV &100 ; [ & a m p ; 3 6 ] S T A : p a c k e d - n a m e h i g h b y t e
84C1 EQUB (('S' AND &1F) * &400 + ('T' AND &1F) * &20 + ('X' AND &1F)) DIV &100 ; [ & a m p ; 3 7 ] S T X : p a c k e d - n a m e h i g h b y t e
84C2 EQUB (('S' AND &1F) * &400 + ('T' AND &1F) * &20 + ('Y' AND &1F)) DIV &100 ; [ & a m p ; 3 8 ] S T Y : p a c k e d - n a m e h i g h b y t e
84C3 EQUB (('O' AND &1F) * &400 + ('P' AND &1F) * &20 + ('T' AND &1F)) DIV &100 ; [ & a m p ; 3 9 ] O P T d i r e c t i v e : p a c k e d - n a m e h i g h b y t e
84C4 .asm_base_opcode←1Used as index base by← 8620 LDA
EQUB &16 ; index &00 base (unused); also asm_mnemonic_hi[&3A] = EQU directive packed-name high byte
84C5 EQUB &00 ; [&01] BRK: base opcode &00
84C6 EQUB &18 ; [&02] CLC: base opcode &18
84C7 EQUB &D8 ; [&03] CLD: base opcode &d8
84C8 EQUB &58 ; [&04] CLI: base opcode &58
84C9 EQUB &B8 ; [&05] CLV: base opcode &b8
84CA EQUB &CA ; [&06] DEX: base opcode &ca
84CB EQUB &88 ; [&07] DEY: base opcode &88
84CC EQUB &E8 ; [&08] INX: base opcode &e8
84CD EQUB &C8 ; [&09] INY: base opcode &c8
84CE EQUB &EA ; [&0a] NOP: base opcode &ea
84CF EQUB &48 ; [&0b] PHA: base opcode &48
84D0 EQUB &08 ; [&0c] PHP: base opcode &08
84D1 EQUB &68 ; [&0d] PLA: base opcode &68
84D2 EQUB &28 ; [&0e] PLP: base opcode &28
84D3 EQUB &40 ; [&0f] RTI: base opcode &40
84D4 EQUB &60 ; [&10] RTS: base opcode &60
84D5 EQUB &38 ; [&11] SEC: base opcode &38
84D6 EQUB &F8 ; [&12] SED: base opcode &f8
84D7 EQUB &78 ; [&13] SEI: base opcode &78
84D8 EQUB &AA ; [&14] TAX: base opcode &aa
84D9 EQUB &A8 ; [&15] TAY: base opcode &a8
84DA EQUB &BA ; [&16] TSX: base opcode &ba
84DB EQUB &8A ; [&17] TXA: base opcode &8a
84DC EQUB &9A ; [&18] TXS: base opcode &9a
84DD EQUB &98 ; [&19] TYA: base opcode &98
84DE EQUB &90 ; [&1a] BCC: base opcode &90
84DF EQUB &B0 ; [&1b] BCS: base opcode &b0
84E0 EQUB &F0 ; [&1c] BEQ: base opcode &f0
84E1 EQUB &30 ; [&1d] BMI: base opcode &30
84E2 EQUB &D0 ; [&1e] BNE: base opcode &d0
84E3 EQUB &10 ; [&1f] BPL: base opcode &10
84E4 EQUB &50 ; [&20] BVC: base opcode &50
84E5 EQUB &70 ; [&21] BVS: base opcode &70
84E6 EQUB &21 ; [&22] AND: base opcode &21
84E7 EQUB &41 ; [&23] EOR: base opcode &41
84E8 EQUB &01 ; [&24] ORA: base opcode &01
84E9 EQUB &61 ; [&25] ADC: base opcode &61
84EA EQUB &C1 ; [&26] CMP: base opcode &c1
84EB EQUB &A1 ; [&27] LDA: base opcode &a1
84EC EQUB &E1 ; [&28] SBC: base opcode &e1
84ED EQUB &06 ; [&29] ASL: base opcode &06
84EE EQUB &46 ; [&2a] LSR: base opcode &46
84EF EQUB &26 ; [&2b] ROL: base opcode &26
84F0 EQUB &66 ; [&2c] ROR: base opcode &66
84F1 EQUB &C6 ; [&2d] DEC: base opcode &c6
84F2 EQUB &E6 ; [&2e] INC: base opcode &e6
84F3 EQUB &E0 ; [&2f] CPX: base opcode &e0
84F4 EQUB &C0 ; [&30] CPY: base opcode &c0
84F5 EQUB &20 ; [&31] BIT: base opcode &20
84F6 EQUB &4C ; [&32] JMP: base opcode &4c
84F7 EQUB &20 ; [&33] JSR: base opcode &20
84F8 EQUB &A2 ; [&34] LDX: base opcode &a2
84F9 EQUB &A0 ; [&35] LDY: base opcode &a0
84FA EQUB &81 ; [&36] STA: base opcode &81
84FB EQUB &86 ; [&37] STX: base opcode &86
84FC EQUB &84 ; [&38] STY: base opcode &84

Finish the inline assembler

Leave the inline 6502 assembler (reached at "]"): set the OPT flag to &FF (not assembling) and jump back into the statement interpreter.

On ExitZP_OPT_FLAG (&28)&FF (BASIC mode, not assembling)
CONTROLresumes the statement interpreter (does not return)
84FD .assembler_exit←1← 850D BEQ
LDA #&ff ; Leaving the assembler: OPT = BASIC mode
84FF STA zp_opt_flag ; store &FF (not assembling)
8501 JMP next_statement ; resume execution

Enter the inline assembler

Reached by the jmp asm_enter at &8B44, in check_eq_star_bracket, when a "[" opens an assembler block. Sets the default OPT 3 (listing + error reporting, assemble to P%) and falls into asm_loop, which assembles one instruction or directive per statement, optionally lists it, and advances. The loop runs until "]" (-> assembler_exit, back to the interpreter) or the end of the program (-> immediate_loop). Does not return.

On ExitCONTROLdoes not return; exits to assembler_exit on "]" or immediate_loop at end of program
8504 .asm_enter←1← 8B44 JMP
LDA #3 ; Entering: default OPT 3
8506 STA zp_opt_flag ; store it
8508 .asm_loop←1← 85A2 JMP
JSR skip_spaces ; Skip spaces
850B CMP #']' ; ']' end of assembler?
850D BEQ assembler_exit ; yes: exit
850F JSR skip_to_statement_end ; Skip to the statement
8512 DEC zp_text_ptr_off ; back up
8514 JSR asm_parse_mnemonic ; Assemble one instruction
8517 DEC zp_text_ptr_off ; back up
8519 LDA zp_opt_flag ; OPT listing bit set?
851B LSR ; shift the list bit into carry
851C BCC asm_next_statement ; no: skip the listing
851E LDA zp_count ; Column for the source text
8520 ADC #4 ; COUNT + 4,
8522 STA zp_fwb_m2 ; source column (&3F)
8524 LDA zp_general_1 ; Print P% high
8526 JSR print_hex_byte ; as two hex digits
8529 LDA zp_general ; Print P% low
852B JSR print_hex_space ; as two hex digits
852E LDX #&fc ; Byte count
8530 LDY zp_fileblk ; String (EQUS) length?
8532 BPL asm_list_count ; not EQUS: use the byte count,
8534 LDY zp_strbuf_len ; EQUS: use the string length
8536 .asm_list_count←1← 8532 BPL
STY zp_general_1 ; Bytes to list
8538 BEQ asm_list_pad_loop ; none
853A LDY #0 ; From byte 0
853C .asm_list_byte_loop←1← 8554 BNE
INX ; Count printed on this line
853D BNE asm_list_byte ; still on the line
853F JSR emit_newline ; Newline and indent for a continuation
8542 LDX zp_fwb_m2 ; indent to the hex column
8544 .asm_list_indent_loop←1← 8548 BNE
JSR print_space ; Print a space
8547 DEX ; one fewer space
8548 BNE asm_list_indent_loop ; loop
854A LDX #&fd ; reset the per-line count
854C .asm_list_byte←1← 853D BNE
LDA (zp_fileblk_1),y ; Assembled byte
854E JSR print_hex_space ; print it as hex
8551 INY ; next
8552 DEC zp_general_1 ; all bytes?
8554 BNE asm_list_byte_loop ; no: continue
8556 .asm_list_pad_loop←2← 8538 BEQ← 8562 JMP
INX ; Pad to the source column
8557 BPL asm_list_source ; done
8559 JSR print_space ; print a space
855C JSR print_char ; two more spaces,
855F JSR print_char ; (continued)
8562 JMP asm_list_pad_loop ; loop
8565 .asm_list_source←1← 8557 BPL
LDY #0 ; Print the source: from offset 0
8567 .asm_list_src_loop←1← 8575 BNE
LDA (zp_text_ptr),y ; Next character
8569 CMP #':' ; ':' end of statement?
856B BEQ asm_list_src_end ; yes
856D CMP #&0d ; end of line?
856F BEQ asm_list_newline ; yes
8571 .asm_list_src_print←1← 8579 BCC
JSR print_token ; de-tokenise and print
8574 INY ; next
8575 BNE asm_list_src_loop ; loop
8577 .asm_list_src_end←1← 856B BEQ
CPY zp_text_ptr_off ; reached the statement end?
8579 BCC asm_list_src_print ; no: continue
857B .asm_list_newline←1← 856F BEQ
JSR emit_newline ; Newline
857E .asm_next_statement←1← 851C BCC
LDY zp_text_ptr_off ; Advance to the next statement
8580 DEY ; back up (loop pre-increments)
8581 .asm_scan_end_loop←1← 858A BNE
INY ; scan for the end
8582 LDA (zp_text_ptr),y ; read a character
8584 CMP #':' ; ':'?
8586 BEQ asm_stmt_end ; yes
8588 CMP #&0d ; end of line?
858A BNE asm_scan_end_loop ; no: continue
858C .asm_stmt_end←1← 8586 BEQ
JSR cend_back ; Check Escape and advance
858F DEY ; Re-read the terminator
8590 LDA (zp_text_ptr),y ; read it
8592 CMP #':' ; ':'?
8594 BEQ asm_continue ; yes: same line
8596 LDA zp_text_ptr_1 ; at end of program memory?
8598 CMP #7 ; page &07 (immediate buffer)?
859A BNE asm_next_line ; no: next line
859C JMP immediate_loop ; yes: immediate mode
859F .asm_next_line←1← 859A BNE
JSR step_to_next_line ; Move to the next line
85A2 .asm_continue←1← 8594 BEQ
JMP asm_loop ; continue assembling
85A5 .asm_define_label←1← 85D1 BEQ
JSR parse_lvalue ; Label: parse the variable
85A8 BEQ asm_mistake ; end: error
85AA BCS asm_mistake ; indirection: error
85AC JSR stack_integer ; stack the address
85AF JSR factor_pcounter ; value = P%
85B2 STA zp_var_type ; integer type
85B4 JSR assign_number ; assign P% to the label
85B7 JSR eeti_sync ; sync the pointer
fall through ↓

Parse and compact an assembler mnemonic

Skip to the mnemonic at PtrA, handling end-of-statement and labels, then pack its three letters (5 bits each) into &3D/&3E for the opcode-table lookup.

On EntryZP_TEXT_PTR (&0B/&0C)the source pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset of the next character
On ExitZP_FWB_EXP (&3D/&3E)the three letters packed 5 bits each
ZP_TEXT_PTR_OFF (&0A)advanced past the mnemonic
85BA .asm_parse_mnemonic←1← 8514 JSR
LDX #3 ; Three characters
85BC JSR skip_spaces ; Skip spaces
85BF LDY #0 ; Clear the compacted value
85C1 STY zp_fwb_exp ; low byte (&3D)
85C3 CMP #':' ; ':' end of statement?
85C5 BEQ asm_emit ; yes: no instruction
85C7 CMP #&0d ; end of line?
85C9 BEQ asm_emit ; yes
85CB CMP #'\' ; comment?
85CD BEQ asm_emit ; yes
85CF CMP #'.' ; '.' label?
85D1 BEQ asm_define_label ; yes: define it
85D3 DEC zp_text_ptr_off ; back up
85D5 .asm_mn_char_loop←1← 85EF BNE
LDY zp_text_ptr_off ; Next character
85D7 INC zp_text_ptr_off ; advance the offset,
85D9 LDA (zp_text_ptr),y ; read it
85DB BMI asm_logic_mnemonic ; token: tokenised AND/EOR/OR
85DD CMP #' ' ; space?
85DF BEQ asm_mn_search ; skip it
85E1 LDY #5 ; Compact the character (5 bits)
85E3 ASL ; shift the char up by 3,
85E4 ASL ; (continued)
85E5 ASL ; (continued)
85E6 .asm_mn_pack_loop←1← 85EC BNE
ASL ; shift into the value
85E7 ROL zp_fwb_exp ; carry into &3D,
85E9 ROL zp_fwb_m1 ; and &3E,
85EB DEY ; next bit,
85EC BNE asm_mn_pack_loop ; all 5 bits
85EE DEX ; next character
85EF BNE asm_mn_char_loop ; loop for three
85F1 .asm_mn_search←1← 85DF BEQ
LDX #&3a ; Point to the end of the opcode table
85F3 LDA zp_fwb_exp ; Compacted mnemonic low byte
85F5 .asm_mn_search_loop←1← 8602 BNE
CMP asm_mnemonic_lo,x ; Compare the low half
85F8 BNE asm_mn_search_next ; no match: next entry
85FA LDY asm_mnemonic_hi,x ; High half
85FD CPY zp_fwb_m1 ; match the high half?
85FF BEQ asm_got_opcode ; matched
8601 .asm_mn_search_next←1← 85F8 BNE
DEX ; Next entry
8602 BNE asm_mn_search_loop ; loop
8604 .asm_mistake←4← 85A8 BEQ← 85AA BCS← 8615 BNE← 861E BNE
JMP syntax_error ; not matched: Mistake
8607 .asm_logic_mnemonic←1← 85DB BMI
LDX #&22 ; opcode index for AND
8609 CMP #&80 ; tokenised AND?
860B BEQ asm_got_opcode ; yes
860D INX ; EOR index
860E CMP #&82 ; tokenised EOR?
8610 BEQ asm_got_opcode ; yes
8612 INX ; ORA index
8613 CMP #&84 ; tokenised OR?
8615 BNE asm_mistake ; no: Mistake
8617 INC zp_text_ptr_off ; step past, expect 'A'
8619 INY ; advance,
861A LDA (zp_text_ptr),y ; read the next character
861C CMP #'A' ; 'A'?
861E BNE asm_mistake ; no: Mistake
8620 .asm_got_opcode←3← 85FF BEQ← 860B BEQ← 8610 BEQ
LDA asm_base_opcode,x ; Base opcode from the table
8623 STA zp_asm_opcode ; Save it
8625 LDY #1 ; Assume a one-byte instruction
8627 CPX #&1a ; index &1A+ takes an operand?
8629 BCS asm_operand ; yes: parse the addressing mode
fall through ↓

Emit assembled bytes to P%/O%

The inline assembler's byte-emit routine. Copies the assembled bytes to the destination given by P% (resint_p, &0440), or by O% (resint_o, &043C) when OPT selects offset assembly (OPT >= 4), then advances P% (and O% for offset assembly) past them. Y is the signed byte count: 0 emits nothing; a positive count stores that many opcode/operand bytes from the assembly buffer at zp_asm_opcode (&29); a negative count is an EQUS, storing zp_strbuf_len (&36) bytes taken from string_work (&0600).

On EntryYsigned byte count: 0 = none, positive N = N opcode bytes from zp_asm_opcode (&29), negative = EQUS string from string_work (&0600)
ZP_OPT_FLAG (&28)current OPT setting (bit 2 selects offset assembly to O%)
ZP_STRBUF_LEN (&36)string length when emitting an EQUS
RESINT_P (&0440)P%, the assembly program counter
RESINT_O (&043C)O%, the offset-assembly destination
On ExitRESINT_P (&0440)P% advanced past the emitted bytes
RESINT_O (&043C)O% advanced too when offset assembly is active
862B .asm_emit←7← 85C5 BEQ← 85C9 BEQ← 85CD BEQ← 86AA JMP← 879C JMP← 881E JMP← 8864 JMP
LDA resint_p ; P% low -> destination
862E STA zp_general ; to &37
8630 STY zp_fileblk ; Save the byte count
8632 LDX zp_opt_flag ; OPT setting
8634 CPX #4 ; offset assembly (OPT >= 4)?
8636 LDX resint_p_1 ; P% high
8639 STX zp_general_1 ; to &38
863B BCC asm_emit_dest ; no offset: assemble at P%
863D LDA resint_o ; offset: assemble at O% instead
8640 LDX resint_o_1 ; O% high
8643 .asm_emit_dest←1← 863B BCC
STA zp_fileblk_1 ; Store the destination pointer
8645 STX zp_fwb_sign ; high byte (&3B)
8647 TYA ; Any bytes to store?
8648 BEQ return_1 ; none: done
864A BPL asm_emit_loop ; positive count: store opcode bytes
864C LDY zp_strbuf_len ; EQUS: length from the string buffer
864E BEQ return_1 ; empty: done
8650 .asm_emit_loop←2← 864A BPL← 8670 BNE
DEY ; Next byte index
8651 LDA zp_asm_opcode,y ; Opcode/operand byte
8654 BIT zp_fileblk ; storing string (EQUS) bytes?
8656 BPL asm_emit_store ; no: store the opcode byte
8658 LDA string_work,y ; yes: take it from the string buffer
865B .asm_emit_store←1← 8656 BPL
STA (zp_fileblk_1),y ; Store the byte at the destination
865D INC resint_p ; Advance P%
8660 BNE asm_emit_advance ; no carry,
8662 INC resint_p_1 ; carry into P% high
8665 .asm_emit_advance←1← 8660 BNE
BCC asm_emit_more ; offset assembly?
8667 INC resint_o ; yes: advance O% too
866A BNE asm_emit_more ; no carry,
866C INC resint_o_1 ; carry into O% high
866F .asm_emit_more←2← 8665 BCC← 866A BNE
TYA ; More bytes?
8670 BNE asm_emit_loop ; loop
8672 .return_1←2← 8648 BEQ← 864E BEQ
RTS ; Return
8673 .asm_operand←1← 8629 BCS
CPX #&22 ; index &22+ is not a branch?
8675 BCS asm_mode_imm ; yes: other operand forms
8677 JSR eval_expr_to_integer ; Evaluate the target address
867A CLC ; Offset = target - (P% + 2)
867B LDA zp_iwa ; target low,
867D SBC resint_p ; minus P% low (- 1),
8680 TAY ; offset low in Y,
8681 LDA zp_iwa_1 ; target high,
8683 SBC resint_p_1 ; minus P% high,
8686 CPY #1 ; offset >= 1?,
8688 DEY ; offset - 1 (for PC+2),
8689 SBC #0 ; borrow into the high byte
868B BEQ asm_branch_back ; in forward range?
868D CMP #&ff ; in backward range?
868F BEQ asm_branch_fwd ; high byte &FF: in range
8691 .asm_branch_range_err←2← 86B0 BPL← 86B5 BMI
LDA zp_opt_flag ; OPT setting errors enabled?
8693 LSR ; errors enabled (bit 1)?
8694 BEQ asm_branch_ok ; no: ignore the range error
8696 BRK ; Out of range error
8697 EQUB &01
8698 EQUS "Out of range"
86A4 EQUB &00
86A5 .asm_branch_ok←1← 8694 BEQ
TAY ; Use the offset byte
86A6 .asm_set_operand←2← 86AE BMI← 86B3 BPL
STY zp_iwa ; as the operand
86A8 .asm_two_byte←2← 86CA BEQ← 873C JMP
LDY #2 ; Two-byte instruction
86AA JMP asm_emit ; store it
86AD .asm_branch_fwd←1← 868F BEQ
TYA ; forward: check the high byte
86AE BMI asm_set_operand ; in range
86B0 BPL asm_branch_range_err ; out of range
86B2 .asm_branch_back←1← 868B BEQ
TYA ; backward: check the high byte
86B3 BPL asm_set_operand ; in range
86B5 BMI asm_branch_range_err ; out of range
86B7 .asm_mode_imm←1← 8675 BCS
CPX #&29 ; index &29+ : not immediate?
86B9 BCS asm_mode_indirect ; yes: indexed/absolute modes
86BB JSR skip_spaces ; Skip spaces
86BE CMP #'#' ; '#' immediate prefix?
86C0 BNE asm_try_indirect ; no: absolute
86C2 JSR asm_opcode_add8 ; Immediate mode: adjust the opcode
86C5 .asm_imm_eval←2← 877D JMP← 87C9 JMP
JSR eval_expr_to_integer ; Evaluate the immediate value
86C8 .asm_imm_byte←2← 86F9 BEQ← 870B BEQ
LDA zp_iwa_1 ; high byte zero (fits in a byte)?
86CA BEQ asm_two_byte ; yes: two-byte instruction
86CC .asm_byte_error←1← 880D JMP
BRK ; Byte error (value > 255)
86CD EQUB &02
86CE EQUS "Byte"
86D2 EQUB &00
86D3 .asm_mode_indirect←1← 86B9 BCS
CPX #&36 ; index &36 : the (zp),Y / (zp,X) group?
86D5 BNE asm_mode_class2 ; no: absolute/indexed group
86D7 JSR skip_spaces ; Skip spaces
86DA .asm_try_indirect←1← 86C0 BNE
CMP #'(' ; '(' opening an indirect mode?
86DC BNE asm_abs_from_paren ; no: absolute
86DE JSR eval_expr_to_integer ; Evaluate the zero-page address
86E1 JSR skip_spaces ; Skip spaces
86E4 CMP #')' ; ')' -> (zp),Y form
86E6 BNE asm_indirect_zpx ; no: try (zp,X)
86E8 JSR skip_spaces ; Skip spaces
86EB CMP #',' ; ','?
86ED BNE asm_index_error ; no: Index error
86EF JSR asm_opcode_add16 ; adjust the opcode for (zp),Y
86F2 JSR skip_spaces ; Skip spaces
86F5 CMP #'Y' ; 'Y'?
86F7 BNE asm_index_error ; no: Index error
86F9 BEQ asm_imm_byte ; process as a two-byte instruction
86FB .asm_indirect_zpx←1← 86E6 BNE
CMP #',' ; ','?
86FD BNE asm_index_error ; no: Index error
86FF JSR skip_spaces ; Skip spaces
8702 CMP #'X' ; 'X'?
8704 BNE asm_index_error ; no: Index error
8706 JSR skip_spaces ; Skip spaces
8709 CMP #')' ; ')'?
870B BEQ asm_imm_byte ; yes: process
870D .asm_index_error←8← 86ED BNE← 86F7 BNE← 86FD BNE← 8704 BNE← 872D BNE← 8764 JMP← 87AF JMP← 87ED JMP
BRK ; Index error
870E EQUB &03
870F EQUS "Index"
8714 EQUB &00
8715 .asm_abs_from_paren←1← 86DC BNE
DEC zp_text_ptr_off ; Back up over the "("
8717 JSR eval_expr_to_integer ; Evaluate the address
871A JSR skip_spaces ; Skip spaces
871D CMP #',' ; ','?
871F BNE asm_absolute ; no: process as absolute
8721 JSR asm_opcode_add16 ; adjust the opcode for absolute,X/,Y
8724 JSR skip_spaces ; Skip spaces
8727 CMP #'X' ; 'X'?
8729 BEQ asm_absolute ; yes: abs,X
872B CMP #'Y' ; 'Y'?
872D BNE asm_index_error ; no: Index error
872F .asm_indexed_adjust←1← 873A BNE
JSR asm_opcode_add8 ; Adjust the opcode for the indexed form
8732 JMP asm_three_byte ; process the absolute operand

Assemble a zero-page or absolute operand

Adjust the opcode being built for absolute addressing (asm_opcode_add4), then fall into asm_zp_or_abs, which tests the high byte of the evaluated operand address in zp_iwa: high byte zero emits the two-byte zero-page form (asm_two_byte), otherwise the three-byte absolute form (asm_indexed_adjust). The inline assembler's addressing-mode handler for absolute / zero-page operands.

On EntryZP_IWA (&2A/&2B)the evaluated operand address (low/high)
ZP_ASM_OPCODE (&29)the opcode being built
On ExitCONTROLtail-calls asm_two_byte (zero page, 2 bytes) or asm_indexed_adjust (absolute, 3 bytes); does not return here
8735 .asm_absolute←5← 871F BNE← 8729 BEQ← 8785 JMP← 87DB JMP← 87EA JMP
JSR asm_opcode_add4 ; Adjust the opcode for absolute mode
8738 .asm_zp_or_abs←3← 8758 BNE← 8762 BEQ← 8810 JMP
LDA zp_iwa_1 ; address fits in zero page (high byte 0)?
873A BNE asm_indexed_adjust ; no: assemble as absolute (three bytes)
873C JMP asm_two_byte ; yes: assemble as two bytes
873F .asm_mode_class2←1← 86D5 BNE
CPX #&2f ; index &2F+ : a different operand class?
8741 BCS asm_mode_class3 ; yes
8743 CPX #&2d ; index &2D+ (accumulator-or-absolute)?
8745 BCS asm_acc_or_abs ; yes
8747 JSR skip_spaces ; Skip spaces
874A CMP #'A' ; 'A' (accumulator)?
874C BEQ asm_accumulator ; yes
874E DEC zp_text_ptr_off ; Back up a character
8750 .asm_acc_or_abs←1← 8745 BCS
JSR eval_expr_to_integer ; Evaluate the address
8753 JSR skip_spaces ; Skip spaces
8756 CMP #',' ; ','?
8758 BNE asm_zp_or_abs ; no: process
875A JSR asm_opcode_add16 ; adjust the opcode for indexed mode
875D JSR skip_spaces ; Skip spaces
8760 CMP #'X' ; 'X'?
8762 BEQ asm_zp_or_abs ; yes: address,X
8764 JMP asm_index_error ; Index error
8767 .asm_accumulator←1← 874C BEQ
JSR asm_opcode_add4 ; Accumulator form: adjust the opcode
876A LDY #1 ; one byte
876C BNE asm_three_emit ; store it
876E .asm_mode_class3←1← 8741 BCS
CPX #&32 ; index &32+ : implied/branch class?
8770 BCS asm_mode_noop ; yes
8772 CPX #&31 ; index &31 (immediate-only)?
8774 BEQ asm_imm1_eval ; yes
8776 JSR skip_spaces ; Skip spaces
8779 CMP #'#' ; '#' immediate?
877B BNE asm_imm1_backup ; no: address
877D JMP asm_imm_eval ; immediate
8780 .asm_imm1_backup←1← 877B BNE
DEC zp_text_ptr_off ; Back up a character
8782 .asm_imm1_eval←1← 8774 BEQ
JSR eval_expr_to_integer ; Evaluate the value
8785 JMP asm_absolute ; assemble as absolute
8788 .asm_mode_noop←1← 8770 BCS
CPX #&33 ; index &33 (no operand)?
878A BEQ asm_addr_eval ; yes
878C BCS asm_mode_equ ; index &34+ : other forms
878E JSR skip_spaces ; Skip spaces
8791 CMP #'(' ; '(' indirect?
8793 BEQ asm_jmp_indirect ; yes
8795 DEC zp_text_ptr_off ; Back up a character
8797 .asm_addr_eval←1← 878A BEQ
JSR eval_expr_to_integer ; Evaluate the address
879A .asm_three_byte←2← 8732 JMP← 87AD BEQ
LDY #3 ; Three-byte instruction
879C .asm_three_emit←1← 876C BNE
JMP asm_emit ; store it
879F .asm_jmp_indirect←1← 8793 BEQ
JSR asm_opcode_add16 ; Indirect: adjust the opcode
87A2 JSR asm_opcode_add16 ; (continued)
87A5 JSR eval_expr_to_integer ; evaluate the address
87A8 JSR skip_spaces ; Skip spaces
87AB CMP #')' ; ')' to close?
87AD BEQ asm_three_byte ; yes: three-byte instruction
87AF JMP asm_index_error ; Index error
87B2 .asm_mode_equ←1← 878C BCS
CPX #&39 ; index &39+ : EQU directives
87B4 BCS asm_opt_directive ; yes
87B6 LDA zp_fwb_exp ; Register letter from the mnemonic
87B8 EOR #1 ; toggle bit 0
87BA AND #&1f ; save it index &37+ (two-register form)?
87BC PHA ; Save the register
87BD CPX #&37 ; yes
87BF BCS asm_equ_eval ; two-register form?
87C1 JSR skip_spaces ; '#' immediate?
87C4 CMP #'#' ; no: absolute
87C6 BNE asm_equ_backup ; discard the saved register, do immediate
87C8 PLA ; discard the register
87C9 JMP asm_imm_eval ; do immediate
87CC .asm_equ_backup←1← 87C6 BNE
DEC zp_text_ptr_off ; Back up a character
87CE JSR eval_expr_to_integer ; Evaluate the address
87D1 PLA ; recover the register letter
87D2 STA zp_general ; into &37
87D4 JSR skip_spaces ; ',' index register?
87D7 CMP #',' ; yes
87D9 BEQ asm_equ_index ; no: assemble as absolute
87DB JMP asm_absolute ; process as absolute
87DE .asm_equ_index←1← 87D9 BEQ
JSR skip_spaces ; Index register letter
87E1 AND #&1f ; matches the expected register?
87E3 CMP zp_general ; no: Index error
87E5 BNE asm_equ_index_err ; register mismatch?
87E7 JSR asm_opcode_add16 ; adjust the opcode for the indexed form
87EA JMP asm_absolute ; assemble as absolute
87ED .asm_equ_index_err←2← 87E5 BNE← 8804 BNE
JMP asm_index_error ; Index error
87F0 .asm_equ_eval←1← 87BF BCS
JSR eval_expr_to_integer ; Evaluate the address
87F3 PLA ; recover the register letter
87F4 STA zp_general ; into &37
87F6 JSR skip_spaces ; ',' index register?
87F9 CMP #',' ; no: single operand
87FB BNE asm_force_zp ; Index register letter
87FD JSR skip_spaces ; read the index register letter
8800 AND #&1f ; matches?
8802 CMP zp_general ; no: Index error
8804 BNE asm_equ_index_err ; adjust the opcode for indexed mode
8806 JSR asm_opcode_add16 ; high byte zero?
8809 LDA zp_iwa_1 ; yes: continue
880B BEQ asm_force_zp ; Byte error (value > 255)
880D JMP asm_byte_error ; Byte error
8810 .asm_force_zp←2← 87FB BNE← 880B BEQ
JMP asm_zp_or_abs ; Assemble as zero-page
8813 .asm_opt_directive←1← 87B4 BCS
BNE equb_directive ; index &39 (OPT)?
8815 JSR eval_expr_to_integer ; no: EQU directives OPT: evaluate the new setting
8818 LDA zp_iwa ; OPT value
881A STA zp_opt_flag ; store it as the OPT flag
881C LDY #0 ; no bytes to assemble
881E JMP asm_emit ; finish

Evaluate an integer expression

Evaluate the expression at the text pointer (eval_expr) and coerce the result to an integer (coerce_to_integer), then copy the secondary text offset back to the primary pointer. Used wherever a statement needs an integer argument.

On ExitZP_IWA (&2A)4-byte integer result
8821 .eval_expr_to_integer←22← 8677 JSR← 86C5 JSR← 86DE JSR← 8717 JSR← 8750 JSR← 8782 JSR← 8797 JSR← 87A5 JSR← 87CE JSR← 87F0 JSR← 8815 JSR← 885A JSR← 9188 JSR← 92A2 JSR← 937A JSR← 9391 JSR← 939D JSR← 93F1 JSR← 9440 JSR← B44C JSR← B472 JSR← B4A0 JSR
JSR eval_expr ; Evaluate the expression
8824 JSR coerce_to_integer ; coerce to an integer
8827 .eeti_sync←3← 85B7 JSR← 8875 JSR← 9121 JSR
LDY zp_text_ptr2_off ; Sync the primary text offset
8829 STY zp_text_ptr_off ; copy PtrB offset to PtrA
882B RTS ; Return

Advance the opcode by four addressing-mode columns (+16)

Add 16 to the base opcode in zp_asm_opcode (via +8, +4, +4) to select an addressing mode four columns along.

On EntryZP_ASM_OPCODE (&29)the base opcode
On ExitZP_ASM_OPCODE (&29)the opcode + 16
Athe new opcode
Xpreserved
Ypreserved
882C .asm_opcode_add16←7← 86EF JSR← 8721 JSR← 875A JSR← 879F JSR← 87A2 JSR← 87E7 JSR← 8806 JSR
JSR asm_opcode_add8 ; add 8 then fall through (+16 total)
fall through ↓

Advance the opcode by two addressing-mode columns (+8)

Add 8 to the base opcode in zp_asm_opcode (via +4, +4) to select an addressing mode two columns along.

On EntryZP_ASM_OPCODE (&29)the base opcode
On ExitZP_ASM_OPCODE (&29)the opcode + 8
Athe new opcode
Xpreserved
Ypreserved
882F .asm_opcode_add8←3← 86C2 JSR← 872F JSR← 882C JSR
JSR asm_opcode_add4 ; add 4 then fall through (+8 total)
fall through ↓

Step the opcode to the next addressing-mode column (+4)

Add 4 to the base opcode in &29 to select the next 6502 addressing-mode encoding.

On EntryZP_ASM_OPCODE (&29)the base opcode
On ExitZP_ASM_OPCODE (&29)the opcode + 4
Athe new opcode
Xpreserved
Ypreserved
8832 .asm_opcode_add4←3← 8735 JSR← 8767 JSR← 882F JSR
LDA zp_asm_opcode ; Opcode += 4 (next addressing-mode column)
8834 CLC ; clear carry,
8835 ADC #4 ; add 4 (next mode column),
8837 STA zp_asm_opcode ; store the opcode
8839 RTS ; Return
883A .equb_directive←1← 8813 BNE
LDX #1 ; Assume one byte (EQUB)
883C LDY zp_text_ptr_off ; Next character of the directive
883E INC zp_text_ptr_off ; consume that character
8840 LDA (zp_text_ptr),y ; 'B' (EQUB)?
8842 CMP #'B' ; yes
8844 BEQ equb_save_count ; two bytes (EQUW) 'W' (EQUW)?
8846 INX ; two bytes
8847 CMP #'W' ; yes
8849 BEQ equb_save_count ; four bytes (EQUD)
884B LDX #4 ; 'D' (EQUD)?
884D CMP #'D' ; yes
884F BEQ equb_save_count ; 'S' (EQUS)?
8851 CMP #'S' ; yes
8853 BEQ equs_save_opt ; none: Mistake (syntax error)
8855 JMP syntax_error ; Mistake (syntax) error
8858 .equb_save_count←3← 8844 BEQ← 8849 BEQ← 884F BEQ
TXA ; Save the byte count
8859 PHA ; push it
885A JSR eval_expr_to_integer ; Evaluate the value
885D LDX #&29 ; Store it into the opcode bytes
885F JSR iwa_store_zp ; into &29 onward
8862 PLA ; recover the byte count
8863 TAY ; into Y
8864 .equb_emit_loop←1← 887A BNE
JMP asm_emit ; Assemble the bytes
8867 .equs_type_error←1← 8870 BNE
JMP err_type_mismatch ; String expected: Type mismatch
886A .equs_save_opt←1← 8853 BEQ
LDA zp_opt_flag ; EQUS: save the OPT flag
886C PHA ; push the OPT flag
886D JSR eval_expr ; evaluate the string expression
8870 BNE equs_type_error ; not a string: Type mismatch
8872 PLA ; restore the OPT flag
8873 STA zp_opt_flag ; store it back
8875 JSR eeti_sync ; sync the text offset
8878 LDY #&ff ; flag EQUS (length from the string buffer)
887A BNE equb_emit_loop ; assemble the string bytes
887C .insert_byte←2← 88DD JSR← 8A55 JSR
PHA ; Save the byte to insert
887D CLC ; Source = dest + Y
887E TYA ; offset to A,
887F ADC zp_general ; + dest low,
8881 STA zp_fileblk ; source low (&39),
8883 LDY #0 ; reset the index,
8885 TYA ; A = 0,
8886 ADC zp_general_1 ; + dest high,
8888 STA zp_fileblk_1 ; source high (&3A)
888A PLA ; recover the byte to insert
888B STA (zp_general),y ; store the inserted byte
888D .insert_byte_loop←1← 8894 BNE
INY ; Copy the rest of the line up
888E LDA (zp_fileblk),y ; read from source+Y,
8890 STA (zp_general),y ; write to dest+Y,
8892 CMP #&0d ; until the carriage return
8894 BNE insert_byte_loop ; until the CR
8896 RTS ; Return

Tokenise a line-number reference

Accumulate the decimal digits at (zp_general),Y into a 16-bit value (value*10 + digit, in &3D/&3E) and, on success, replace them in place with the three-byte &8D line-number token (the GOTO/GOSUB encoding). Stops at the first non-digit; reports overflow if the value exceeds 16 bits.

On EntryAthe first decimal digit
ZP_GENERAL (&37/&38)the source text pointer
Yoffset of the first digit
On Exit(ZP_GENERAL)the digits replaced by the &8D 3-byte token
Cclear on success, set on overflow (too large)
8897 .parse_decimal_u16←1← 89B0 JSR
AND #&0f ; First digit
8899 STA zp_fwb_exp ; Value low = digit
889B STY zp_fwb_m1 ; Value high = 0
889D .parse_dec_loop←2← 88CE BCC← 88D2 BPL
INY ; Next character
889E LDA (zp_general),y ; read it
88A0 CMP #':' ; above 9?
88A2 BCS parse_dec_encode ; not a digit: done
88A4 CMP #'0' ; below 0?
88A6 BCC parse_dec_encode ; not a digit: done
88A8 AND #&0f ; Digit value
88AA PHA ; save it
88AB LDX zp_fwb_m1 ; value high
88AD LDA zp_fwb_exp ; value low
88AF ASL ; value * 2
88B0 ROL zp_fwb_m1 ; into the high byte
88B2 BMI parse_dec_overflow ; overflow
88B4 ASL ; value * 4
88B5 ROL zp_fwb_m1 ; into the high byte
88B7 BMI parse_dec_overflow ; overflow
88B9 ADC zp_fwb_exp ; + value = value * 5
88BB STA zp_fwb_exp ; store the low byte
88BD TXA ; now the high byte:
88BE ADC zp_fwb_m1 ; + the x4 high byte (= x5)
88C0 ASL zp_fwb_exp ; * 2 = value * 10
88C2 ROL ; into the high byte
88C3 BMI parse_dec_overflow ; overflow
88C5 BCS parse_dec_overflow ; overflow
88C7 STA zp_fwb_m1 ; store the high byte
88C9 PLA ; Add the digit
88CA ADC zp_fwb_exp ; add the digit to the low byte,
88CC STA zp_fwb_exp ; store it
88CE BCC parse_dec_loop ; next digit
88D0 INC zp_fwb_m1 ; carry into the high byte
88D2 BPL parse_dec_loop ; next digit
88D4 PHA ; overflow marker
88D5 .parse_dec_overflow←4← 88B2 BMI← 88B7 BMI← 88C3 BMI← 88C5 BCS
PLA ; Discard the saved digit
88D6 LDY #0 ; Offset 0
88D8 SEC ; flag a value was read
88D9 RTS ; Return
88DA .parse_dec_encode←2← 88A2 BCS← 88A6 BCC
DEY ; Back up
88DB LDA #&8d ; Line-number token &8D
88DD JSR insert_byte ; Make room for the 3 encoded bytes
88E0 LDA zp_general ; Destination = source + 2
88E2 ADC #2 ; source low + 2
88E4 STA zp_fileblk ; dest low
88E6 LDA zp_general_1 ; source high...
88E8 ADC #0 ; + carry
88EA STA zp_fileblk_1 ; dest high
88EC .parse_dec_shift_loop←1← 88F1 BNE
LDA (zp_general),y ; Shift the bytes up
88EE STA (zp_fileblk),y ; up two bytes
88F0 DEY ; next
88F1 BNE parse_dec_shift_loop ; loop
88F3 LDY #3 ; Three encoded bytes
fall through ↓

Encode a 16-bit line number into 3 bytes

Pack the line number in &3D/&3E into the BBC three-byte GOTO encoding (a control byte holding the scrambled top bits, then low|&40 and high|&40) so the bytes never collide with tokens. The control byte carries only four high bits, so the encoding is exact only for line numbers 0-32767 (above that the top bit is lost - the documented line-number ceiling), and no encoded byte is ever &0D.

On EntryZP_FWB_EXP (&3D/&3E)the 16-bit line number
ZP_GENERAL (&37/&38)the destination text pointer
Yoffset of the last of the three bytes
On Exit(ZP_GENERAL)the three encoded bytes (after the &8D token)
88F5 .encode_line_number←1← 906A JSR
LDA zp_fwb_m1 ; Byte 2 = line-number high | &40
88F7 ORA #&40 ; | &40 (lift out of token range)
88F9 STA (zp_general),y ; store byte 2
88FB DEY ; back to byte 1
88FC LDA zp_fwb_exp ; Byte 1 = line-number low (6 bits) | &40
88FE AND #&3f ; keep the low 6 bits,
8900 ORA #&40 ; | &40
8902 STA (zp_general),y ; store byte 1
8904 DEY ; Byte 0: scramble the top bits
8905 LDA zp_fwb_exp ; low byte: its top 2 bits...
8907 AND #&c0 ; mask them,
8909 STA zp_fwb_exp ; (hold them)
890B LDA zp_fwb_m1 ; high byte: its top 2 bits...
890D AND #&c0 ; mask them,
890F LSR ; shift down,
8910 LSR ; (continued)
8911 ORA zp_fwb_exp ; merge with the low byte pair,
8913 LSR ; shift the four bits down,
8914 LSR ; (continued)
8915 EOR #&54 ; scramble (EOR &54)
8917 STA (zp_general),y ; store the control byte
8919 JSR inc_ptr_general ; Advance past the 3 bytes
891C JSR inc_ptr_general ; (continued)
891F JSR inc_ptr_general ; (continued)
8922 LDY #0 ; done: reset Y for the caller
8924 .not_name_char←3← 8928 BCS← 8930 BCS← 8938 BCS
CLC ; not a name character
8925 RTS ; return (carry clear)

Test A for a name character

Return carry set if A is 0-9, A-Z, a-z or _, the characters allowed in a variable or FN/PROC name.

On EntryAthe character to test
On ExitCset if A is a name character
Apreserved
Xpreserved
Ypreserved
8926 .is_alphanumeric←5← 89CB JSR← 89D4 JSR← 8A43 JSR← 8A74 JSR← B167 JSR
CMP #'{' ; above 'z'?
8928 BCS not_name_char ; yes: no
892A CMP #'_' ; '_' or above?
892C BCS return_2 ; yes: name char
892E CMP #'[' ; '[' to '^'?
8930 BCS not_name_char ; yes: no
8932 CMP #'A' ; 'A' or above?
8934 BCS return_2 ; yes: name char
8936 .is_digit←3← 893F BNE← 896D JSR← 89A7 JSR
CMP #':' ; above '9'?
8938 BCS not_name_char ; yes: no
893A CMP #'0' ; digit?
893C .return_2←2← 892C BCS← 8934 BCS
RTS ; carry set if so
893D .is_dot_or_digit←1← 89B7 JSR
CMP #'.' ; '.'?
893F BNE is_digit ; no: test alphanumeric
8941 RTS ; Return

Read a byte via the general pointer, then advance it

Load the byte at (zp_general),Y and fall through to inc_ptr_general to step the 16-bit pointer on by one.

On Entry(ZP_GENERAL) (&37/&38)the pointer to read through
Yoffset added to the pointer
On ExitAthe byte read
ZP_GENERALadvanced by one
Xpreserved
Ypreserved
8942 .read_via_ptr_general←4← B3D9 JSR← B3E8 JSR← B3F1 JSR← B3F6 JSR
LDA (zp_general),y ; Read the byte at (zp_general)+Y, then advance
fall through ↓

Increment the general 16-bit pointer

Increment the little-endian pointer held in zp_general (&37/&38) by one, carrying into the high byte.

On EntryZP_GENERAL (&37/&38)the pointer to advance
On ExitZP_GENERALincremented by one
Apreserved
Xpreserved
Ypreserved
8944 .inc_ptr_general←8← 8919 JSR← 891C JSR← 891F JSR← 894B JSR← 8961 JSR← 89BC JSR← 89D9 JSR← 8A79 JSR
INC zp_general ; Increment the general pointer: low byte
8946 BNE return_3 ; No carry: done
8948 INC zp_general_1 ; Carry into the high byte
894A .return_3←2← 8946 BNE← 895B BEQ
RTS ; Return (shared)

Advance the general pointer and read the next byte

Call inc_ptr_general to increment the 16-bit general pointer zp_general (&37/&38) by one (carrying into the high byte), then load the byte at (zp_general),Y and return it in A. The tokeniser's fetch-next-character primitive for hex constants and string literals.

On EntryZP_GENERAL (&37)the pointer to advance (&37/&38)
Yindex added to the pointer for the read (usually 0)
On ExitAthe byte read at the incremented pointer
ZP_GENERAL (&37)incremented by one
Ypreserved
894B .general_next_byte←3← 896A JSR← 8980 JSR← BFDC JSR
JSR inc_ptr_general ; Advance the pointer...
894E LDA (zp_general),y ; ...then read the next byte
8950 RTS ; Return the byte

Tokenise a line

Convert the line being entered into its internal form, replacing keywords with tokens via the keyword_table while leaving strings, line numbers and names intact. Works through the buffer using the general pointer (zp_general, &37).

Three entry points share this scanner, differing only in how much of the &3B/&3C state they reset:

  • tokenise_line (&8951): start a fresh line; clear both the start-of-statement flag &3B and the line-number flag &3C.
  • tokenise_resume (&8955): clear only &3C; the caller has already set &3B. EVAL enters here with &3B = mid-statement so a re-tokenised string reads PTR etc. as functions, not assignment targets.
  • tok_scan (&8957): clear neither; execute_line enters here having pre-armed &3C = &FF so a typed leading line number is encoded with the &8D token (then recovered by check_line_number, see &8B2D).
On EntryZP_GENERAL (&37/&38)a pointer to the line text to tokenise
On Exit(ZP_GENERAL)the line rewritten with keyword tokens
8951 .tokenise_line←1← 90C3 JSR
LDY #0 ; Tokeniser state (&3B/&3C): start of statement
8953 STY zp_fwb_sign ; Start-of-statement flag (&3B)
8955 .tokenise_resume←1← AC1A JSR
STY zp_fwb_ovf ; Clear the line-number flag (&3C)
8957 .tok_scan←5← 8964 BNE← 8974 BCC← 897A BCS← 89C8 JMP← 8B2A JSR
LDA (zp_general),y ; Scan the next source character
8959 CMP #&0d ; CR: end of line
895B BEQ return_3 ; Carriage return ends the line
895D CMP #' ' ; space: skip it
895F BNE tok_check_hex ; Skip spaces
8961 .tok_advance←5← 8985 BEQ← 8994 BEQ← 8998 BEQ← 89E9 JMP← 8A89 JMP
JSR inc_ptr_general ; advance and read the next character
8964 BNE tok_scan ; loop
8966 .tok_check_hex←1← 895F BNE
CMP #'&' ; An "&" introduces a hex constant: copy it unchanged
8968 BNE tok_check_string ; not "&": check the other cases
896A .tok_hex_loop←2← 8970 BCS← 8978 BCC
JSR general_next_byte ; Hex constant: advance and get a character
896D JSR is_digit ; a digit?
8970 BCS tok_hex_loop ; yes: keep copying
8972 CMP #'A' ; below 'A'?
8974 BCC tok_scan ; not hex: resume scanning
8976 CMP #'G' ; 'A'..'F'?
8978 BCC tok_hex_loop ; hex letter: keep copying
897A BCS tok_scan ; past F: resume
897C .tok_check_string←1← 8968 BNE
CMP #'"' ; A quote starts a string literal: copy it verbatim
897E BNE tok_check_colon ; not a quote: check for a colon
8980 .tok_string_loop←1← 8989 BNE
JSR general_next_byte ; String literal: copy to the closing quote
8983 CMP #'"' ; a quote?
8985 BEQ tok_advance ; yes: end of string
8987 CMP #&0d ; CR (unterminated)?
8989 BNE tok_string_loop ; no: keep copying
898B RTS ; Return
898C .tok_check_colon←1← 897E BNE
CMP #':' ; A colon starts a new statement: reset the state
898E BNE tok_check_comma_star ; not a colon: check for a comma
8990 STY zp_fwb_sign ; Colon: back to start-of-statement
8992 STY zp_fwb_ovf ; clear the line-number flag
8994 BEQ tok_advance ; continue
8996 .tok_check_comma_star←1← 898E BNE
CMP #',' ; a comma?
8998 BEQ tok_advance ; yes: skip it
899A CMP #'*' ; "*": maybe a *command (statement position checked next)
899C BNE tok_check_number ; not "*": try a keyword or name
899E LDA zp_fwb_sign ; at the start of a statement?
89A0 BNE tok_not_keyword ; no (mid-statement): "*" is the multiply operator
89A2 RTS ; yes: *command - RTS leaves the rest untokenised
89A3 .tok_check_number←1← 899C BNE
CMP #'.' ; a "." (abbreviation dot)?
89A5 BEQ tok_skip_number_loop ; yes
89A7 JSR is_digit ; a digit?
89AA BCC tok_check_letter ; no: a letter or symbol
89AC LDX zp_fwb_ovf ; line-number armed (after GOTO etc.)?
89AE BEQ tok_skip_number_loop ; not armed: an ordinary number
89B0 JSR parse_decimal_u16 ; tokenise the line number
89B3 BCC tok_continue ; continue
89B5 .tok_skip_number_loop←3← 89A5 BEQ← 89AE BEQ← 89BF JMP
LDA (zp_general),y ; Skip a number: read a character
89B7 JSR is_dot_or_digit ; a digit or "."?
89BA BCC tok_resume_mid ; no: end of the number
89BC JSR inc_ptr_general ; advance
89BF JMP tok_skip_number_loop ; loop
89C2 .tok_resume_mid←2← 89BA BCC← 89D7 BCC
LDX #&ff ; Now in the middle of a statement:
89C4 STX zp_fwb_sign ; set the flag
89C6 STY zp_fwb_ovf ; clear the line-number flag
89C8 JMP tok_scan ; resume scanning
89CB .tok_skip_name←1← 89EE BCS
JSR is_alphanumeric ; Skip a variable name: alphanumeric?
89CE BCC tok_not_keyword ; no: not a name
89D0 .tok_name←2← 8A16 BCS← 8A46 BCS
LDY #0 ; Consume the whole name run; no interior keyword match
89D2 .tok_name_loop←2← 89DC JMP← 89FA BCC
LDA (zp_general),y ; read it
89D4 JSR is_alphanumeric ; alphanumeric?
89D7 BCC tok_resume_mid ; no: end of the name
89D9 JSR inc_ptr_general ; advance
89DC JMP tok_name_loop ; loop
89DF .tok_check_letter←1← 89AA BCC
CMP #'A' ; a letter ('A'+)?
89E1 BCS tok_try_keyword ; yes: try to match a keyword
89E3 .tok_not_keyword←2← 89A0 BNE← 89CE BCC
LDX #&ff ; Not a keyword: middle of statement
89E5 STX zp_fwb_sign ; set mid-statement,
89E7 STY zp_fwb_ovf ; clear the line-number flag
89E9 .tok_continue←1← 89B3 BCC
JMP tok_advance ; continue scanning
89EC .tok_try_keyword←1← 89E1 BCS
CMP #'X' ; 'X' or above?
89EE BCS tok_skip_name ; nothing starts with X/Y/Z: skip the name
89F0 LDX #&71 ; Point at the keyword table (&8071): low
89F2 STX zp_fileblk ; (store)
89F4 LDX #&80 ; high &80
89F6 STX zp_fileblk_1 ; (store)
89F8 .tok_kw_entry←1← 8A34 JMP
CMP (zp_fileblk),y ; Compare the first letter with this entry
89FA BCC tok_name_loop ; entry past our letter: not a keyword
89FC BNE tok_kw_check_end ; first letter differs: next entry
89FE .tok_kw_match_loop←1← 8A05 BEQ
INY ; matches: compare the rest of the keyword
89FF LDA (zp_fileblk),y ; entry char (bit 7 = the token)
8A01 BMI tok_kw_found ; whole keyword matched: got a token
8A03 CMP (zp_general),y ; compare with the line
8A05 BEQ tok_kw_match_loop ; match: next character
8A07 LDA (zp_general),y ; mismatch: a "." abbreviation?
8A09 CMP #'.' ; is it "."?
8A0B BEQ tok_kw_abbrev ; yes: accept the abbreviation
8A0D .tok_kw_check_end←2← 89FC BNE← 8A10 BPL
INY ; Skip to the next entry: past the name
8A0E LDA (zp_fileblk),y ; read it
8A10 BPL tok_kw_check_end ; until the token byte (bit 7 set)
8A12 CMP #&fe ; WIDTH token &FE doubles as the end-of-table sentinel
8A14 BNE tok_kw_next_entry ; no: a real token, try the next entry
8A16 BCS tok_name ; sentinel: not a keyword (skip the name)
8A18 .tok_kw_abbrev←1← 8A0B BEQ
INY ; Abbreviation: skip to this entry's token
8A19 .tok_kw_skip_loop←2← 8A1F BNE← 8A23 BNE
LDA (zp_fileblk),y ; read it
8A1B BMI tok_kw_found ; token byte: got it
8A1D INC zp_fileblk ; advance the table pointer
8A1F BNE tok_kw_skip_loop ; no wrap
8A21 INC zp_fileblk_1 ; carry into high
8A23 BNE tok_kw_skip_loop ; loop
8A25 .tok_kw_next_entry←1← 8A14 BNE
SEC ; Advance past this entry to the next:
8A26 INY ; (include the token byte)
8A27 TYA ; offset...
8A28 ADC zp_fileblk ; low
8A2A STA zp_fileblk ; (store)
8A2C BCC tok_kw_retry ; no carry
8A2E INC zp_fileblk_1 ; carry into high
8A30 .tok_kw_retry←1← 8A2C BCC
LDY #0 ; reset Y
8A32 LDA (zp_general),y ; re-read the first letter
8A34 JMP tok_kw_entry ; try the next entry
8A37 .tok_kw_found←2← 8A01 BMI← 8A1B BMI
TAX ; Token byte found: keep it in X
8A38 INY ; the flag byte follows
8A39 LDA (zp_fileblk),y ; get the token flag
8A3B STA zp_fwb_exp ; (save it in &3D)
8A3D DEY ; back up Y
8A3E LSR ; flag bit 0: conditional tokenisation?
8A3F BCC tok_emit_token ; no
8A41 LDA (zp_general),y ; a name char follows? (then keep it as a name)
8A43 JSR is_alphanumeric ; test it
8A46 BCS tok_name ; yes: keep it as a name, not a token
8A48 .tok_emit_token←1← 8A3F BCC
TXA ; Emit the token: A = token byte
8A49 BIT zp_fwb_exp ; flag bit 6: pseudo-variable?
8A4B BVC tok_write_token ; no
8A4D LDX zp_fwb_sign ; at the start of a statement?
8A4F BNE tok_write_token ; no
8A51 CLC ; superfluous: all paths reach here carry-clear
8A52 ADC #&40 ; assignment form: token + &40
8A54 .tok_write_token←2← 8A4B BVC← 8A4F BNE
DEY ; Write the token over the keyword
8A55 JSR insert_byte ; overwrite the keyword
8A58 LDY #0 ; reset Y
8A5A LDX #&ff ; X = &FF (mid-statement marker)
8A5C LDA zp_fwb_exp ; Apply the state-change flags:
8A5E LSR ; bit 0 (already used)
8A5F LSR ; bit 1: enter middle-of-statement?
8A60 BCC tok_flag_start_stmt ; no
8A62 STX zp_fwb_sign ; set middle-of-statement
8A64 STY zp_fwb_ovf ; clear the line-number flag
8A66 .tok_flag_start_stmt←1← 8A60 BCC
LSR ; bit 2: enter start-of-statement?
8A67 BCC tok_flag_fnproc ; no
8A69 STY zp_fwb_sign ; set start-of-statement
8A6B STY zp_fwb_ovf ; clear the line-number flag
8A6D .tok_flag_fnproc←1← 8A67 BCC
LSR ; bit 3: FN/PROC (do not tokenise the name)?
8A6E BCC tok_flag_linenum ; no
8A70 PHA ; save A
8A71 INY ; skip the FN/PROC name untokenised:
8A72 .tok_skip_fnproc_loop←1← 8A7C JMP
LDA (zp_general),y ; char
8A74 JSR is_alphanumeric ; alphanumeric?
8A77 BCC tok_skip_fnproc_done ; no: end of the name
8A79 JSR inc_ptr_general ; advance
8A7C JMP tok_skip_fnproc_loop ; loop
8A7F .tok_skip_fnproc_done←1← 8A77 BCC
DEY ; step back
8A80 PLA ; restore A
8A81 .tok_flag_linenum←1← 8A6E BCC
LSR ; bit 4: start a line number?
8A82 BCC tok_flag_skipline ; no
8A84 STX zp_fwb_ovf ; set the line-number flag
8A86 .tok_flag_skipline←1← 8A82 BCC
LSR ; bit 5: skip the rest of the line (REM/DATA)?
8A87 BCS return_4 ; yes: stop tokenising
8A89 JMP tok_advance ; continue scanning

Skip spaces at the secondary text pointer

As skip_spaces, but works on the secondary text pointer (zp_text_ptr2, &19) and its offset (&1B). Used while the primary pointer is preserved, e.g. when scanning ahead during assignment.

On ExitAfirst non-space character
Yits offset
&1Badvanced past that character
8A8C .skip_spaces_ptr2←21← 8A94 BEQ← 8AAE JSR← 8D32 JSR← 8E43 JSR← 8EE3 JSR← 9841 JSR← AC50 JSR← AC5B JSR← AC66 JSR← ADAD JSR← AE02 JSR← B094 JSR← B287 JSR← B2A0 JSR← B7EA JSR← B813 JSR← B866 JSR← BABD JSR← BB60 JSR← BB6F JSR← BFB5 JSR
LDY zp_text_ptr2_off ; Get the secondary text offset
8A8E INC zp_text_ptr2_off ; advance it
8A90 LDA (zp_text_ptr2),y ; Read the character
8A92 CMP #' ' ; Loop while the character is a space
8A94 BEQ skip_spaces_ptr2 ; Space: keep skipping
8A96 .return_4←1← 8A87 BCS
RTS ; Return the first non-space character

Skip spaces at the text pointer

Advance the primary text pointer past any spaces and return the first non-space character. The workhorse of the tokeniser and interpreter: most statements call it between syntax elements.

On EntryZP_TEXT_PTR (&0B)points at the line being interpreted
ZP_TEXT_PTR_OFF (&0A)offset of the next character
On ExitAfirst non-space character
Yits offset within the line
ZP_TEXT_PTR_OFF (&0A)advanced past that character
8A97 .skip_spaces←48← 8508 JSR← 85BC JSR← 86BB JSR← 86D7 JSR← 86E1 JSR← 86E8 JSR← 86F2 JSR← 86FF JSR← 8706 JSR← 871A JSR← 8724 JSR← 8747 JSR← 8753 JSR← 875D JSR← 8776 JSR← 878E JSR← 87A8 JSR← 87C1 JSR← 87D4 JSR← 87DE JSR← 87F6 JSR← 87FD JSR← 8A9F BEQ← 8B38 JSR← 8D89 JSR← 8D9A JSR← 8DC3 JSR← 8E8A JSR← 8F39 JSR← 8F79 JSR← 912F JSR← 91A9 JSR← 920B JSR← 9349 JSR← 942F JSR← 9446 JSR← B13F JSR← B1F9 JSR← B279 JSR← B5BE JSR← B5CF JSR← B75C JSR← B8F2 JSR← B915 JSR← B9DA JSR← BA44 JSR← BAEE JSR← BB15 JSR
LDY zp_text_ptr_off ; Get the text offset
8A99 INC zp_text_ptr_off ; advance it for next time
8A9B LDA (zp_text_ptr),y ; Read the character
8A9D CMP #' ' ; Loop while the character is a space
8A9F BEQ skip_spaces ; Space: keep skipping
8AA1 RTS ; Return the first non-space character

Raise "Missing ," error

Raise BASIC error &05, "Missing ,", via a BRK error block. Reached from the argument-list parsers when a required comma is absent. Does not return.

On ExitCONTROLraises BRK error &05 "Missing ,"; does not return to the caller
8AA2 .missing_comma←4← 8AB3 BNE← 8E21 JMP← AD03 JMP← B036 JMP
BRK ; BRK error block ("Missing ,") follows
8AA3 EQUB &05
8AA4 EQUS "Missing ,"
8AAD EQUB &00

Skip spaces and require a comma

Skip spaces at PtrB, then check the character is a comma. Raises "Missing ," if it is not. Used by statements that take a comma-separated argument list.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the parser position
On ExitAthe comma (consumed)
ZP_TEXT_PTR2_OFF (&1B)advanced past the comma
BRKMissing , if a comma is absent
8AAE .skip_spaces_expect_comma←5← 92DA JSR← 93F7 JSR← AB47 JSR← B0C8 JSR← BF5C JSR
JSR skip_spaces_ptr2 ; Skip spaces at PtrB
8AB1 CMP #',' ; Require a comma
8AB3 BNE missing_comma ; Missing: "Missing ," error
8AB5 RTS ; Return

OLD

Recover the program cleared by NEW, if memory is intact. OLD.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8AB6 .stmt_old
JSR check_end_of_statement ; Check the statement ends
8AB9 LDA zp_page ; Point at PAGE
8ABB STA zp_general_1 ; high byte (&38),
8ABD LDA #0 ; low byte 0,
8ABF STA zp_general ; to &37
8AC1 STA (zp_general),y ; Remove the end marker
8AC3 JSR check_program ; Re-check the program and set TOP
8AC6 BNE clear_then_immediate ; clear heap and return to immediate mode
fall through ↓

END

End the program and return to the immediate prompt. END.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8AC8 .stmt_end
JSR check_end_of_statement ; Check the statement ends
8ACB JSR check_program ; Check the program
8ACE BNE immediate_loop ; return to immediate mode (keep variables)
fall through ↓

STOP

Stop the program, reporting "STOP at line nnnn". STOP.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8AD0 .stmt_stop
JSR check_end_of_statement ; Check the statement ends
8AD3 BRK ; STOP error
8AD4 EQUB &00
8AD5 EQUS "STOP"
8AD9 EQUB &00

NEW

Clear the current program and its variables. NEW.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8ADA .stmt_new
JSR check_end_of_statement ; Check the statement ends
fall through ↓

Clear program and enter the immediate loop

Entered from language_startup. Sets up an empty program (a CR and an &FF end marker at PAGE, TOP = PAGE+2), clears variables/heap/stack, and falls through into the immediate loop. Does not return.

On EntryZP_PAGE (&18)PAGE, the start of program memory
On ExitCONTROLfalls into immediate_loop (does not return)
8ADD .start_new_program←1← 806E JMP
LDA #&0d ; Empty program is a single CR...
8ADF LDY zp_page ; TOP starts at PAGE: high byte
8AE1 STY zp_top_1 ; (TOP high)
8AE3 LDY #0 ; low byte 0
8AE5 STY zp_top ; TOP low = PAGE
8AE7 STY zp_trace_flag ; TRACE off
8AE9 STA (zp_top),y ; ...stored as the end marker at PAGE
8AEB LDA #&ff ; &FF...
8AED INY ; next byte
8AEE STA (zp_top),y ; ...marks the program end at PAGE+1
8AF0 INY ; TOP = PAGE + 2
8AF1 STY zp_top ; (store)
8AF3 .clear_then_immediate←6← 8AC6 BNE← 8B35 JMP← 8F66 JMP← 903A JMP← 90D9 JMP← BF27 JMP
JSR clear_vars_heap_stack ; Clear variables, heap and stack
fall through ↓

Immediate ("> ") loop

Print the prompt, read a line into the input buffer (PtrA = &0700), and tokenise it. A line that begins with a line number is inserted into the program; otherwise it is executed immediately. The top-level loop - does not return.

On ExitCONTROLloops forever reading and running immediate lines
8AF6 .immediate_loop←7← 859C JMP← 8ACE BNE← 8B41 JMP← 98BC JMP← B599 JMP← B61A JMP← BEAF JMP
LDY #7 ; PtrA = &0700, the input buffer: high byte
8AF8 STY zp_text_ptr_1 ; (store)
8AFA LDY #0 ; low byte 0
8AFC STY zp_text_ptr ; (store)
8AFE LDA #&33 ; ON ERROR OFF: default handler at &B433
8B00 STA zp_error_vec ; (low)
8B02 LDA #&b4 ; (high)
8B04 STA zp_error_vec_1 ; (store)
8B06 LDA #'>' ; The ">" prompt
8B08 JSR read_input_line ; Print it and read a line into the buffer
fall through ↓

Execute the line at the program pointer

Run the tokenised statements on the line addressed by the program pointer (zp_text_ptr, &0B), starting at offset zp_text_ptr_off. Resets the error handler and the 6502 stack, tokenises the line, and either inserts a numbered line or runs it via the statement loop.

On EntryZP_TEXT_PTR (&0B/&0C)the line to execute
ZP_TEXT_PTR_OFF (&0A)the starting offset
On ExitCONTROLruns the line (numbered lines are inserted instead)
8B0B .execute_line←1← BD1D JMP
LDA #&33 ; Restore the default error handler (ON ERROR OFF)
8B0D STA zp_error_vec ; low byte = &33,
8B0F LDA #&b4 ; high byte = &B4,
8B11 STA zp_error_vec_1 ; store it (handler at &B433)
8B13 LDX #&ff ; OPT = &FF: not inside the [ ] assembler
8B15 STX zp_opt_flag ; store the OPT flag
8B17 STX zp_fwb_ovf ; Arm line-number encoding (&3C) for a leading line number
8B19 TXS ; Reset the 6502 hardware stack
8B1A JSR reset_data_and_stacks ; Clear the DATA pointer and the BASIC stacks
8B1D TAY ; Y = 0
8B1E LDA zp_text_ptr ; Point the general pointer at the line text
8B20 STA zp_general ; low byte,
8B22 LDA zp_text_ptr_1 ; high byte,
8B24 STA zp_general_1 ; store it
8B26 STY zp_fwb_sign ; start of statement (&3B = 0)
8B28 STY zp_text_ptr_off ; and the offset
8B2A JSR tok_scan ; Tokenise (&3C pre-armed for a leading number)
8B2D JSR check_line_number ; reuse the &8D codec: decode the leading line number
8B30 BCC exec_dispatch ; no line number: execute it
8B32 JSR insert_line ; numbered line: insert it (IWA = the line number)
8B35 JMP clear_then_immediate ; inserted: immediate loop
8B38 .exec_dispatch←1← 8B30 BCC
JSR skip_spaces ; Skip spaces
8B3B CMP #&c6 ; Token >= &C6 is a command: dispatch it
8B3D BCS dispatch_token ; command token: dispatch it
8B3F BCC try_variable_assignment ; Otherwise treat it as a variable assignment
8B41 .exec_immediate←1← 8B8F BEQ
JMP immediate_loop ; Back to immediate mode
8B44 .exec_assembler←1← 8B6F BEQ
JMP asm_enter ; Enter the assembler
8B47 .fn_return←1← 8B67 BEQ
TSX ; Inside a function call?
8B48 CPX #&fc ; stack near empty (no FN frame)?
8B4A BCS no_fn_error ; no: error
8B4C LDA hw_stack_top ; Pushed token
8B4F CMP #&a4 ; FN?
8B51 BNE no_fn_error ; no: error
8B53 JSR eval_expr ; Evaluate the return value
8B56 JMP assign_check_end ; check end, return from the function
8B59 .no_fn_error←2← 8B4A BCS← 8B51 BNE
BRK ; No FN error
8B5A EQUB &07
8B5B EQUS "No "
8B5E EQUB &A4, &00

Check for =, * and [ statements

Recognise the statement forms that are not introduced by a token: "=" (return a value from FN), "*" (pass the rest of the line to OSCLI), and "[" (enter the inline assembler). Dispatches to the matching handler, otherwise checks for end of statement.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the introducing character
On ExitCONTROLtail-jumps to the FN-return, OSCLI or assembler handler, else falls into the end-of-statement check
8B60 .check_eq_star_bracket←1← 8BCE BCS
LDY zp_text_ptr_off ; Step back to the introducing character
8B62 DEY ; one character back
8B63 LDA (zp_text_ptr),y ; fetch it
8B65 CMP #'=' ; "=" returns a value from a function (FN)
8B67 BEQ fn_return ; "=": return a value from a function
8B69 CMP #'*' ; "*" passes the rest of the line to OSCLI
8B6B BEQ exec_star_command ; "*": an embedded OSCLI command
8B6D CMP #'[' ; "[" enters the inline assembler
8B6F BEQ exec_assembler ; "[": enter the assembler
8B71 BNE stmt_backup_end ; otherwise check for end of statement
8B73 .exec_star_command←1← 8B6B BEQ
JSR skip_to_statement_end ; Point PtrA at the command text
8B76 LDX zp_text_ptr ; XY -> the command string
8B78 LDY zp_text_ptr_1 ; (high byte)
8B7A JSR oscli ; Pass it to OSCLI
fall through ↓

DATA / DEF / REM / ELSE

Skip to the end of the line. DATA introduces inline data (read by READ), DEF a PROC/FN definition, REM a comment, and ELSE the alternative of a taken IF: none execute inline, so all four share this skip-to-end handler.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8B7D .stmt_data←2← 8B89 BEQ← B907 JMP
LDA #&0d ; CR
8B7F LDY zp_text_ptr_off ; Line offset
8B81 DEY ; back up one (the loop pre-increments)
8B82 .data_scan_loop←1← 8B85 BNE
INY ; Scan to the end of line
8B83 CMP (zp_text_ptr),y ; reached the CR?
8B85 BNE data_scan_loop ; no: keep scanning
8B87 .stmt_eol←2← 8BA1 BNE← 9902 JMP
CMP #&8b ; ELSE?
8B89 BEQ stmt_data ; yes: skip to end of line
8B8B LDA zp_text_ptr_1 ; In the command buffer?
8B8D CMP #7 ; page &07 (command buffer)?
8B8F BEQ exec_immediate ; yes: immediate mode
8B91 JSR step_to_next_line ; Check for end of program, step past CR
8B94 BNE next_statement ; more: next statement
8B96 .stmt_backup_end←7← 8B71 BNE← 8D80 JMP← 9212 JMP← 9350 JMP← 9453 JMP← B7A1 JMP← BB1C JMP
DEC zp_text_ptr_off ; Back up
8B98 .stmt_check_end←4← 8D7A JMP← 9353 JMP← B9CC JMP← BA41 JMP
JSR check_end_of_statement ; check the statement ends
fall through ↓

Statement execution loop

The main interpreter loop: fetch the next statement's leading token and dispatch it, then advance to the next statement (after a colon) or line. Statement handlers jmp back here when they finish, so it is the common continuation rather than a callable subroutine.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset at the next statement
On ExitCONTROLdispatches the statement; handlers return here
8B9B .statement_loop←23← 8BF8 JMP← 8C08 JMP← 8ECF JMP← 8F18 JMP← 926C JMP← 928A JMP← 92B4 JMP← 92D7 JMP← 9318 JMP← 93E1 JMP← 9427 JMP← B49D JMP← B4AB JMP← B8C9 JMP← B8EF JMP← BB12 JMP← BBCA JMP← BECC JMP← BF21 JMP← BF43 JMP← BF6C JMP← BFA6 JMP← BFF6 JMP
LDY #0 ; Fetch the next character of the statement
8B9D LDA (zp_text_ptr),y ; Get the current character
8B9F CMP #':' ; A colon separates statements on a line
8BA1 BNE stmt_eol ; Not a colon: check for ELSE / end of line
fall through ↓

Execute the next statement on the line

The interpreter's per-statement entry point. Read the character at PtrA+offset while advancing zp_text_ptr_off (&0A) past the ':' separator, skipping spaces. A token >= &CF falls into dispatch_token, which indexes the action-address table and JMP (&0037)s to its handler; a byte below &CF is not a command token, so it branches to try_variable_assignment for an implied LET. Non-returning: every path transfers to a statement handler.

On EntryZP_TEXT_PTR (&0B)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset at the statement start (the ':' or first character)
On ExitCONTROLnon-returning; dispatches via dispatch_token JMP (&0037) to a keyword handler, or branches to try_variable_assignment
8BA3 .next_statement←10← 8501 JMP← 8B94 BNE← 8BAB BEQ← 98DE JMP← B20B JSR← B430 JMP← B74E JMP← B84C JMP← B8E1 JMP← BBF9 JMP
LDY zp_text_ptr_off ; Skip spaces to the next statement
8BA5 INC zp_text_ptr_off ; advance past the colon
8BA7 LDA (zp_text_ptr),y ; Get the next character
8BA9 CMP #' ' ; Skip spaces
8BAB BEQ next_statement ; loop
8BAD CMP #&cf ; Below &CF: a variable assignment, not a command
8BAF BCC try_variable_assignment ; Below &CF: a variable assignment
fall through ↓

Dispatch a tokenised function or command

Index the action-address table by (token - &8E): load the handler address into zp_general (&37/&38) and JMP (&0037). This is the indirect jump that reaches every fn_* / stmt_* handler.

On EntryAa command/function token (&8E-&FF)
8BB1 .dispatch_token←2← 8B3D BCS← AE0D JMP
TAX ; Token to X for indexing
8BB2 LDA action_lo_by_token,x ; Handler low byte = action_table_lo[token - &8E]
8BB5 STA zp_general ; Store the handler low byte
8BB7 LDA action_hi_by_token,x ; Handler high byte from action_table_hi
8BBA STA zp_general_1 ; Store the handler high byte
8BBC JMP (zp_general) ; Jump to the keyword handler

Not a command token: try an assignment

Reached when the statement does not begin with a command token: copy PtrA to PtrB, parse a variable / indirection reference, and perform an implied-LET assignment (creating the variable if new). Falls back to the =, * (OSCLI) or [ (assembler) special statement forms.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
Yoffset of the statement start
On ExitCONTROLperforms the assignment then rejoins statement_loop, or dispatches to the =, * or [ form
8BBF .try_variable_assignment←2← 8B3F BCC← 8BAF BCC
LDX zp_text_ptr ; Copy PtrA to PtrB: low
8BC1 STX zp_text_ptr2 ; (store)
8BC3 LDX zp_text_ptr_1 ; high
8BC5 STX zp_text_ptr2_1 ; (store)
8BC7 STY zp_text_ptr2_off ; offset
8BC9 JSR pvr_parse ; Parse a variable / indirection reference
8BCC BNE let_assign ; Existing variable: do the assignment
8BCE BCS check_eq_star_bracket ; Not a variable: try =, * or [
8BD0 STX zp_text_ptr2_off ; New variable: position for "="
8BD2 JSR expect_eq ; Require "="
8BD5 JSR create_variable ; Create the new variable
8BD8 LDX #5 ; Type 5 = floating point
8BDA CPX zp_iwa_2 ; Is the destination a float?
8BDC BNE assign_new_var ; no
8BDE INX ; X = 6
8BDF .assign_new_var←1← 8BDC BNE
JSR clear_value_bytes ; Evaluate and store the value
8BE2 DEC zp_text_ptr_off ; Step back, continue
fall through ↓

LET

Assign an expression to a variable; the LET keyword is optional. [LET] var = expr.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8BE4 .stmt_let
JSR parse_lvalue ; Parse the variable being assigned
8BE7 BEQ let_mistake ; end of statement: error
8BE9 .let_assign←1← 8BCC BNE
BCC let_numeric ; numeric target?
8BEB JSR stack_integer ; Stack the destination address
8BEE JSR eval_after_eq ; Expect "=" and evaluate the right-hand side
8BF1 LDA zp_var_type ; value type
8BF3 BNE err_type_mismatch ; A string variable needs a string value
8BF5 JSR assign_string ; Store the string
8BF8 JMP statement_loop ; next statement
8BFB .let_numeric←1← 8BE9 BCC
JSR stack_integer ; Stack the destination address
8BFE JSR eval_after_eq ; Expect "=" and evaluate
8C01 LDA zp_var_type ; value type
8C03 BEQ err_type_mismatch ; A numeric variable needs a numeric value
8C05 JSR assign_number ; Store the number
8C08 JMP statement_loop ; next statement
8C0B .let_mistake←1← 8BE7 BEQ
JMP syntax_error ; Mistake (syntax) error

Raise "Type mismatch" error

Raise BASIC error &06, "Type mismatch", via a BRK error block. Reached (18 callers) whenever a routine finds a string where a number is required, or vice versa. Does not return.

On ExitCONTROLraises BRK error &06 "Type mismatch"; does not return to the caller
8C0E .err_type_mismatch←18← 8867 JMP← 8BF3 BNE← 8C03 BEQ← 92F7 JMP← 98BF JMP← 9A9A JMP← 9C88 JMP← 9D39 JMP← ABE6 JMP← AC9B JMP← AD67 JMP← AECE JMP← B033 JMP← B0BF JMP← B4AE JMP← B9C4 JMP← BECF JMP← BF96 JMP
BRK ; Type mismatch error
8C0F EQUB &06
8C10 EQUS "Type mismatch"
8C1D EQUB &00

Store a string value into a variable

Assign the string in the string buffer to the string variable whose descriptor address is on the stack. Reuses the existing allocation if the new string fits, otherwise grabs fresh space from the heap.

On Entry(ZP_STACK_PTR) (&04/&05)the variable descriptor address
STRING_WORK (&0600)the string characters
ZP_STRBUF_LEN (&36)the string length
On ExitTHE STRING VARIABLEupdated (allocation reused or grown)
8C1E .assign_string←3← 8BF5 JSR← BA13 JSR← BB3D JSR
JSR unstack_integer ; Unstack the variable descriptor address
fall through ↓

Assign a string to a variable descriptor already in IWA

Alternate entry to assign_string that assumes the variable descriptor address is already in zp_iwa (&2A/&2B) rather than on the stack. Assign the string in the string buffer to that variable, reusing the existing allocation if the new string fits, otherwise growing it in place or grabbing fresh heap space.

On EntryZP_IWA (&2A/&2B)the variable descriptor address
ZP_IWA_2 (&2C)the variable type (&80 = absolute $-string address)
STRING_WORK (&0600)the string characters
ZP_STRBUF_LEN (&36)the string length
8C21 .assign_string_to←2← B300 JSR← BAE0 JSR
LDA zp_iwa_2 ; Variable type
8C23 CMP #&80 ; An absolute $-string address?
8C25 BEQ assign_str_addr ; yes
8C27 LDY #2 ; Bytes currently allocated
8C29 LDA (zp_iwa),y ; read it
8C2B CMP zp_strbuf_len ; Does the new string fit the existing allocation?
8C2D BCS assign_str_store ; yes: reuse the allocation
8C2F LDA zp_vartop ; Tentative new address = heap top
8C31 STA zp_iwa_2 ; (low)
8C33 LDA zp_vartop_1 ; high...
8C35 STA zp_iwa_3 ; (high)
8C37 LDA zp_strbuf_len ; Round the size up (min 8, granularity 8)
8C39 CMP #8 ; at least 8?
8C3B BCC assign_str_alloc_size ; under 8: take it as is
8C3D ADC #7 ; else add 7 (round up),
8C3F BCC assign_str_alloc_size ; no overflow
8C41 LDA #&ff ; cap at 255
8C43 .assign_str_alloc_size←2← 8C3B BCC← 8C3F BCC
CLC ; (clear carry)
8C44 PHA ; Save the new allocation size
8C45 TAX ; keep the size in X
8C46 LDA (zp_iwa),y ; Is the existing block at the heap top?
8C48 LDY #0 ; offset 0 (data low)
8C4A ADC (zp_iwa),y ; (data address + allocated == top?)
8C4C EOR zp_vartop ; compare with heap top low
8C4E BNE assign_str_alloc ; no: allocate fresh space
8C50 INY ; high byte:
8C51 ADC (zp_iwa),y ; + allocated,
8C53 EOR zp_vartop_1 ; == heap top high?
8C55 BNE assign_str_alloc ; no: allocate fresh space
8C57 STA zp_iwa_3 ; yes: extend in place from this block
8C59 TXA ; new size...
8C5A INY ; offset 2 (old allocation)
8C5B SEC ; new - old...
8C5C SBC (zp_iwa),y ; reclaim the old allocation
8C5E TAX ; X = extra bytes needed
8C5F .assign_str_alloc←2← 8C4E BNE← 8C55 BNE
TXA ; New heap top = top + allocation
8C60 CLC ; (clear carry)
8C61 ADC zp_vartop ; + heap top low,
8C63 TAY ; (low in Y)
8C64 LDA zp_vartop_1 ; heap top high...
8C66 ADC #0 ; + carry (new top high)
8C68 CPY zp_stack_ptr ; collides with the stack?
8C6A TAX ; (keep the new top high)
8C6B SBC zp_stack_ptr_1 ; new top - stack pointer
8C6D BCS err_no_room ; yes: No room
8C6F STY zp_vartop ; Commit the new heap top
8C71 STX zp_vartop_1 ; (high)
8C73 PLA ; Store the new allocation size
8C74 LDY #2 ; offset 2
8C76 STA (zp_iwa),y ; (store)
8C78 DEY ; Store the new data address
8C79 LDA zp_iwa_3 ; data address high
8C7B BEQ assign_str_store ; extended in place: keep the address
8C7D STA (zp_iwa),y ; store the data high,
8C7F DEY ; offset 0
8C80 LDA zp_iwa_2 ; data low...
8C82 STA (zp_iwa),y ; (store)
8C84 .assign_str_store←2← 8C2D BCS← 8C7B BEQ
LDY #3 ; Store the current length
8C86 LDA zp_strbuf_len ; the length
8C88 STA (zp_iwa),y ; (store)
8C8A BEQ return_5 ; empty string: done
8C8C DEY ; Fetch the data address
8C8D DEY ; (to offset 1)
8C8E LDA (zp_iwa),y ; high...
8C90 STA zp_iwa_3 ; (high)
8C92 DEY ; offset 0
8C93 LDA (zp_iwa),y ; low...
8C95 STA zp_iwa_2 ; (low)
8C97 .assign_str_copy_loop←1← 8C9F BNE
LDA string_work,y ; Copy the string buffer to the storage
8C9A STA (zp_iwa_2),y ; to storage
8C9C INY ; next
8C9D CPY zp_strbuf_len ; done?
8C9F BNE assign_str_copy_loop ; loop
8CA1 .return_5←1← 8C8A BEQ
RTS ; Return
8CA2 .assign_str_addr←1← 8C25 BEQ
JSR terminate_strbuf ; $addr: prepare the destination
8CA5 CPY #0 ; empty string?
8CA7 BEQ assign_str_addr_cr ; yes: just the terminator
8CA9 .assign_str_addr_loop←1← 8CAF BNE
LDA string_work,y ; Copy the string to the address
8CAC STA (zp_iwa),y ; store it
8CAE DEY ; next (downwards)
8CAF BNE assign_str_addr_loop ; loop
8CB1 LDA string_work ; First character
8CB4 .assign_str_addr_cr←1← 8CA7 BEQ
STA (zp_iwa),y ; Store it (with the CR terminator following)
8CB6 RTS ; Return

Raise the "No room" error

Raise BASIC error &00, "No room", via a BRK error block. Reached when there is insufficient memory for the requested operation. Does not return.

On ExitCONTROLraises BRK error &00 "No room"; does not return to the caller
8CB7 .err_no_room←3← 8C6D BCS← 9553 JMP← BE41 JMP
BRK ; No room error
8CB8 EQUB &00
8CB9 EQUS "No room"
8CC0 EQUB &00

Restore a stacked value into a variable

Pop the value on top of the BASIC stack into the variable at zp_general, dispatched by the type/size byte: a numeric value of that many bytes, a $addr string (CR terminated), or a string variable (copied into its heap allocation). Used by the FN/PROC LOCAL and parameter machinery.

On EntryZP_GENERAL (&37/&38)the destination variable address
ZP_FILEBLK (&39)the type/size byte
(ZP_STACK_PTR) (&04/&05)the saved value on top of stack
On ExitTHE VARIABLErestored from the stack
ZP_STACK_PTRadvanced past the popped value
8CC1 .unstack_value_to_var←1← B21F JSR
LDA zp_fileblk ; Type/size byte
8CC3 CMP #&80 ; $addr string?
8CC5 BEQ unstack_addr ; yes
8CC7 BCC unstack_numeric ; numeric?
8CC9 LDY #0 ; String variable: length on the stack
8CCB LDA (zp_stack_ptr),y ; read the length,
8CCD TAX ; into X as the byte count
8CCE BEQ unstack_str_setlen ; empty: just set the length
8CD0 LDA (zp_general),y ; Data address - 1 (for 1-based copy)
8CD2 SBC #1 ; low byte - 1,
8CD4 STA zp_fileblk ; dest pointer low (&39),
8CD6 INY ; next
8CD7 LDA (zp_general),y ; high byte
8CD9 SBC #0 ; - borrow,
8CDB STA zp_fileblk_1 ; dest pointer high (&3A)
8CDD .unstack_str_copy←1← 8CE3 BNE
LDA (zp_stack_ptr),y ; Copy the string bytes
8CDF STA (zp_fileblk),y ; into the heap allocation,
8CE1 INY ; next byte,
8CE2 DEX ; count down
8CE3 BNE unstack_str_copy ; loop
8CE5 .unstack_str_setlen←1← 8CCE BEQ
LDA (zp_stack_ptr,x) ; String length
8CE7 LDY #3 ; descriptor offset 3
8CE9 .unstack_str_drop←1← 8D01 BNE
STA (zp_general),y ; Store the length
8CEB JMP drop_stacked_string ; drop the value from the stack
8CEE .unstack_addr←1← 8CC5 BEQ
LDY #0 ; $addr: length on the stack
8CF0 LDA (zp_stack_ptr),y ; read the length,
8CF2 TAX ; into X as the count
8CF3 BEQ unstack_addr_cr ; empty: just the terminator
8CF5 .unstack_addr_copy←1← 8CFD BNE
INY ; Copy the string to the address
8CF6 LDA (zp_stack_ptr),y ; read a char from the stack,
8CF8 DEY ; back to the destination offset,
8CF9 STA (zp_general),y ; store it at the address,
8CFB INY ; advance,
8CFC DEX ; count down
8CFD BNE unstack_addr_copy ; loop
8CFF .unstack_addr_cr←1← 8CF3 BEQ
LDA #&0d ; CR terminator
8D01 BNE unstack_str_drop ; store it
8D03 .unstack_numeric←1← 8CC7 BCC
LDY #0 ; Numeric: copy byte 0
8D05 LDA (zp_stack_ptr),y ; read it,
8D07 STA (zp_general),y ; store it,
8D09 INY ; next byte
8D0A CPY zp_fileblk ; all bytes done?
8D0C BCS unstack_numeric_drop ; yes
8D0E LDA (zp_stack_ptr),y ; Copy byte 1
8D10 STA (zp_general),y ; store it
8D12 INY ; byte 2
8D13 LDA (zp_stack_ptr),y ; read it,
8D15 STA (zp_general),y ; store it,
8D17 INY ; byte 3
8D18 LDA (zp_stack_ptr),y ; read it,
8D1A STA (zp_general),y ; store it,
8D1C INY ; next byte
8D1D CPY zp_fileblk ; all bytes done?
8D1F BCS unstack_numeric_drop ; yes
8D21 LDA (zp_stack_ptr),y ; byte 4 (real only)
8D23 STA (zp_general),y ; store it,
8D25 INY ; next byte
8D26 .unstack_numeric_drop←2← 8D0C BCS← 8D1F BCS
TYA ; Bytes copied
8D27 CLC ; carry clear for the stack drop
8D28 JMP drop_string_adv ; drop them from the stack
8D2B .print_file←1← 8D9F BEQ
DEC zp_text_ptr_off ; Back up over "#"
8D2D JSR sync_ptrb_from_ptra ; Get the file handle
8D30 .print_file_loop←4← 8D55 BMI← 8D62 BMI← 8D6A BEQ← 8D75 BEQ
TYA ; Save the handle
8D31 PHA ; (push it)
8D32 JSR skip_spaces_ptr2 ; Skip spaces
8D35 CMP #',' ; ',' another value?
8D37 BNE print_file_done ; no: done
8D39 JSR eval_or_eor ; Evaluate the value
8D3C JSR fwa_pack_temp1 ; pack it (in case real)
8D3F PLA ; Recover the handle
8D40 TAY ; into Y for OSBPUT
8D41 LDA zp_var_type ; Type byte
8D43 JSR osbput ; write it
8D46 TAX ; examine the type byte
8D47 BEQ print_file_str ; string?
8D49 BMI print_file_real ; real?
8D4B LDX #3 ; Integer: 4 bytes
8D4D .print_file_int_loop←1← 8D53 BPL
LDA zp_iwa,x ; Write a byte
8D4F JSR osbput ; send it,
8D52 DEX ; next byte (MSB first)
8D53 BPL print_file_int_loop ; loop
8D55 BMI print_file_loop ; next value
8D57 .print_file_real←1← 8D49 BMI
LDX #4 ; Real: 5 bytes
8D59 .print_file_real_loop←1← 8D60 BPL
LDA fp_temp1,x ; Write a byte
8D5C JSR osbput ; send it,
8D5F DEX ; next byte (MSB first)
8D60 BPL print_file_real_loop ; loop
8D62 BMI print_file_loop ; next value
8D64 .print_file_str←1← 8D47 BEQ
LDA zp_strbuf_len ; String: write the length
8D66 JSR osbput ; send it,
8D69 TAX ; empty string?
8D6A BEQ print_file_loop ; empty
8D6C .print_file_str_loop←1← 8D73 BNE
LDA strbuf_base,x ; Write a character
8D6F JSR osbput ; send it,
8D72 DEX ; next character (written in reverse)
8D73 BNE print_file_str_loop ; loop
8D75 BEQ print_file_loop ; next value
8D77 .print_file_done←1← 8D37 BNE
PLA ; Recover the handle
8D78 STY zp_text_ptr_off ; sync the pointer
8D7A JMP stmt_check_end ; next statement
8D7D .print_newline←3← 8DC8 BEQ← 8DCC BEQ← 8DD0 BEQ
JSR emit_newline ; Print a newline
8D80 .print_done←3← 8D8E BEQ← 8D92 BEQ← 8D96 BEQ
JMP stmt_backup_end ; next statement
8D83 .print_semicolon←1← 8DDC BEQ
LDA #0 ; Semicolon: clear the field width...
8D85 STA zp_print_bytes ; the field width,
8D87 STA zp_print_flag ; ...and flags
8D89 JSR skip_spaces ; Next non-space character
8D8C CMP #':' ; ':' end?
8D8E BEQ print_done ; yes: end without a newline
8D90 CMP #&0d ; end of line?
8D92 BEQ print_done ; yes
8D94 CMP #&8b ; ELSE?
8D96 BEQ print_done ; yes
8D98 BNE print_check_sep ; otherwise continue the print loop
fall through ↓

PRINT

Print expressions to the screen, or a file with #, with formatting controlled by @% and separators. PRINT [~][items][;][,].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8D9A .stmt_print
JSR skip_spaces ; Next non-space character
8D9D CMP #'#' ; A leading # directs output to a file (PRINT#)
8D9F BEQ print_file ; '#': PRINT# to a file
8DA1 DEC zp_text_ptr_off ; Back up over the character
8DA3 JMP print_item ; enter the print loop
8DA6 .print_comma←1← 8DD8 BEQ
LDA resint_at ; Comma: pad with spaces to the next @% field
8DA9 BEQ print_item ; zero: no padding
8DAB LDA zp_count ; Current column (COUNT)
8DAD .print_field_loop←1← 8DB2 BCS
BEQ print_item ; at column 0: no padding
8DAF SBC resint_at ; reduce by the field width...
8DB2 BCS print_field_loop ; ...until COUNT mod width
8DB4 TAY ; spaces needed to the next field
8DB5 .print_pad_loop←1← 8DB9 BNE
JSR print_space ; print a space
8DB8 INY ; count up to zero
8DB9 BNE print_pad_loop ; loop
8DBB .print_item←3← 8DA3 JMP← 8DA9 BEQ← 8DAD BEQ
CLC ; Prepare for decimal
8DBC LDA resint_at ; Take the field width from @% (&0400)
8DBF STA zp_print_bytes ; as the print field width
8DC1 .print_set_hex←1← 8DD4 BEQ
ROR zp_print_flag ; Set the hex/dec flag (~ selects hex)
8DC3 .print_next←3← 8DE1 BCC← 8E10 BEQ← 8E1F BEQ
JSR skip_spaces ; Next non-space character
8DC6 CMP #':' ; ':' end of statement?
8DC8 BEQ print_newline ; yes
8DCA CMP #&0d ; end of line?
8DCC BEQ print_newline ; yes
8DCE CMP #&8b ; ELSE?
8DD0 BEQ print_newline ; yes
8DD2 .print_check_sep←1← 8D98 BNE
CMP #'~' ; '~' hex mode?
8DD4 BEQ print_set_hex ; yes: set the flag
8DD6 CMP #',' ; Comma: advance to the next print field
8DD8 BEQ print_comma ; yes
8DDA CMP #';' ; Semicolon: print the next item with no gap
8DDC BEQ print_semicolon ; yes
8DDE JSR print_special_item ; Handle the ' TAB and SPC print items
8DE1 BCC print_next ; handled: next item
8DE3 LDA zp_print_bytes ; Save the field width...
8DE5 PHA ; push it
8DE6 LDA zp_print_flag ; ...and flags (the evaluator may PRINT)
8DE8 PHA ; push it
8DE9 DEC zp_text_ptr2_off ; Back up to the item
8DEB JSR eval_or_eor ; Evaluate the expression to print
8DEE PLA ; Restore the flags...
8DEF STA zp_print_flag ; store them
8DF1 PLA ; ...and the field width
8DF2 STA zp_print_bytes ; store it
8DF4 LDA zp_text_ptr2_off ; Update the program pointer
8DF6 STA zp_text_ptr_off ; from PtrB offset (&1B)
8DF8 TYA ; String item?
8DF9 BEQ print_string ; A string value: print it directly
8DFB JSR number_to_ascii ; A number: convert to an ASCII string
8DFE LDA zp_print_bytes ; and right-justify it within the field width
8E00 SEC ; minus the string length
8E01 SBC zp_strbuf_len ; width - string length = pad count
8E03 BCC print_string ; longer than the field: print as is
8E05 BEQ print_string ; equal: print as is
8E07 TAY ; spaces to pad with
8E08 .print_num_pad←1← 8E0C BNE
JSR print_space ; print a leading space
8E0B DEY ; one fewer pad space
8E0C BNE print_num_pad ; loop
8E0E .print_string←3← 8DF9 BEQ← 8E03 BCC← 8E05 BEQ
LDA zp_strbuf_len ; String length
8E10 BEQ print_next ; empty: next item
8E12 LDY #0 ; From the start
8E14 .print_string_loop←1← 8E1D BNE
LDA string_work,y ; String character
8E17 JSR print_char ; print it
8E1A INY ; next
8E1B CPY zp_strbuf_len ; printed all characters?
8E1D BNE print_string_loop ; loop
8E1F BEQ print_next ; next item
8E21 .print_tab_error←1← 8E26 BNE
JMP missing_comma ; Missing , error
8E24 .print_tab_xy←1← 8E48 BNE
CMP #',' ; ',' TAB(x,y) form?
8E26 BNE print_tab_error ; no: TAB(x) or SPC
8E28 LDA zp_iwa ; Save the x coordinate
8E2A PHA ; push it
8E2B JSR eval_subexpr ; Evaluate y, expect )
8E2E JSR coerce_to_integer ; coerce to integer
8E31 LDA #&1f ; VDU 31 (move cursor)
8E33 JSR oswrch ; send the VDU 31 control code
8E36 PLA ; x coordinate
8E37 JSR oswrch ; send the x coordinate
8E3A JSR vdu_send_byte ; y coordinate
8E3D JMP print_sync ; next item
8E40 .print_tab_x←1← 8E82 BEQ
JSR eval_expr_integer ; TAB(x): evaluate x
8E43 JSR skip_spaces_ptr2 ; skip spaces
8E46 CMP #')' ; ')'?
8E48 BNE print_tab_xy ; no: TAB(x,y)
8E4A LDA zp_iwa ; Spaces = x - COUNT
8E4C SBC zp_count ; subtract the current column
8E4E BEQ print_sync ; already at column x: nothing to do
8E50 TAY ; count
8E51 BCS print_spc_loop ; past column x: skip
8E53 JSR emit_newline ; Newline to reach a fresh line
8E56 BEQ print_spc_count ; fresh line: pad to column x
8E58 .print_spc←1← 8E86 BEQ
JSR eval_factor_integer ; SPC(n): evaluate the count
8E5B .print_spc_count←1← 8E56 BEQ
LDY zp_iwa ; spaces = x
8E5D BEQ print_sync ; none
8E5F .print_spc_loop←2← 8E51 BCS← 8E63 BNE
JSR print_space ; Print a space
8E62 DEY ; one fewer space
8E63 BNE print_spc_loop ; loop
8E65 BEQ print_sync ; next item
8E67 .print_spc_newline←1← 8E7E BEQ
JSR emit_newline ; SPC: print a newline
8E6A .print_sync←5← 8E3D JMP← 8E4E BEQ← 8E5D BEQ← 8E65 BEQ← 8EB9 BNE
CLC ; Sync the program pointer
8E6B LDY zp_text_ptr2_off ; load PtrB offset (&1B),
8E6D STY zp_text_ptr_off ; store to PtrA offset (&0A)
8E6F RTS ; Return

Handle the PRINT ' TAB and SPC items

Recognise and act on the special PRINT items: apostrophe (force a newline), TAB(x[,y]) and SPC(n). Returns carry clear when it consumed one, so the caller skips ordinary expression printing.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset at the PRINT item
On ExitCclear if a special item was consumed and printed, else set
ZP_TEXT_PTR_OFF (&0A)advanced past a consumed item
8E70 .print_special_item←2← 8DDE JSR← 8E8D JSR
LDX zp_text_ptr ; Save PtrA into PtrB: low
8E72 STX zp_text_ptr2 ; store it
8E74 LDX zp_text_ptr_1 ; high
8E76 STX zp_text_ptr2_1 ; store it
8E78 LDX zp_text_ptr_off ; offset
8E7A STX zp_text_ptr2_off ; store it
8E7C CMP #ASC("'") ; an apostrophe?
8E7E BEQ print_spc_newline ; yes: force a newline
8E80 CMP #&8a ; TAB token?
8E82 BEQ print_tab_x ; yes: handle TAB
8E84 CMP #&89 ; SPC token?
8E86 BEQ print_spc ; yes: handle SPC
8E88 SEC ; none: not consumed (carry set)
8E89 .return_6←1← 8E90 BCC
RTS ; Return

Skip spaces then handle a PRINT special item

Skip spaces at PtrA, then dispatch the next PRINT item to print_special_item (which handles ~, TAB, SPC, ' and so on). If that consumed the item, return with carry clear; if the next character is a string literal (a double-quote), print it inline via print_inline_loop; otherwise return with carry set, leaving the character in A.

On EntryZP_TEXT_PTR (&0B)PtrA, at the item being scanned
ZP_TEXT_PTR_OFF (&0A)offset within the line
On ExitCclear if the item was consumed or printed, set if not recognised
Athe unconsumed character when carry is set
8E8A .print_special_skip←2← BA5A JSR← BA5F JSR
JSR skip_spaces ; Skip spaces, then handle a special item
8E8D JSR print_special_item ; handle it
8E90 BCC return_6 ; consumed: done
8E92 CMP #'"' ; a string literal (quote)?
8E94 BEQ print_inline_loop ; yes: print it inline
8E96 SEC ; not consumed
8E97 RTS ; Return
8E98 .missing_quote←2← 8EAC BEQ← ADE9 JMP
BRK ; Missing " error block
8E99 EQUB &09
8E9A EQUS "Missing ", &22
8EA3 EQUB &00
8EA4 .print_inline_char←2← 8EB0 BNE← 8EBB BEQ
JSR print_char ; print a character
8EA7 .print_inline_loop←1← 8E94 BEQ
INY ; Print the inline string: advance
8EA8 LDA (zp_text_ptr2),y ; char
8EAA CMP #&0d ; CR (unterminated)?
8EAC BEQ missing_quote ; Missing " error
8EAE CMP #'"' ; a quote?
8EB0 BNE print_inline_char ; no: print it
8EB2 INY ; advance
8EB3 STY zp_text_ptr2_off ; update the offset
8EB5 LDA (zp_text_ptr2),y ; doubled ""?
8EB7 CMP #'"' ; is it another quote?
8EB9 BNE print_sync ; no: end of the string
8EBB BEQ print_inline_char ; yes: print one quote
fall through ↓

CLG

Clear the graphics window to the graphics background colour. CLG.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8EBD .stmt_clg
JSR check_end_of_statement ; Check the statement ends
8EC0 LDA #&10 ; VDU 16 (clear graphics)
8EC2 BNE cls_send ; send it
fall through ↓

CLS

Clear the text window to the text background colour. CLS.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8EC4 .stmt_cls
JSR check_end_of_statement ; Check the statement ends
8EC7 JSR reset_print_column ; Reset the print column
8ECA LDA #&0c ; VDU 12 (clear screen)
8ECC .cls_send←1← 8EC2 BNE
JSR oswrch ; send it
8ECF JMP statement_loop ; next statement

CALL

Call machine code, passing the resident integer variables and an optional parameter block. CALL address [,params...].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8ED2 .stmt_call
JSR eval_expr ; Evaluate the call address
8ED5 JSR coerce_var_to_integer ; coerce to an integer
8ED8 JSR stack_integer ; stack the address
8EDB LDY #0 ; Build the parameter block at &0600:
8EDD STY string_work ; zero the parameter count
8EE0 .call_block_init←1← 8F09 JMP
STY call_block_base ; reset the block write offset
8EE3 JSR skip_spaces_ptr2 ; next parameter?
8EE6 CMP #',' ; a comma?
8EE8 BNE call_check_end ; no: end of the parameters
8EEA LDY zp_text_ptr2_off ; parse the parameter (a variable)
8EEC JSR pvr_record_offset ; resolve it to an address
8EEF BEQ call_param_error ; bad parameter: error
8EF1 LDY call_block_base ; append its address to the block:
8EF4 INY ; advance to the next slot
8EF5 LDA zp_iwa ; address low
8EF7 STA string_work,y ; store it
8EFA INY ; next slot
8EFB LDA zp_iwa_1 ; address high
8EFD STA string_work,y ; store it
8F00 INY ; next slot
8F01 LDA zp_iwa_2 ; type
8F03 STA string_work,y ; store it
8F06 INC string_work ; one more parameter
8F09 JMP call_block_init ; next parameter
8F0C .call_check_end←1← 8EE8 BNE
DEC zp_text_ptr2_off ; Check for end of statement
8F0E JSR sync_text_ptr ; error if more follows
8F11 JSR unstack_integer ; pop the address into IWA
8F14 JSR usr_call ; set up registers and call the code
8F17 CLD ; clear decimal mode on return
8F18 JMP statement_loop ; Back to execution
8F1B .call_param_error←1← 8EEF BEQ
JMP no_such_variable ; parameter error (shared)

Call user machine code (USR/CALL)

Load A, X, Y and the carry from the resident integer variables A%, X%, Y% and C%, then JMP (IWA) to the user routine. Its RTS returns to usr_call's caller, which captures the registers back.

On EntryZP_IWA (&2A/&2B)the address to call
RESINT_A/X/Y/C (A%, X%, Y%, C%)the register values
On ExitAas left by the called code
Xas left by the called code
Yas left by the called code
Pas left by the called code
8F1E .usr_call←2← 8F14 JSR← ABD5 JSR
LDA resint_c ; C% supplies the carry...
8F21 LSR ; ...shifted into the carry flag
8F22 LDA resint_a ; A from A%
8F25 LDX resint_x ; X from X%
8F28 LDY resint_y ; Y from Y%
8F2B JMP (zp_iwa) ; Call the routine at the address in IWA
8F2E .call_arg_error←3← 8F34 BCC← 8F3E BNE← 8F43 BCC
JMP syntax_error ; error (shared)

DELETE

Delete a range of program lines. DELETE start, end.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8F31 .stmt_delete
JSR check_line_number ; Read the start line number
8F34 BCC call_arg_error ; none: Syntax error
8F36 JSR stack_integer ; stack it
8F39 JSR skip_spaces ; Skip spaces
8F3C CMP #',' ; ','?
8F3E BNE call_arg_error ; no: error
8F40 JSR check_line_number ; Read the end line number
8F43 BCC call_arg_error ; none: error
8F45 JSR check_end_of_statement ; check the statement ends
8F48 LDA zp_iwa ; Save the end line
8F4A STA zp_fileblk ; low to &39,
8F4C LDA zp_iwa_1 ; high byte,
8F4E STA zp_fileblk_1 ; to &3A
8F50 JSR unstack_integer ; Recover the start line
8F53 .delete_loop←1← 8F64 BCS
JSR delete_program_line ; Delete the line
8F56 JSR check_escape ; Find the next line number
8F59 JSR iwa_inc ; Advance the line counter
8F5C LDA zp_fileblk ; past the end line?
8F5E CMP zp_iwa ; end low - line low,
8F60 LDA zp_fileblk_1 ; end high,
8F62 SBC zp_iwa_1 ; - line high
8F64 BCS delete_loop ; no: delete the next
8F66 JMP clear_then_immediate ; done: immediate mode

Parse an optional start,step line-number range

Parse the optional [start[,step]] arguments shared by AUTO and RENUMBER: default the start line to 10, read an explicit start if present and push it on the BASIC stack; then default the step to 10 and, if a comma follows, read an explicit step, raising "Silly" if the step is zero. Tail-calls check_end_of_statement to verify nothing else follows.

On EntryZP_TEXT_PTR (&0B/&0C)PtrA, at the text following the keyword
ZP_TEXT_PTR_OFF (&0A)offset within the line
On ExitZP_IWA (&2A)the step increment (default 10), &2A-&2D
CONTROLthe start line number is left pushed on the BASIC stack; returns via check_end_of_statement
8F69 .parse_line_range←2← 8FA3 JSR← 90AC JSR
LDA #&0a ; Default start = 10
8F6B JSR int_result_a ; IWA = 10
8F6E JSR check_line_number ; Read the start line if given
8F71 JSR stack_integer ; stack it
8F74 LDA #&0a ; Default increment = 10
8F76 JSR int_result_a ; IWA = 10
8F79 JSR skip_spaces ; Skip spaces
8F7C CMP #',' ; ',' increment given?
8F7E BNE range_backup ; no: use the default
8F80 JSR check_line_number ; Read the increment
8F83 LDA zp_iwa_1 ; zero?
8F85 BNE silly_error ; no
8F87 LDA zp_iwa ; low byte zero too?
8F89 BEQ silly_error ; zero increment: error
8F8B INC zp_text_ptr_off ; step past
8F8D .range_backup←1← 8F7E BNE
DEC zp_text_ptr_off ; back up
8F8F JMP check_end_of_statement ; check the statement ends

Point the RENUMBER scan pointers at TOP and PAGE

Copy TOP (&12/&13) into the table pointer at &3B/&3C, then fall into point_general_page to set the program-scan pointer at &37/&38 to PAGE (low byte forced to 1, the first line-number byte). Used by RENUMBER to initialise the old-number table (built from the heap at TOP) and the pointer that walks the program from PAGE.

On EntryZP_TOP (&12/&13)TOP, the top of the program / base of the heap
ZP_PAGE (&18)PAGE, the start of program text
On ExitZP_FWB_SIGN (&3B/&3C)table pointer set to TOP
ZP_GENERAL (&37/&38)scan pointer set to PAGE, low byte 1 (first line-number byte)
Acorrupted
8F92 .setup_scan_top←2← 8FAE JSR← 9040 JSR
LDA zp_top ; Copy TOP to &3B/&3C
8F94 STA zp_fwb_sign ; low to &3B,
8F96 LDA zp_top_1 ; high byte,
8F98 STA zp_fwb_ovf ; to &3C
8F9A .point_general_page←1← 8FE7 JSR
LDA zp_page ; Point &37/&38 at PAGE
8F9C STA zp_general_1 ; page to &38,
8F9E LDA #1 ; offset 1,
8FA0 STA zp_general ; low byte &37
8FA2 RTS ; Return

RENUMBER

Renumber program lines and fix up line references. RENUMBER [start[,step]].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
8FA3 .stmt_renumber
JSR parse_line_range ; Parse the start and step arguments
8FA6 LDX #&39 ; Pop the step into the file block (&39)
8FA8 JSR unstack_int_to_zp ; (pop it)
8FAB JSR check_program ; Pop the start number
8FAE JSR setup_scan_top ; Point at the program start and the table
8FB1 .renum_pass1_loop←1← 8FD4 BCC
LDY #0 ; Pass 1: for each line, read its number
8FB3 LDA (zp_general),y ; high byte
8FB5 BMI renum_pass2 ; high bit set marks the end of program
8FB7 STA (zp_fwb_sign),y ; Store the old number in the table
8FB9 INY ; low byte
8FBA LDA (zp_general),y ; read the line number low
8FBC STA (zp_fwb_sign),y ; (store)
8FBE SEC ; Advance the table pointer by 2: low
8FBF TYA ; offset to A,
8FC0 ADC zp_fwb_sign ; + table pointer (carry adds 2)
8FC2 STA zp_fwb_sign ; (store)
8FC4 TAX ; keep the low byte
8FC5 LDA zp_fwb_ovf ; table pointer high...
8FC7 ADC #0 ; + carry
8FC9 STA zp_fwb_ovf ; (store)
8FCB CPX zp_himem ; Has the table reached HIMEM?
8FCD SBC zp_himem_1 ; high byte vs HIMEM
8FCF BCS renum_no_space ; yes: no room for the table
8FD1 JSR advance_to_next_line ; Advance to the next program line
8FD4 BCC renum_pass1_loop ; loop over all lines
8FD6 .renum_no_space←1← 8FCF BCS
BRK ; RENUMBER ran out of space: error
8FD7 EQUB &00, &CC
8FD9 EQUS " space"
8FDF .silly_error←2← 8F85 BNE← 8F89 BEQ
BRK ; error block
8FE0 EQUB &00
8FE1 EQUS "Silly"
8FE6 EQUB &00
8FE7 .renum_pass2←1← 8FB5 BMI
JSR point_general_page ; Pass 2: reset to the program start
8FEA .renum_pass2_loop←1← 900B BCC
LDY #0 ; for each line:
8FEC LDA (zp_general),y ; line number high byte
8FEE BMI renum_pass3 ; end of program: go to pass 3
8FF0 LDA zp_fileblk_1 ; Write the new number: high byte
8FF2 STA (zp_general),y ; (into the line)
8FF4 LDA zp_fileblk ; low byte
8FF6 INY ; advance
8FF7 STA (zp_general),y ; (into the line)
8FF9 CLC ; Add the step to the running number: low
8FFA LDA zp_iwa ; step low,
8FFC ADC zp_fileblk ; + current number low
8FFE STA zp_fileblk ; (store)
9000 LDA #0 ; high byte...
9002 ADC zp_fileblk_1 ; + carry
9004 AND #&7f ; keep it a valid line number (< &8000)
9006 STA zp_fileblk_1 ; (store)
9008 JSR advance_to_next_line ; Advance to the next line
900B BCC renum_pass2_loop ; loop
900D .renum_pass3←1← 8FEE BMI
LDA zp_page ; Pass 3: scan from PAGE, high byte
900F STA zp_text_ptr_1 ; (text pointer high)
9011 LDY #0 ; low byte 0
9013 STY zp_text_ptr ; (store)
9015 INY ; advance
9016 LDA (zp_text_ptr),y ; read the line number high byte
9018 BMI renum_done ; end of program: done
901A .renum_line_loop←2← 9034 BCC← 9038 BCS
LDY #4 ; Scan the line from offset 4 (past the header)
901C .renum_scan_loop←2← 9025 BNE← 906F BNE
LDA (zp_text_ptr),y ; next token
901E CMP #&8d ; Is it the line-number prefix &8D?
9020 BEQ renum_ref ; yes: rewrite the reference
9022 INY ; advance
9023 CMP #&0d ; end of line (CR)?
9025 BNE renum_scan_loop ; no: keep scanning
9027 LDA (zp_text_ptr),y ; read the next line's header
9029 BMI renum_done ; end of program
902B LDY #3 ; line length is at offset 3
902D LDA (zp_text_ptr),y ; get it
902F CLC ; advance to the next line: low
9030 ADC zp_text_ptr ; + text pointer low
9032 STA zp_text_ptr ; (store)
9034 BCC renum_line_loop ; loop
9036 INC zp_text_ptr_1 ; carry into high byte
9038 BCS renum_line_loop ; loop
903A .renum_done←2← 9018 BMI← 9029 BMI
JMP clear_then_immediate ; Done: back to the immediate loop
903D .renum_ref←1← 9020 BEQ
JSR decode_line_number ; Decode the &8D-encoded line number
9040 JSR setup_scan_top ; Point at the old->new table
9043 .renum_table_search←2← 907A BCC← 907E BCS
LDY #0 ; Search the table for this old number:
9045 LDA (zp_general),y ; table end marker?
9047 BMI renum_missing_line ; not found: reference to a missing line
9049 LDA (zp_fwb_sign),y ; old number high...
904B INY ; advance
904C CMP zp_iwa_1 ; match the referenced number high?
904E BNE renum_table_next ; no: next table entry
9050 LDA (zp_fwb_sign),y ; old number low...
9052 CMP zp_iwa ; match low?
9054 BNE renum_table_next ; no: next entry
9056 LDA (zp_general),y ; Found: take the new number high
9058 STA zp_fwb_exp ; (save)
905A DEY ; new number low
905B LDA (zp_general),y ; read it from the table
905D STA zp_fwb_m1 ; (save)
905F LDY zp_text_ptr_off ; position within the line
9061 DEY ; back up to the &8D token
9062 LDA zp_text_ptr ; Point at the reference in the line: low
9064 STA zp_general ; reference pointer low
9066 LDA zp_text_ptr_1 ; high
9068 STA zp_general_1 ; reference pointer high
906A JSR encode_line_number ; Re-encode the new line number in place
906D .renum_resume←1← 909D BEQ
LDY zp_text_ptr_off ; resume scanning
906F BNE renum_scan_loop ; continue the line scan
9071 .renum_table_next←2← 904E BNE← 9054 BNE
JSR advance_to_next_line ; Next table entry: advance...
9074 LDA zp_fwb_sign ; ...the table pointer by 2
9076 ADC #2 ; low + 2
9078 STA zp_fwb_sign ; (store)
907A BCC renum_table_search ; loop
907C INC zp_fwb_ovf ; carry
907E BCS renum_table_search ; loop
9080 .renum_missing_line←1← 9047 BMI
JSR print_inline_string ; Reference to a missing line: report it
9083 EQUS "Failed at " ; build the "Failed at <line>" message... print it
908D INY ; This line's number
908E LDA (zp_text_ptr),y ; high byte,
9090 STA zp_iwa_1 ; store high,
9092 INY ; low byte,
9093 LDA (zp_text_ptr),y ; read it,
9095 STA zp_iwa ; store low
9097 JSR print_line_number ; print it
909A JSR emit_newline ; newline
909D BEQ renum_resume ; loop
fall through ↓

Advance the general pointer to the next program line

Add the line length byte (at offset 3 of the current line) to zp_general so it addresses the next program line. Clears carry for the caller.

On EntryZP_GENERAL (&37/&38)a pointer to the current line
Y2 (offset just before the length byte)
Cclear (it is the carry-in to the add)
On ExitZP_GENERALaddresses the next line
Cclear
Xpreserved
909F .advance_to_next_line←3← 8FD1 JSR← 9008 JSR← 9071 JSR
INY ; Line length is at offset 3
90A0 LDA (zp_general),y ; get the line length
90A2 ADC zp_general ; add it to the pointer: low
90A4 STA zp_general ; (store)
90A6 BCC return_7 ; no carry: done
90A8 INC zp_general_1 ; carry into the high byte
90AA CLC ; clear carry for the caller
90AB .return_7←1← 90A6 BCC
RTS ; Return at the next line

AUTO

Generate line numbers automatically during program entry until Escape. AUTO [start[,step]].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
90AC .stmt_auto
JSR parse_line_range ; Parse the start and increment
90AF LDA zp_iwa ; Save the increment
90B1 PHA ; push it
90B2 JSR unstack_integer ; Recover the start line
90B5 .auto_loop←2← 90D3 BCC← 90D7 BPL
JSR stack_integer ; Stack the line number
90B8 JSR trace_print_number ; Print it
90BB LDA #' ' ; Print a space and read a line
90BD JSR read_input_line ; do it
90C0 JSR unstack_integer ; Pop the line number
90C3 JSR tokenise_line ; Tokenise the line
90C6 JSR insert_line ; Insert it into the program
90C9 JSR clear_vars_heap_stack ; Clear variables and heap
90CC PLA ; Increment
90CD PHA ; keep it stacked
90CE CLC ; Next line number = line + increment
90CF ADC zp_iwa ; + current line low,
90D1 STA zp_iwa ; store low,
90D3 BCC auto_loop ; no carry: loop,
90D5 INC zp_iwa_1 ; carry into the high byte
90D7 BPL auto_loop ; still in range: loop
90D9 JMP clear_then_immediate ; overflow: stop
90DC .dim_no_room_jmp←1← 9106 BCS
JMP dim_no_room ; No room error
90DF .dim_byte_block←1← 9168 JMP
DEC zp_text_ptr_off ; Back up to the variable
90E1 JSR parse_lvalue ; Parse it
90E4 BEQ bad_dim ; not a variable: Bad DIM
90E6 BCS bad_dim ; indirection: Bad DIM
90E8 JSR stack_integer ; Stack the variable address
90EB JSR eval_expr_integer ; Evaluate the size
90EE JSR iwa_inc ; n + 1 bytes
90F1 LDA zp_iwa_3 ; fits in 16 bits?
90F3 ORA zp_iwa_2 ; OR byte 2 (any => > 65535)
90F5 BNE bad_dim ; no: Bad DIM
90F7 CLC ; New top = top + size
90F8 LDA zp_iwa ; size low,
90FA ADC zp_vartop ; + VARTOP low,
90FC TAY ; new top low in Y,
90FD LDA zp_iwa_1 ; size high,
90FF ADC zp_vartop_1 ; + VARTOP high,
9101 TAX ; new top high in X
9102 CPY zp_stack_ptr ; collides with the stack?
9104 SBC zp_stack_ptr_1 ; high byte vs the stack
9106 BCS dim_no_room_jmp ; yes: No room
9108 LDA zp_vartop ; Block address = old top
910A STA zp_iwa ; address low,
910C LDA zp_vartop_1 ; high byte,
910E STA zp_iwa_1 ; address high
9110 STY zp_vartop ; Commit the new top
9112 STX zp_vartop_1 ; high byte
9114 LDA #0 ; Result is the address
9116 STA zp_iwa_2 ; clear byte 2,
9118 STA zp_iwa_3 ; and byte 3
911A LDA #&40 ; integer type
911C STA zp_var_type ; set the type
911E JSR assign_number ; assign the address to the variable
9121 JSR eeti_sync ; sync the pointer
9124 JMP dim_after ; continue

Raise the "Bad DIM" error

Raise BASIC error &0A, "Bad DIM", via a BRK error block (the message ends with the DIM token &DE). Reached by the DIM/array machinery on an empty name, a re-DIM of an existing array, a bound exceeding 16383, or another invalid dimension. Does not return.

On ExitCONTROLraises BRK error &0A "Bad DIM"; does not return to the caller
9127 .bad_dim←8← 90E4 BEQ← 90E6 BCS← 90F5 BNE← 9150 BEQ← 9172 BNE← 9193 BNE← 91B4 JMP← 925A JMP
BRK ; Bad DIM error
9128 EQUB &0A
9129 EQUS "Bad "
912D EQUB &DE, &00

DIM

Dimension an array, or reserve a block of bytes. DIM var(subscripts) | DIM var size.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
912F .stmt_dim←1← 9215 JMP
JSR skip_spaces ; Skip spaces
9132 TYA ; Point &37/&38 at the name
9133 CLC ; Clear carry for the add
9134 ADC zp_text_ptr ; Name offset + text pointer low
9136 LDX zp_text_ptr_1 ; Text pointer high in X
9138 BCC dim_name ; No carry into the high byte
913A INX ; Carry into the high byte
913B CLC ; Clear carry to back up by one
913C .dim_name←1← 9138 BCC
SBC #0 ; Back up one byte (validate reads from offset 1): low
913E STA zp_general ; &37 = name pointer low
9140 TXA ; High byte
9141 SBC #0 ; Back up one byte: high
9143 STA zp_general_1 ; &38 = name pointer high
9145 LDX #5 ; Default element size = 5 (real)
9147 STX zp_fwb_m2 ; Stash it as the element size (zp_fwb_m2)
9149 LDX zp_text_ptr_off ; Name offset
914B JSR validate_var_name ; Validate the name
914E CPY #1 ; empty name?
9150 BEQ bad_dim ; yes: Bad DIM
9152 CMP #'(' ; '(' array?
9154 BEQ dim_array ; yes
9156 CMP #'$' ; '$' string?
9158 BEQ dim_check_paren ; yes
915A CMP #'%' ; '%' integer?
915C BNE dim_byte ; no: DIM var n (byte block)
915E .dim_check_paren←1← 9158 BEQ
DEC zp_fwb_m2 ; Element size = 4 (integer/string)
9160 INY ; step past the suffix
9161 INX ; Count the suffix char in the name length too
9162 LDA (zp_general),y ; following character
9164 CMP #'(' ; '(' array?
9166 BEQ dim_array ; yes
9168 .dim_byte←1← 915C BNE
JMP dim_byte_block ; DIM var n: allocate a byte block
916B .dim_array←2← 9154 BEQ← 9166 BEQ
STY zp_fileblk ; Array: save the name length
916D STX zp_text_ptr_off ; Save the name offset too (&0a)
916F JSR find_variable ; Already defined?
9172 BNE bad_dim ; yes: Bad DIM (re-DIM)
9174 JSR create_variable ; Create the array variable
9177 LDX #1 ; Clear its pointer
9179 JSR clear_value_bytes ; Zero the value byte
917C LDA zp_fwb_m2 ; Save the element size
917E PHA ; Push it for the post-loop multiply
917F LDA #1 ; Descriptor write offset starts at 1
9181 PHA ; Push the descriptor write offset
9182 JSR int_result_a ; running size = 1
9185 .dim_bound_loop←1← 91AE BEQ
JSR stack_integer ; Stack the running size
9188 JSR eval_expr_to_integer ; Evaluate the next bound
918B LDA zp_iwa_1 ; fits in 14 bits?
918D AND #&c0 ; Isolate the top 2 bits of the bound high byte
918F ORA zp_iwa_2 ; OR in byte 2,
9191 ORA zp_iwa_3 ; and byte 3 (any set => bound > 16383)
9193 BNE bad_dim ; no: Bad DIM
9195 JSR iwa_inc ; Extent = bound + 1
9198 PLA ; Restore the descriptor offset
9199 TAY ; into Y as the descriptor write index
919A LDA zp_iwa ; Store the extent in the descriptor
919C STA (zp_vartop),y ; extent low byte,
919E INY ; advance to the high byte,
919F LDA zp_iwa_1 ; extent high byte,
91A1 STA (zp_vartop),y ; store it,
91A3 INY ; advance past the extent
91A4 TYA ; save the offset
91A5 PHA ; Push the updated descriptor offset
91A6 JSR unstack_multiplier ; Multiply the running size by the extent
91A9 JSR skip_spaces ; Skip spaces
91AC CMP #',' ; ',' another dimension?
91AE BEQ dim_bound_loop ; yes
91B0 CMP #')' ; ')' end of dimensions?
91B2 BEQ dim_alloc ; yes
91B4 JMP bad_dim ; otherwise Bad DIM
91B7 .dim_alloc←1← 91B2 BEQ
PLA ; Recover the data offset (1 + 2*dims)
91B8 STA zp_print_flag ; save it
91BA PLA ; Recover the element size
91BB STA zp_fwb_m2 ; as the multiplier low byte
91BD LDA #0 ; element size high = 0
91BF STA zp_fwb_m3 ; (16-bit element size in zp_fwb_m2/m3)
91C1 JSR imul16 ; Total = element count * element size
91C4 LDY #0 ; Store the data offset in descriptor byte 0
91C6 LDA zp_print_flag ; the saved offset (1 + 2*dims)
91C8 STA (zp_vartop),y ; into descriptor byte 0
91CA ADC zp_iwa ; Add it to the total size
91CC STA zp_iwa ; total size low = header + data,
91CE BCC dim_clear ; no carry into the high byte
91D0 INC zp_iwa_1 ; carry into the total size high byte
91D2 .dim_clear←1← 91CE BCC
LDA zp_vartop_1 ; Point at the current variable top
91D4 STA zp_general_1 ; high byte to &38,
91D6 LDA zp_vartop ; low byte
91D8 STA zp_general ; to &37 (saved old top)
91DA CLC ; New top = top + total size
91DB ADC zp_iwa ; + total size low,
91DD TAY ; new top low held in Y,
91DE LDA zp_iwa_1 ; high byte
91E0 ADC zp_vartop_1 ; + total size high
91E2 BCS dim_no_room ; overflow: No room
91E4 TAX ; new top high in X
91E5 CPY zp_stack_ptr ; collides with the stack?
91E7 SBC zp_stack_ptr_1 ; high byte vs stack high (16-bit compare)
91E9 BCS dim_no_room ; yes: No room
91EB STY zp_vartop ; Commit the new variable top
91ED STX zp_vartop_1 ; high byte
91EF LDA zp_general ; Zero the array storage from the old top
91F1 ADC zp_print_flag ; skip the descriptor header to the data,
91F3 TAY ; data start offset in Y,
91F4 LDA #0 ; zero byte to fill with,
91F6 STA zp_general ; pointer low = 0 (Y carries the offset)
91F8 BCC dim_clear_loop ; no page crossing
91FA INC zp_general_1 ; step the pointer to the next page
91FC .dim_clear_loop←3← 91F8 BCC← 9205 BNE← 9209 BNE
STA (zp_general),y ; store a zero byte
91FE INY ; next byte,
91FF BNE dim_clear_check ; no page wrap
9201 INC zp_general_1 ; cross into the next page
9203 .dim_clear_check←1← 91FF BNE
CPY zp_vartop ; reached the new top?
9205 BNE dim_clear_loop ; low byte not at top yet: keep zeroing
9207 CPX zp_general_1 ; compare the page to the new top page
9209 BNE dim_clear_loop ; not there yet: keep zeroing
920B .dim_after←1← 9124 JMP
JSR skip_spaces ; Skip spaces
920E CMP #',' ; ',' another array?
9210 BEQ dim_next_array ; yes
9212 JMP stmt_backup_end ; no: next statement
9215 .dim_next_array←1← 9210 BEQ
JMP stmt_dim ; DIM the next array
9218 .dim_no_room←3← 90DC JMP← 91E2 BCS← 91E9 BCS
BRK ; No room error
9219 EQUB &0B, &DE
921B EQUS " space"
9221 EQUB &00

Increment the integer accumulator

IWA = IWA + 1, carrying through all four bytes.

On EntryZP_IWA (&2A-&2D)the integer to increment
On ExitZP_IWAincremented by one
Xpreserved
Ypreserved
9222 .iwa_inc←4← 8F59 JSR← 90EE JSR← 9195 JSR← AF39 JSR
INC zp_iwa ; Increment IWA: byte 0
9224 BNE return_8 ; No carry: done
9226 INC zp_iwa_1 ; Carry into byte 1
9228 BNE return_8 ; no carry: done
922A INC zp_iwa_2 ; byte 2
922C BNE return_8 ; no carry: done
922E INC zp_iwa_3 ; byte 3
9230 .return_8←3← 9224 BNE← 9228 BNE← 922C BNE
RTS ; Return
9231 .unstack_multiplier←1← 91A6 JSR
LDX #&3f ; Unstack the multiplier (into &3F area)
9233 JSR unstack_int_to_zp ; into the &3F area
fall through ↓

16-bit integer multiply (IWA * FWB)

Multiply IWA by the 16-bit multiplier in the FWB mantissa (m2/m3) by shift-and-add, leaving the 16-bit product in IWA low/high. Raises Too big on overflow. Used for array subscript arithmetic.

On EntryZP_IWA (&2A/&2B)the multiplicand
ZP_FWB_M2 (&3F/&40)the 16-bit multiplier
On ExitZP_IWA (&2A/&2B)the 16-bit product
Xcorrupted
Ycorrupted
BRKToo big on overflow
9236 .imul16←2← 91C1 JSR← 9736 JSR
LDX #0 ; Clear the running product (X:Y)
9238 LDY #0 ; high in X, low in Y
923A .imul_loop←1← 9253 BNE
LSR zp_fwb_m3 ; Shift the multiplier right: next bit into carry
923C ROR zp_fwb_m2 ; low byte (next bit -> carry)
923E BCC imul_double ; bit clear: skip the add
9240 CLC ; bit set: add the multiplicand (IWA)
9241 TYA ; product low...
9242 ADC zp_iwa ; low
9244 TAY ; (into Y)
9245 TXA ; product high...
9246 ADC zp_iwa_1 ; high
9248 TAX ; (into X)
9249 BCS imul_overflow ; overflow: Too big
924B .imul_double←1← 923E BCC
ASL zp_iwa ; Double the multiplicand: low
924D ROL zp_iwa_1 ; high
924F LDA zp_fwb_m2 ; more multiplier bits?
9251 ORA zp_fwb_m3 ; any multiplier bits left?
9253 BNE imul_loop ; loop
9255 STY zp_iwa ; Store the product: low
9257 STX zp_iwa_1 ; high
9259 RTS ; Return
925A .imul_overflow←1← 9249 BCS
JMP bad_dim ; overflow error

HIMEM=

Set HIMEM, the top of memory available to BASIC. HIMEM = address.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
925D .stmt_himem
JSR eval_eq_integer ; Step past "=", evaluate an integer
9260 LDA zp_iwa ; Set HIMEM and the stack: low
9262 STA zp_himem ; HIMEM low
9264 STA zp_stack_ptr ; stack pointer low
9266 LDA zp_iwa_1 ; high
9268 STA zp_himem_1 ; HIMEM high
926A STA zp_stack_ptr_1 ; stack pointer high
926C JMP statement_loop ; Back to execution

LOMEM=

Set LOMEM, the start of variable storage. LOMEM = address.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
926F .stmt_lomem
JSR eval_eq_integer ; Step past "=", evaluate an integer
9272 LDA zp_iwa ; Set LOMEM (and empty the variables): low byte
9274 STA zp_lomem ; LOMEM low
9276 STA zp_vartop ; VARTOP low (no variables yet)
9278 LDA zp_iwa_1 ; high byte
927A STA zp_lomem_1 ; LOMEM high
927C STA zp_vartop_1 ; VARTOP high
927E JSR clear_var_table ; Clear all dynamic variables
9281 BEQ page_done ; Back to the execution loop
fall through ↓

PAGE=

Set PAGE, the start of the BASIC program. PAGE = address.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
9283 .stmt_page
JSR eval_eq_integer ; Step past "=", evaluate an integer
9286 LDA zp_iwa_1 ; PAGE is a page number (the high byte)
9288 STA zp_page ; (store)
928A .page_done←2← 9281 BEQ← 9293 BEQ
JMP statement_loop ; Back to execution

CLEAR

Discard all variables and the stack. CLEAR.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
928D .stmt_clear
JSR check_end_of_statement ; Check the statement ends
9290 JSR clear_vars_heap_stack ; Clear variables, heap and stack
9293 BEQ page_done ; next statement
fall through ↓

TRACE

Trace executed line numbers for debugging. TRACE ON | OFF | line.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
9295 .stmt_trace
JSR check_line_number ; Line number following?
9298 BCS trace_check_end ; yes: TRACE to that line
929A CMP #&ee ; ON token?
929C BEQ trace_on ; yes
929E CMP #&87 ; OFF token?
92A0 BEQ trace_off ; yes
92A2 JSR eval_expr_to_integer ; Evaluate the trace ceiling
92A5 .trace_check_end←1← 9298 BCS
JSR check_end_of_statement ; check the statement ends
92A8 LDA zp_iwa ; Set the trace ceiling
92AA STA zp_trace_max ; low byte,
92AC LDA zp_iwa_1 ; high byte,
92AE .trace_ceiling_loop←1← 92BE BNE
STA zp_trace_max_1 ; to &22
92B0 LDA #&ff ; TRACE on
92B2 .trace_set_flag←1← 92C7 BEQ
STA zp_trace_flag ; Set the TRACE flag
92B4 JMP statement_loop ; next statement
92B7 .trace_on←1← 929C BEQ
INC zp_text_ptr_off ; TRACE ON: step past
92B9 JSR check_end_of_statement ; check the statement ends
92BC LDA #&ff ; ceiling = max
92BE BNE trace_ceiling_loop ; set it on
92C0 .trace_off←1← 92A0 BEQ
INC zp_text_ptr_off ; TRACE OFF: step past
92C2 JSR check_end_of_statement ; check the statement ends
92C5 LDA #0 ; flag = 0
92C7 BEQ trace_set_flag ; set it off
fall through ↓

TIME=

Set the centisecond elapsed-time clock. TIME = value.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
92C9 .stmt_time
JSR eval_eq_integer ; Step past "=", evaluate the value
92CC LDX #<(zp_iwa) ; Point at IWA
92CE LDY #>(zp_iwa) ; high byte of the address
92D0 STY zp_fwa_sign ; clear the 5th byte
92D2 LDA #osword_write_clock ; OSWORD 2 (write clock)
92D4 JSR osword ; Write system clock
92D7 JMP statement_loop ; next statement

Evaluate a comma-separated integer argument

Skip a comma at PtrB (Missing , if absent), then fall into eval_expr_integer to evaluate the following expression as an integer.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the comma
On ExitZP_IWA (&2A-&2D)the integer result
BRKMissing , or Type mismatch
92DA .eval_comma_integer←4← 9380 JSR← 9403 JSR← B459 JSR← B47C JSR
JSR skip_spaces_expect_comma ; Step past the comma
fall through ↓

Evaluate an expression as an integer

Evaluate the expression at PtrB (eval_or_eor) and coerce the result to an integer (coerce_to_integer). Unlike eval_expr_to_integer it does not sync the primary pointer, so the caller keeps managing PtrB.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the expression
On ExitZP_IWA (&2A-&2D)the integer result
BRKType mismatch on a string
92DD .eval_expr_integer←8← 8E40 JSR← 90EB JSR← 9702 JSR← AB41 JSR← B047 JSR← B0C2 JSR← B7F5 JSR← B81A JSR
JSR eval_or_eor ; Evaluate the expression
92E0 JMP coerce_to_integer ; ...and coerce to integer

Evaluate a single factor as an integer

Evaluate one factor at PtrB (eval_factor, so without applying any operators) and coerce it to an integer: a string raises Type mismatch, a real is converted, an integer is returned unchanged.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the factor
On ExitZP_IWA (&2A-&2D)the integer result
BRKType mismatch on a string
92E3 .eval_factor_integer←10← 8E58 JSR← 95AA JSR← 95B2 JSR← 9691 JSR← AB33 JSR← ABD2 JSR← ACD1 JSR← AFAD JSR← B3BD JSR← BFBC JSR
JSR eval_factor ; Evaluate a factor
92E6 BEQ coerce_type_error ; string: Type mismatch
92E8 BMI coerce_real ; real: convert to integer
92EA .return_9←2← 92F2 BPL← 92FF BMI
RTS ; Return

Require "=" then evaluate an integer

Copy PtrA to PtrB and require an "=" (sub_c9807), evaluate the value that follows, then fall into coerce_var_to_integer. Used by the pseudo-variable assignments (TIME=, PAGE=, ...).

On EntryZP_TEXT_PTR (&0B/&0C)PtrA before the "="
On ExitZP_IWA (&2A-&2D)the integer result
BRKMistake if "=" is missing, Type mismatch on a string
92EB .eval_eq_integer←4← 925D JSR← 926F JSR← 9283 JSR← 92C9 JSR
JSR copy_ptra_to_ptrb ; Expect "=" and evaluate
fall through ↓

Coerce the current value to an integer

Load the value type from zp_var_type and fall into coerce_to_integer, converting the just-evaluated value to an integer. The entry to use when the type is in zp_var_type rather than A.

On EntryZP_VAR_TYPE (&27)the value type
ZP_IWA / ZP_FWAthe value
On ExitZP_IWA (&2A-&2D)the integer result
BRKType mismatch on a string
92EE .coerce_var_to_integer←6← 8ED5 JSR← 93FD JSR← B592 JSR← BBB7 JSR← BF37 JSR← BF62 JSR
LDA zp_var_type ; Result type, then coerce to integer
fall through ↓

Coerce the current value to an integer

Check the type of the last evaluated value (in A from zp_var_type): a string raises "Type mismatch", an integer returns unchanged, and a real is converted to a 4-byte integer in the integer accumulator.

On EntryAvalue type from zp_var_type (&27)
92F0 .coerce_to_integer←21← 8824 JSR← 8E2E JSR← 92E0 JMP← 9688 JSR← 974A JSR← 976F JSR← 99BF JSR← 99CE JSR← 9B3E JSR← 9B59 JSR← 9B6C JSR← 9B7B JSR← 9B85 JSR← AB4D JSR← AD0C JSR← AF0F JSR← AFDD JSR← AFFF JSR← B05E JSR← B921 JSR← B9A2 JSR
BEQ coerce_type_error ; string?
92F2 BPL return_9 ; integer: return
92F4 .coerce_real←1← 92E8 BMI
JMP fwa_to_int ; real: convert to integer
92F7 .coerce_type_error←3← 92E6 BEQ← 92F0 BEQ← 92FD BEQ
JMP err_type_mismatch ; Type mismatch error

Evaluate an expression as a real

Evaluate a factor at PtrB and ensure the result is real, converting an integer with int_to_fwa.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the factor to evaluate
On ExitZP_FWA (&2E-&35)the value as a real
Areal type marker (negative)
BRKType mismatch on a string
92FA .eval_real←11← 9E3C JSR← A6BE JSR← A7B4 JSR← A7FE JSR← A8DA JSR← A907 JSR← A98D JSR← A998 JSR← AA91 JSR← ABB1 JSR← ABC2 JSR
JSR eval_factor ; Evaluate a factor
fall through ↓

Coerce the result to a real

Leave a real unchanged, convert an integer to FWA (int_to_fwa), or raise Type mismatch for a string.

On EntryAthe value type (negative real, positive integer, 0 string)
ZP_IWA / ZP_FWAthe value
On ExitZP_FWA (&2E-&35)the value as a real
Areal type marker (negative)
BRKType mismatch on a string
92FD .ensure_real←7← 9A59 JSR← 9D29 JSR← 9DE6 JSR← 9DF2 JSR← 9E36 JSR← B852 JSR← B870 JSR
BEQ coerce_type_error ; string?
92FF BMI return_9 ; real: return
9301 JMP int_to_fwa ; integer: convert to real

PROC

Call a named procedure, stacking parameters and the return position. PROCname[(params)].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
9304 .stmt_proc
LDA zp_text_ptr ; Remember the call site in PtrB
9306 STA zp_text_ptr2 ; PtrA low to PtrB,
9308 LDA zp_text_ptr_1 ; high byte,
930A STA zp_text_ptr2_1 ; to PtrB high,
930C LDA zp_text_ptr_off ; offset,
930E STA zp_text_ptr2_off ; to PtrB offset
9310 LDA #&f2 ; Enter the procedure (PROC token &F2)
9312 JSR call_proc_fn ; call it
9315 JSR sync_text_ptr ; check the statement ends
9318 JMP statement_loop ; next statement
931B .proc_clear_loop←1← 9332 BMI
LDY #3 ; Clear the local string length
931D LDA #0 ; zero,
931F STA (zp_iwa),y ; string length = 0 (offset 3)
9321 BEQ local_bump ; always
fall through ↓

LOCAL

Make variables local to the current PROC/FN, stacking their old values. LOCAL var,...

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
9323 .stmt_local←1← 934E BEQ
TSX ; LOCAL is only meaningful inside a PROC/FN
9324 CPX #&fc ; inside a PROC/FN?
9326 BCS not_local_error ; no: Not LOCAL error
9328 JSR parse_lvalue ; Parse the local variable
932B BEQ local_not_var ; not a variable: error
932D JSR stack_local ; Save the variable's value for restoration
9330 LDY zp_iwa_2 ; string variable?
9332 BMI proc_clear_loop ; yes: leave it cleared
9334 JSR stack_integer ; stack the variable address
9337 LDA #0 ; Initialise the local to zero / empty
9339 JSR int_result_a ; value zero
933C STA zp_var_type ; integer type
933E JSR assign_number ; assign it
9341 .local_bump←1← 9321 BEQ
TSX ; Bump the local count in the frame
9342 INC frame_local_count,x ; at frame offset +6
9345 LDY zp_text_ptr2_off ; Sync the program pointer
9347 STY zp_text_ptr_off ; from the PtrB offset
9349 JSR skip_spaces ; Skip spaces
934C CMP #',' ; A comma introduces another LOCAL
934E BEQ stmt_local ; yes
9350 JMP stmt_backup_end ; next statement
9353 .local_not_var←1← 932B BEQ
JMP stmt_check_end ; not a variable: error

ENDPROC

Return from a procedure. Checks a PROC frame is present (the framed token at &01FF must be &F2), then falls into the end-of-statement path whose rts unwinds the frame back through call_proc_fn (&B197) -- restoring LOCAL values, the caller's text pointer and the saved 6502/value stacks. It does NOT tidy the FOR/REPEAT/GOSUB stacks, so ENDPROC from inside a loop leaks that loop's frame. ENDPROC.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
9356 .stmt_endproc
TSX ; ENDPROC needs a PROC frame on the stack
9357 CPX #&fc ; stack near empty (no PROC frame)?
9359 BCS no_proc_error ; no frame: Not PROC error
935B LDA hw_stack_top ; Framed call token
935E CMP #&f2 ; The framed call must be a PROC
9360 BNE no_proc_error ; not PROC: error
9362 JMP check_end_of_statement ; Return to the caller, restoring locals
9365 .no_proc_error←2← 9359 BCS← 9360 BNE
BRK ; No PROC error
9366 EQUB &0D
9367 EQUS "No "
936A EQUB &F2
936B .not_local_error←1← 9326 BCS
BRK ; Not LOCAL error
936C EQUB &0C
936D EQUS "Not "
9371 EQUB &EA
9372 .bad_statement←4← 93B2 BNE← 93B8 BNE← 93C6 BCC← 93CD BCC
BRK ; Bad statement error
9373 EQUB &19
9374 EQUS "Bad "
9378 EQUB &EB, &00

GCOL

Set the graphics colour and plotting action. GCOL action, colour.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
937A .stmt_gcol
JSR eval_expr_to_integer ; Evaluate the GCOL mode
937D LDA zp_iwa ; save it
937F PHA ; push the GCOL mode
9380 JSR eval_comma_integer ; Step past the comma, evaluate the colour
9383 JSR sync_text_ptr ; check the statement ends
9386 LDA #&12 ; VDU 18 (GCOL)
9388 JSR oswrch ; send it
938B JMP mode_send ; send the mode and colour

COLOUR

Select the text colour or redefine a logical colour. COLOUR n.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
938E .stmt_colour
LDA #&11 ; VDU 17 (COLOUR)
9390 PHA ; push it for later
9391 JSR eval_expr_to_integer ; Evaluate the colour
9394 JSR check_end_of_statement ; check the statement ends
9397 JMP mode_send ; send VDU 17 and the colour

MODE

Select a screen mode, resetting the display. MODE n.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
939A .stmt_mode
LDA #&16 ; VDU 22 (MODE)
939C PHA ; push it for later
939D JSR eval_expr_to_integer ; Evaluate the mode number
93A0 JSR check_end_of_statement ; check the statement ends
93A3 JSR read_himem_byte ; Read the high word of the machine address
93A6 CPX #&ff ; not &xxFF: skip the memory test
93A8 BNE mode_reset_col ; addr not &xxFF: skip the check
93AA CPY #&ff ; not all ones: skip the memory test
93AC BNE mode_reset_col ; high byte &FF too: skip the check
93AE LDA zp_stack_ptr ; Stack not empty (STACK != HIMEM)?
93B0 CMP zp_himem ; STACK low vs HIMEM low
93B2 BNE bad_statement ; yes: Bad MODE
93B4 LDA zp_stack_ptr_1 ; STACK high,
93B6 CMP zp_himem_1 ; vs HIMEM high
93B8 BNE bad_statement ; Bad MODE
93BA LDX zp_iwa ; Top of RAM for this mode
93BC LDA #osbyte_read_himem_for_mode ; OSBYTE &85
93BE JSR osbyte ; Read top of user RAM for given screen mode
93C1 CPX zp_vartop ; below the variables?
93C3 TYA ; new top high,
93C4 SBC zp_vartop_1 ; vs VARTOP high
93C6 BCC bad_statement ; yes: Bad MODE
93C8 CPX zp_top ; below the program top?
93CA TYA ; new top high,
93CB SBC zp_top_1 ; vs TOP high
93CD BCC bad_statement ; yes: Bad MODE
93CF STX zp_himem ; Set HIMEM and STACK to the new top
93D1 STX zp_stack_ptr ; STACK low,
93D3 STY zp_himem_1 ; HIMEM high,
93D5 STY zp_stack_ptr_1 ; STACK high
93D7 .mode_reset_col←2← 93A8 BNE← 93AC BNE
JSR reset_print_column ; Reset the print column
93DA .mode_send←2← 938B JMP← 9397 JMP
PLA ; Send the stacked VDU byte
93DB JSR oswrch ; send it
93DE JSR vdu_send_byte ; Send the parameter
93E1 JMP statement_loop ; next statement

MOVE

Move the graphics cursor without drawing (PLOT 4). MOVE x, y.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
93E4 .stmt_move
LDA #4 ; MOVE is PLOT 4 (move the cursor, no draw)
93E6 BNE draw_save_mode ; do the PLOT
fall through ↓

DRAW

Draw a line from the graphics cursor to a point (PLOT 5). DRAW x, y.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
93E8 .stmt_draw
LDA #5 ; DRAW is PLOT 5 (draw a line)
93EA .draw_save_mode←1← 93E6 BNE
PHA ; Save the plot mode
93EB JSR eval_expr ; Evaluate the X coordinate
93EE JMP plot_coord ; evaluate Y and plot

PLOT

Plot a point, line or shape with a given mode. PLOT mode, x, y.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
93F1 .stmt_plot
JSR eval_expr_to_integer ; Evaluate the plot mode
93F4 LDA zp_iwa ; save it
93F6 PHA ; push the plot mode
93F7 JSR skip_spaces_expect_comma ; Step past the comma
93FA JSR eval_or_eor ; Evaluate the X coordinate
93FD .plot_coord←1← 93EE JMP
JSR coerce_var_to_integer ; ensure integer
9400 JSR stack_integer ; stack X
9403 JSR eval_comma_integer ; Step past the comma, evaluate Y
9406 JSR sync_text_ptr ; check the statement ends
9409 LDA #&19 ; VDU 25 (PLOT)
940B JSR oswrch ; send it
940E PLA ; Send the plot action
940F JSR oswrch ; send it
9412 JSR unstack_int_to_general ; Pop X
9415 LDA zp_general ; Send X low
9417 JSR oswrch ; send it
941A LDA zp_general_1 ; Send X high
941C JSR oswrch ; send it
941F JSR vdu_send_byte ; Send Y low
9422 LDA zp_iwa_1 ; Send Y high
9424 JSR oswrch ; send it
9427 JMP statement_loop ; next statement
942A .plot_send_hi←1← 9451 BEQ
LDA zp_iwa_1 ; Send the high byte
942C JSR oswrch ; send it
fall through ↓

VDU

Send bytes to the VDU drivers; ";" sends a 16-bit word. VDU n[,|;]...

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
942F .stmt_vdu←1← 944B BEQ
JSR skip_spaces ; Next character
9432 .vdu_loop←1← 944F BNE
CMP #':' ; ':' end of statement?
9434 BEQ vdu_done ; yes
9436 CMP #&0d ; end of line?
9438 BEQ vdu_done ; yes
943A CMP #&8b ; ELSE?
943C BEQ vdu_done ; yes
943E DEC zp_text_ptr_off ; Back up to the value
9440 JSR eval_expr_to_integer ; Evaluate it
9443 JSR vdu_send_byte ; send the low byte
9446 JSR skip_spaces ; Next character
9449 CMP #',' ; ',' another byte?
944B BEQ stmt_vdu ; yes
944D CMP #';' ; ';' 16-bit value?
944F BNE vdu_loop ; no: check the statement ends
9451 BEQ plot_send_hi ; yes: send the high byte too
9453 .vdu_done←3← 9434 BEQ← 9438 BEQ← 943C BEQ
JMP stmt_backup_end ; next statement

Send the integer accumulator low byte to OSWRCH

Load the low byte of the integer work accumulator and send it to the VDU by jumping through WRCHV (OSWRCH), which returns to the caller. Used to output a computed byte value.

On EntryZP_IWA (&2A)the byte to send (IWA low byte)
On ExitAthe byte sent (written to the VDU via OSWRCH)
9456 .vdu_send_byte←4← 8E3A JSR← 93DE JSR← 941F JSR← 9443 JSR
LDA zp_iwa ; Send the low byte to OSWRCH
9458 JMP (wrchv) ; jump through WRCHV (OSWRCH)

Find a PROC or FN definition by name

Search the PROC (token &F2) or FN linked list for a definition whose name starts at (zp_general)+1. Shares the chain walk with find_variable; returns a pointer to the body, or "not found".

On Entry(ZP_GENERAL) (&37/&38)the reference (PROC/FN token at +1)
ZP_FILEBLK (&39)the length of the name being sought
On ExitZP_IWA (&2A/&2B)pointer to the definition body (when found)
Zclear if found, set if not present
945B .find_proc_fn←1← B1E1 JSR
LDY #1 ; Look at the PROC/FN token
945D LDA (zp_general),y ; get it
945F LDY #&f6 ; Index the PROC / FN entries of the variable table
9461 CMP #&f2 ; Is it PROC (&F2)?
9463 BEQ findvar_chain_head ; PROC: scan the PROC list
9465 LDY #&f8 ; FN: point at the FN list head
9467 BNE findvar_chain_head ; scan the FN list
fall through ↓

Find a variable by name

Search the heap for a variable whose name starts at (zp_general)+1. The initial character selects one of the per-letter linked lists via the variable table; the chain is walked comparing the rest of the name. On a match, returns a pointer to the value in zp_iwa/zp_iwa_1; otherwise reports it is not present.

On Entry(ZP_GENERAL) (&37/&38)the variable reference (name at +1)
ZP_FILEBLK (&39)the length of the name being sought
On ExitZP_IWA (&2A/&2B)pointer to the value (when found)
Zclear if found, set if not present
; Each variable chain (one per initial letter) is a list of
; entries: a 2-byte link to the next, the rest of the name, a
; NUL, then the value. The walk alternates two node pointers
; (&3A/&3B and &3C/&3D), reading one node's link while it
; compares the other - so it never copies a whole pointer.
; On a match IWA points at the value; zp_fileblk holds the
; length of the name being sought.
9469 .find_variable←4← 916F JSR← 965A JSR← 96BC JSR← 96DF JSR
LDY #1 ; Point at the first character of the name
946B LDA (zp_general),y ; get it
946D ASL ; Two bytes per entry: double the initial letter
946E TAY ; Y = 2 * char: index into the variable table
946F .findvar_chain_head←2← 9463 BEQ← 9467 BNE
LDA resint_at,y ; Head of the chain from the variable table (&0400+2*ch)
9472 STA zp_fileblk_1 ; Chain head low byte -> node pointer (&3A)
9474 LDA resint_at_1,y ; Chain head high byte (&0400 + 1 + 2*char)
9477 STA zp_fwb_sign ; node pointer high (&3B)
9479 .findvar_walk←4← 94CA BNE← 94D2 BEQ← 94D6 BNE← 94DF BNE
LDA zp_fwb_sign ; End of the chain: variable not found
947B BEQ return_10 ; Zero high byte: end of chain, not found
947D LDY #0 ; Read the node: offset 0
947F LDA (zp_fileblk_1),y ; link low byte...
9481 STA zp_fwb_ovf ; ...saved (&3C)
9483 INY ; offset 1
9484 LDA (zp_fileblk_1),y ; link high byte...
9486 STA zp_fwb_exp ; ...saved (&3D)
9488 INY ; offset 2: first stored name character
9489 LDA (zp_fileblk_1),y ; get it
948B BNE findvar_cmp_a ; Non-null: compare the name
948D DEY ; Null name (the base entry): ...
948E CPY zp_fileblk ; did the search name end too?
9490 BNE findvar_walk_b ; no: follow the link
9492 INY ; advance
9493 BCS findvar_match_a ; match: compute the value pointer
9495 .findvar_cmp_a_loop←1← 94A0 BNE
INY ; Compare the rest of the name against this entry
9496 LDA (zp_fileblk_1),y ; next stored name character
9498 BEQ findvar_walk_b ; entry name ended early: no match
949A .findvar_cmp_a←1← 948B BNE
CMP (zp_general),y ; compare with the search name
949C BNE findvar_walk_b ; differ: follow the link
949E CPY zp_fileblk ; reached the end of the search name?
94A0 BNE findvar_cmp_a_loop ; no: keep comparing
94A2 INY ; does the entry name end here too?
94A3 LDA (zp_fileblk_1),y ; read the stored name char
94A5 BNE findvar_walk_b ; entry name is longer: no match
94A7 .findvar_match_a←1← 9493 BCS
TYA ; Match: return a pointer to the value
94A8 ADC zp_fileblk_1 ; value pointer = node + name length: low
94AA STA zp_iwa ; (IWA low)
94AC LDA zp_fwb_sign ; node high...
94AE ADC #0 ; + carry
94B0 STA zp_iwa_1 ; IWA high = pointer to the value
94B2 .return_10←2← 947B BEQ← 94B5 BEQ
RTS ; Return (IWA = value pointer, or not found)
94B3 .findvar_walk_b←4← 9490 BNE← 9498 BEQ← 949C BNE← 94A5 BNE
LDA zp_fwb_exp ; No match: follow the link to the next entry
94B5 BEQ return_10 ; Zero high byte: end of chain, not found
94B7 LDY #0 ; Read the next node via its saved link (&3C)
94B9 LDA (zp_fwb_ovf),y ; link low byte...
94BB STA zp_fileblk_1 ; ...into the node pointer (&3A)
94BD INY ; offset 1
94BE LDA (zp_fwb_ovf),y ; link high byte...
94C0 STA zp_fwb_sign ; ...(&3B)
94C2 INY ; offset 2: name character
94C3 LDA (zp_fwb_ovf),y ; get it
94C5 BNE findvar_cmp_b ; Non-null: compare the name
94C7 DEY ; Null name: ...
94C8 CPY zp_fileblk ; did the search name end too?
94CA BNE findvar_walk ; no: back to the other-pointer loop
94CC INY ; advance
94CD BCS findvar_match_b ; match: compute the value pointer
94CF .findvar_cmp_b_loop←1← 94DA BNE
INY ; compare loop
94D0 LDA (zp_fwb_ovf),y ; next stored character
94D2 BEQ findvar_walk ; entry name ended: no match
94D4 .findvar_cmp_b←1← 94C5 BNE
CMP (zp_general),y ; compare with the search name
94D6 BNE findvar_walk ; differ: next entry
94D8 CPY zp_fileblk ; end of the search name?
94DA BNE findvar_cmp_b_loop ; no: keep comparing
94DC INY ; does the entry name end here too?
94DD LDA (zp_fwb_ovf),y ; read the stored name char
94DF BNE findvar_walk ; entry name is longer: no match
94E1 .findvar_match_b←1← 94CD BCS
TYA ; value pointer = node + name length: low
94E2 ADC zp_fwb_ovf ; plus the node low
94E4 STA zp_iwa ; (IWA low)
94E6 LDA zp_fwb_exp ; node high...
94E8 ADC #0 ; + carry
94EA STA zp_iwa_1 ; IWA high = pointer to the value
94EC RTS ; Return the value pointer
94ED .create_def_entry←1← B171 JSR
LDY #1 ; Look at the PROC/FN token
94EF LDA (zp_general),y ; get it
94F1 TAX ; to X
94F2 LDA #&f6 ; PROC list index (&F6)
94F4 CPX #&f2 ; Is it PROC?
94F6 BEQ createvar_chain ; PROC: use that chain
94F8 LDA #&f8 ; FN list index (&F8)
94FA BNE createvar_chain ; use the FN chain
fall through ↓

Create a new variable

Append a new variable entry at VARTOP: walk to the end of the per-letter chain, link in the new node, and copy the name into it. The caller initialises the value bytes (clear_value_bytes) and advances VARTOP (sub_c9539).

On Entry(ZP_GENERAL) (&37/&38)the variable reference (name at +1)
ZP_FILEBLK (&39)the name length
ZP_VARTOP (&02/&03)top of the variable heap
On Exit(ZP_VARTOP)a new linked entry with its name
Yoffset of the last name byte in the entry
94FC .create_variable←3← 8BD5 JSR← 9174 JSR← 9589 JSR
LDY #1 ; First character of the name
94FE LDA (zp_general),y ; get it
9500 ASL ; double it to index the variable table
9501 .createvar_chain←2← 94F6 BEQ← 94FA BNE
STA zp_fileblk_1 ; chain pointer low (&3A)
9503 LDA #4 ; table base high page (&04)
9505 STA zp_fwb_sign ; chain pointer high (&3B)
9507 .createvar_walk_loop←1← 9514 BPL
LDA (zp_fileblk_1),y ; Walk to the end of the chain: link high
9509 BEQ createvar_link ; zero: found the end, append here
950B TAX ; save it
950C DEY ; link low
950D LDA (zp_fileblk_1),y ; read it
950F STA zp_fileblk_1 ; follow the link: low
9511 STX zp_fwb_sign ; high
9513 INY ; advance
9514 BPL createvar_walk_loop ; loop
9516 .createvar_link←1← 9509 BEQ
LDA zp_vartop_1 ; Link the new entry (at VARTOP): high...
9518 STA (zp_fileblk_1),y ; ...into the previous link
951A LDA zp_vartop ; low...
951C DEY ; point at the low link byte
951D STA (zp_fileblk_1),y ; (store)
951F TYA ; the new entry header
9520 INY ; next entry byte
9521 STA (zp_vartop),y ; (store)
9523 CPY zp_fileblk ; name length reached?
9525 BEQ return_11 ; done
9527 .createvar_copy_loop←1← 952E BNE
INY ; Copy the name into the entry:
9528 LDA (zp_general),y ; name char...
952A STA (zp_vartop),y ; ...into the entry
952C CPY zp_fileblk ; all of the name?
952E BNE createvar_copy_loop ; loop
9530 RTS ; Return

Zero a run of value bytes

Write X zero bytes after a new variable name at (zp_vartop),Y, initialising its value to zero.

On EntryXthe number of value bytes to clear
ZP_VARTOP (&02/&03)base of the new variable entry
Yoffset of the last name byte
On Exit(ZP_VARTOP)X value bytes zeroed
Yadvanced past the cleared bytes
X0
A0
9531 .clear_value_bytes←4← 8BDF JSR← 9179 JSR← 957F JSR← B176 JSR
LDA #0 ; Zero X value bytes after the name:
9533 .clearval_loop←1← 9537 BNE
INY ; advance
9534 STA (zp_vartop),y ; clear a byte
9536 DEX ; count down
9537 BNE clearval_loop ; loop
9539 .advance_vartop←1← B184 JSR
SEC ; Advance VARTOP past the new entry:
953A TYA ; Y = bytes used
953B ADC zp_vartop ; low
953D BCC vartop_check ; no carry into the high byte
953F INC zp_vartop_1 ; carry into high
9541 .vartop_check←1← 953D BCC
LDY zp_vartop_1 ; Check VARTOP hasn't reached the stack:
9543 CPY zp_stack_ptr_1 ; compare high
9545 BCC vartop_commit ; below: room
9547 BNE vartop_no_room ; above: no room
9549 CMP zp_stack_ptr ; equal: compare low
954B BCC vartop_commit ; below: room
954D .vartop_no_room←1← 9547 BNE
LDA #0 ; No room: unlink the new entry...
954F LDY #1 ; link field offset
9551 STA (zp_fileblk_1),y ; (clear the link)
9553 JMP err_no_room ; No room error
9556 .vartop_commit←2← 9545 BCC← 954B BCC
STA zp_vartop ; Commit the new VARTOP
9558 .return_11←1← 9525 BEQ
RTS ; Return

Validate / measure a variable name

Scan the variable name at (zp_general)+1, stopping at the first non-name character (letters, digits and _ are accepted; a leading digit is rejected, giving an empty name). Advances X as the running text offset.

On Entry(ZP_GENERAL) (&37/&38)a pointer to the name (name at +1)
Xthe current text offset
On ExitYone past the name (Y-1 = length; Y=1 means invalid)
Xthe text offset advanced past the name
Athe terminating non-name character
9559 .validate_var_name←1← 914B JSR
LDY #1 ; Validate the name from offset 1:
955B .valname_loop←2← 956F BNE← B1D5 JSR
LDA (zp_general),y ; character...
955D CMP #'0' ; below "0": end of name
955F BCC return_12 ; below "0": stop
9561 CMP #'@' ; in the digit/symbol range?
9563 BCS valname_check_uc ; letter range: continue
9565 CMP #':' ; ":" or above (not a digit)?
9567 BCS return_12 ; not a name character: stop
9569 CPY #1 ; is this the first character?
956B BEQ return_12 ; names can't start with a digit
956D .valname_count←2← 9577 BCC← 957C BCC
INX ; count the character
956E INY ; next
956F BNE valname_loop ; loop
9571 .valname_check_uc←1← 9563 BCS
CMP #'_' ; below "_"?
9573 BCS valname_check_lc ; "_" or lower-case: accept
9575 CMP #'[' ; A-Z?
9577 BCC valname_count ; letter: accept
9579 .return_12←3← 955F BCC← 9567 BCS← 956B BEQ
RTS ; Return
957A .valname_check_lc←1← 9573 BCS
CMP #'{' ; a-z?
957C BCC valname_count ; accept
957E RTS ; Return
957F .zero_new_value←2← 9590 BNE← 9593 BNE
JSR clear_value_bytes ; Zero the value bytes of the new variable
fall through ↓

Parse an assignment target variable

Parse the variable reference being assigned to (parse_var_ref) and return a pointer to its storage plus its type, creating the variable (create_variable + clear_value_bytes) if it does not yet exist. Shared by LET, FOR (stmt_for), LOCAL (stmt_local) and the PROC/FN parameter binder (call_proc_fn) — so any assignable target, including ?, ! and $ indirections and array elements, is equally valid as a LET target, a loop control variable, a LOCAL, or a formal parameter.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset of the target reference
On ExitZP_IWA (&2A/&2B)a pointer to the variable storage
ZP_IWA_2 (&2C)the variable type byte
9582 .parse_lvalue←9← 85A5 JSR← 8BE4 JSR← 90E1 JSR← 9328 JSR← B256 JSR← B7C4 JSR← B9E4 JSR← BA7F JSR← BB1F JSR
JSR parse_var_ref ; Parse the variable reference
9585 BNE return_13 ; found: return
9587 BCS return_13 ; indirection/array: return
9589 JSR create_variable ; undefined: create the variable
958C LDX #5 ; Decide how many value bytes to clear
958E CPX zp_iwa_2 ; integer type (4) or real (5)?
9590 BNE zero_new_value ; integer: clear 5 bytes
9592 INX ; real: clear 6 bytes
9593 BNE zero_new_value ; then retry the parse so it is found
9595 .lvalue_indirect←1← 95DF BCC
CMP #'!' ; '!' indirection?
9597 BEQ lvalue_word ; yes: word indirection
9599 CMP #'$' ; '$' indirection?
959B BEQ lvalue_dollar ; yes: string indirection
959D EOR #&3f ; '?' indirection?
959F BEQ lvalue_save_width ; yes: byte indirection
95A1 LDA #0 ; none: not an lvalue
95A3 SEC ; set carry (not an lvalue)
95A4 .return_13←2← 9585 BNE← 9587 BCS
RTS ; Return
95A5 .lvalue_word←1← 9597 BEQ
LDA #4 ; '!': word width (4)
95A7 .lvalue_save_width←1← 959F BEQ
PHA ; Save the width
95A8 INC zp_text_ptr2_off ; step past the operator
95AA JSR eval_factor_integer ; evaluate the address
95AD JMP pvr_restore_type ; finish as an indirection
95B0 .lvalue_dollar←1← 959B BEQ
INC zp_text_ptr2_off ; '$': step past the operator
95B2 JSR eval_factor_integer ; evaluate the address
95B5 LDA zp_iwa_1 ; address in zero page?
95B7 BEQ lvalue_range_error ; yes: $ range error
95B9 LDA #&80 ; Type = string indirection (&80)
95BB STA zp_iwa_2 ; store the type byte
95BD SEC ; found: SEC
95BE RTS ; Return
95BF .lvalue_range_error←1← 95B7 BEQ
BRK ; $ range error
95C0 EQUB &08
95C1 EQUS "$ range"
95C8 EQUB &00

Parse a variable reference / lvalue

Scan a variable name, array element, or indirection operator at the text pointer. Sets the value address in &2A/&2B and a type byte in &2C: 0=? byte indirection, 4=integer (also ! word indirection), 5=real, &80=$ indirection, &81=string lvalue. Returns A nonzero when resolved; A=0 carry clear for a valid name not yet defined (caller creates it), A=0 carry set when no name is present.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset of the reference
On ExitZP_IWA (&2A/&2B)the value address (when resolved)
ZP_IWA_2 (&2C)the type byte (0/4/5/&80/&81)
Anonzero if resolved, 0 if a name needs creating / absent
Cwith A=0: clear = create the name, set = no name
95C9 .parse_var_ref←2← 9582 JSR← B695 JSR
LDA zp_text_ptr ; Copy the text pointer into the scan pointer
95CB STA zp_text_ptr2 ; low byte,
95CD LDA zp_text_ptr_1 ; high byte,
95CF STA zp_text_ptr2_1 ; store it
95D1 LDY zp_text_ptr_off ; Start one before the text offset
95D3 DEY ; (the scan loop pre-increments)
95D4 .pvr_advance←1← 95DB BEQ
INY ; Advance
95D5 .pvr_record_offset←1← 8EEC JSR
STY zp_text_ptr2_off ; Record the scan offset
95D7 LDA (zp_text_ptr2),y ; Next character
95D9 CMP #' ' ; space?
95DB BEQ pvr_advance ; skip spaces
fall through ↓

Parse a variable reference at the scan pointer

Alternate entry to parse_var_ref for callers that have already pointed the scan pointer zp_text_ptr2 (&19/&1A) at the first non-space character (in A). Classify the reference as an indirection operator, a resident integer (A%..Z%), or a named/array variable, setting the value address in zp_iwa (&2A/&2B) and the type byte in zp_iwa_2 (&2C).

On EntryAthe first non-space character of the reference
ZP_TEXT_PTR2 (&19/&1A)the scan pointer at the reference
ZP_TEXT_PTR2_OFF (&1B)the scan offset of the character in A
On ExitZP_IWA (&2A/&2B)the value address (when resolved)
ZP_IWA_2 (&2C)the type byte (0/4/5/&80/&81)
Anonzero if resolved, 0 if a name needs creating / absent
Cwith A=0: clear = create the name, set = no name
95DD .pvr_parse←2← 8BC9 JSR← AE22 JSR
CMP #'@' ; below '@': an indirection operator
95DF BCC lvalue_indirect ; below '@': handle as indirection ($ ! ?)
95E1 CMP #'[' ; above 'Z'?
95E3 BCS pvr_named ; yes: a named (dynamic) variable
95E5 ASL ; Resident integer: hash char to &0400 + char*4
95E6 ASL ; (continued)
95E7 STA zp_iwa ; value pointer low
95E9 LDA #4 ; page &04
95EB STA zp_iwa_1 ; value pointer high byte (&2B)
95ED INY ; Second character
95EE LDA (zp_text_ptr2),y ; read it
95F0 INY ; advance the scan offset
95F1 CMP #'%' ; must be '%' for a resident integer
95F3 BNE pvr_named ; not %: treat as a named variable
95F5 LDX #4 ; Type = integer
95F7 STX zp_iwa_2 ; store in the type byte (&2C)
95F9 LDA (zp_text_ptr2),y ; Following character
95FB CMP #'(' ; '(' -> array element
95FD BNE pvr_check_indirect ; yes: handle as an array
95FF .pvr_named←2← 95E3 BCS← 95F3 BNE
LDX #5 ; Named variable: type = real/string (5)
9601 STX zp_iwa_2 ; store in the type byte (&2C)
9603 LDA zp_text_ptr2_off ; Point &37/&38 at the name in the text
9605 CLC ; offset + scan pointer:
9606 ADC zp_text_ptr2 ; low byte,
9608 LDX zp_text_ptr2_1 ; high byte in X,
960A BCC pvr_name_setup ; no carry into the high byte
960C INX ; carry into the high byte
960D CLC ; clear carry to back up one
960E .pvr_name_setup←1← 960A BCC
SBC #0 ; back up one (name reads from offset 1): low
9610 STA zp_general ; &37 = name pointer low
9612 BCS pvr_name_ptr_hi ; no borrow into the high byte
9614 DEX ; borrow into the high byte
9615 .pvr_name_ptr_hi←1← 9612 BCS
STX zp_general_1 ; &38 = name pointer high
9617 LDX zp_text_ptr2_off ; Reset the length counter
9619 LDY #1 ; scan the name from offset 1
961B .pvr_scan_loop←3← 962B BNE← 9633 BNE← 963F BNE
LDA (zp_general),y ; Scan the name: next character
961D CMP #'A' ; below 'A'?
961F BCS pvr_scan_check_uc ; no: check letters
9621 CMP #'0' ; a digit?
9623 BCC pvr_name_end ; no: name ends
9625 CMP #':' ; above '9'?
9627 BCS pvr_name_end ; no: name ends
9629 INX ; count this character
962A INY ; advance the scan
962B BNE pvr_scan_loop ; continue
962D .pvr_scan_check_uc←1← 961F BCS
CMP #'[' ; above 'Z'?
962F BCS pvr_scan_check_lc ; yes: check lower case
9631 INX ; A-Z: count it
9632 INY ; advance the scan
9633 BNE pvr_scan_loop ; continue
9635 .pvr_scan_check_lc←1← 962F BCS
CMP #'_' ; below '_'?
9637 BCC pvr_name_end ; yes: name ends
9639 CMP #'{' ; above 'z'?
963B BCS pvr_name_end ; yes: name ends
963D INX ; _ or a-z: count it
963E INY ; advance the scan
963F BNE pvr_scan_loop ; continue
9641 .pvr_name_end←4← 9623 BCC← 9627 BCS← 9637 BCC← 963B BCS
DEY ; Back up to the terminator
9642 BEQ pvr_no_name ; empty name: undefined
9644 CMP #'$' ; '$' suffix -> string variable
9646 BEQ pvr_string ; yes
9648 CMP #'%' ; '%' suffix -> integer variable
964A BNE pvr_name_len ; no: real variable
964C DEC zp_iwa_2 ; mark the type integer
964E INY ; step past the %
964F INX ; count the % in the name length
9650 INY ; peek at the following character
9651 LDA (zp_general),y ; check the following character
9653 DEY ; step back to the %
9654 .pvr_name_len←1← 964A BNE
STY zp_fileblk ; Record the name length
9656 CMP #'(' ; '(' -> array element
9658 BEQ pvr_resint_array ; yes
965A JSR find_variable ; Look the variable up in storage
965D BEQ pvr_undefined ; not found: undefined
965F STX zp_text_ptr2_off ; Update the scan offset past the name
9661 .pvr_after_name←1← 96AC JMP
LDY zp_text_ptr2_off ; Next character after the name
9663 LDA (zp_text_ptr2),y ; read it
9665 .pvr_check_indirect←1← 95FD BNE
CMP #'!' ; '!' indirection following?
9667 BEQ pvr_word_indirect ; yes: word indirection
9669 CMP #'?' ; '?' indirection following?
966B BEQ pvr_byte_indirect ; yes: byte indirection
966D CLC ; Plain variable: found
966E STY zp_text_ptr2_off ; save the scan offset
9670 LDA #&ff ; A = &FF (found)
9672 RTS ; Return
9673 .pvr_no_name←1← 9642 BEQ
LDA #0 ; No name found: A=0, SEC
9675 SEC ; (set carry: caller errors)
9676 RTS ; Return
9677 .pvr_undefined←2← 965D BEQ← 96BF BEQ
LDA #0 ; Valid name, undefined: A=0, CLC
9679 CLC ; (clear carry: caller creates it)
967A RTS ; Return
967B .pvr_byte_indirect←1← 966B BEQ
LDA #0 ; '?' indirection: byte type (1)
967D BEQ pvr_save_width ; always taken
967F .pvr_word_indirect←1← 9667 BEQ
LDA #4 ; '!' indirection: word type (4)
9681 .pvr_save_width←1← 967D BEQ
PHA ; Save the indirection width
9682 INY ; step past the operator
9683 STY zp_text_ptr2_off ; record the new scan offset
9685 JSR load_var_by_type ; Stack the base address
9688 JSR coerce_to_integer ; evaluate it as an integer
968B LDA zp_iwa_1 ; Save the base address:
968D PHA ; push high byte,
968E LDA zp_iwa ; low byte,
9690 PHA ; push it too
9691 JSR eval_factor_integer ; evaluate the index expression
9694 CLC ; address = base + index
9695 PLA ; pull base low,
9696 ADC zp_iwa ; + index low,
9698 STA zp_iwa ; store low,
969A PLA ; pull base high,
969B ADC zp_iwa_1 ; + index high,
969D STA zp_iwa_1 ; store high
969F .pvr_restore_type←1← 95AD JMP
PLA ; Restore the indirection type
96A0 STA zp_iwa_2 ; into the type byte (0=? or 4=!)
96A2 CLC ; found: A=&FF, CLC
96A3 LDA #&ff ; A = &FF (found)
96A5 RTS ; Return
96A6 .pvr_resint_array←1← 9658 BEQ
INX ; Resident-integer array: step past "("
96A7 INC zp_fileblk ; count the "(" in the name length too
96A9 JSR index_array ; index the array
96AC JMP pvr_after_name ; check for trailing ! or ?
96AF .pvr_string←1← 9646 BEQ
INX ; String variable: step past "$"
96B0 INY ; advance the scan
96B1 STY zp_fileblk ; record the name length
96B3 INY ; peek at the char after the $
96B4 DEC zp_iwa_2 ; mark the type string
96B6 LDA (zp_general),y ; following character
96B8 CMP #'(' ; '(' -> string array
96BA BEQ pvr_string_array ; yes
96BC JSR find_variable ; Look the string variable up
96BF BEQ pvr_undefined ; not found: undefined
96C1 STX zp_text_ptr2_off ; Update the scan offset
96C3 LDA #&81 ; Type = string lvalue (&81)
96C5 STA zp_iwa_2 ; store in the type byte (&2C)
96C7 SEC ; found: SEC
96C8 RTS ; Return
96C9 .pvr_string_array←1← 96BA BEQ
INX ; String array: step past "("
96CA STY zp_fileblk ; record the name length
96CC DEC zp_iwa_2 ; mark the type string
96CE JSR index_array ; index the array
96D1 LDA #&81 ; Type = string lvalue (&81)
96D3 STA zp_iwa_2 ; store in the type byte (&2C)
96D5 SEC ; found: SEC
96D6 RTS ; Return
96D7 .array_error←2← 96E2 BEQ← 9709 BNE
BRK ; No such array: Array error
96D8 EQUB &0E
96D9 EQUS "Array"
96DE EQUB &00

Locate an array and evaluate one subscript

Find the named array (find_variable) and step the value pointer to the addressed element, evaluating one subscript at PtrB and bounds-checking it. Raises Array on a missing array, Subscript on an out-of-range index.

On Entry(ZP_GENERAL) (&37/&38)the array name reference
ZP_TEXT_PTR2 (&19/&1A)PtrB at the subscript
On ExitZP_IWA (&2A/&2B)a pointer to the element
ZP_IWA_2 (&2C)the element type
BRKArray / Subscript on error
96DF .index_array←2← 96A9 JSR← 96CE JSR
JSR find_variable ; Find the array
96E2 BEQ array_error ; missing: Array error
96E4 STX zp_text_ptr2_off ; Update the scan offset
96E6 LDA zp_iwa_2 ; Save the variable type...
96E8 PHA ; push the type
96E9 LDA zp_iwa ; ...and the array data pointer
96EB PHA ; push the low byte
96EC LDA zp_iwa_1 ; high byte
96EE PHA ; push it
96EF LDY #0 ; Offset to the array data (1 + 2*dims)
96F1 LDA (zp_iwa),y ; read it
96F3 CMP #4 ; more than a few?
96F5 BCC idxarr_one ; no: handle the small case
96F7 TYA ; Accumulate the flattened index
96F8 JSR int_result_a ; running index = 0
96FB LDA #1 ; one subscript so far
96FD STA zp_iwa_3 ; store it
96FF .idxarr_loop←1← 9742 BCS
JSR stack_integer ; Stack the running index
9702 JSR eval_expr_integer ; Evaluate the next subscript
9705 INC zp_text_ptr2_off ; step past it
9707 CPX #',' ; comma (another subscript)?
9709 BNE array_error ; no: wrong dimension count -> Array
970B LDX #&39 ; Unstack the running index
970D JSR unstack_int_to_zp ; into &39/&3A
9710 LDY zp_fwb_ovf ; dimension descriptor offset
9712 PLA ; Recover the array base pointer
9713 STA zp_general_1 ; high byte
9715 PLA ; low byte
9716 STA zp_general ; set the pointer
9718 PHA ; re-stack it
9719 LDA zp_general_1 ; high byte
971B PHA ; push it too
971C JSR check_subscript_bound ; Bounds-check this subscript
971F STY zp_iwa_3 ; advance to the next dimension
9721 LDA (zp_general),y ; This dimension's extent: low
9723 STA zp_fwb_m2 ; store it
9725 INY ; ...high
9726 LDA (zp_general),y ; read it
9728 STA zp_fwb_m3 ; store it
972A LDA zp_iwa ; Add the subscript into the running index
972C ADC zp_fileblk ; + subscript low,
972E STA zp_iwa ; store the low byte
9730 LDA zp_iwa_1 ; high byte
9732 ADC zp_fileblk_1 ; + subscript high,
9734 STA zp_iwa_1 ; store the high byte
9736 JSR imul16 ; index *= this extent (Horner)
9739 LDY #0 ; Dimensions processed so far
973B SEC ; set carry for subtract
973C LDA (zp_general),y ; data offset (byte 0)
973E SBC zp_iwa_3 ; minus the offset reached
9740 CMP #3 ; more dimensions to come?
9742 BCS idxarr_loop ; yes: next subscript
9744 JSR stack_integer ; Stack the running index
9747 JSR eval_subexpr ; Evaluate the final subscript
974A JSR coerce_to_integer ; ...as an integer
974D PLA ; Recover the array base
974E STA zp_general_1 ; high byte
9750 PLA ; low byte
9751 STA zp_general ; set the pointer
9753 LDX #&39 ; Unstack the running index
9755 JSR unstack_int_to_zp ; into &39/&3A
9758 LDY zp_fwb_ovf ; dimension descriptor offset
975A JSR check_subscript_bound ; Bounds-check the final subscript
975D CLC ; Add it into the index
975E LDA zp_fileblk ; subscript low
9760 ADC zp_iwa ; plus the index low,
9762 STA zp_iwa ; store it
9764 LDA zp_fileblk_1 ; subscript high
9766 ADC zp_iwa_1 ; plus the index high,
9768 STA zp_iwa_1 ; store it
976A BCC idxarr_type ; scale by the element size
976C .idxarr_one←1← 96F5 BCC
JSR eval_subexpr ; One subscript: evaluate it
976F JSR coerce_to_integer ; ...as an integer
9772 PLA ; Recover the array base
9773 STA zp_general_1 ; high byte
9775 PLA ; low byte
9776 STA zp_general ; set the pointer
9778 LDY #1 ; descriptor offset 1
977A JSR check_subscript_bound ; Bounds-check the subscript
977D .idxarr_type←1← 976A BCC
PLA ; Element type
977E STA zp_iwa_2 ; store it
9780 CMP #5 ; real/string (5 bytes)?
9782 BNE idxarr_scale ; no: integer (4 bytes)
9784 LDX zp_iwa_1 ; index = 5 (x4 + x)
9786 LDA zp_iwa ; keep the original low
9788 ASL zp_iwa ; index x2:
978A ROL zp_iwa_1 ; carry up to the high byte,
978C ASL zp_iwa ; index x4:
978E ROL zp_iwa_1 ; carry up again
9790 ADC zp_iwa ; add the original (x5),
9792 STA zp_iwa ; store the low byte
9794 TXA ; original high byte
9795 ADC zp_iwa_1 ; add x4 high (x5),
9797 STA zp_iwa_1 ; store the high byte
9799 BCC idxarr_offset ; done: skip the x4 path
979B .idxarr_scale←1← 9782 BNE
ASL zp_iwa ; index *= 4
979D ROL zp_iwa_1 ; carry into the high byte,
979F ASL zp_iwa ; index x4:
97A1 ROL zp_iwa_1 ; carry up again
97A3 .idxarr_offset←1← 9799 BCC
TYA ; Add the element offset within the descriptor
97A4 ADC zp_iwa ; plus the scaled index low,
97A6 STA zp_iwa ; store the low byte
97A8 BCC idxarr_addr ; no carry into the high byte
97AA INC zp_iwa_1 ; else bump the high byte
97AC CLC ; clear carry for the base add
97AD .idxarr_addr←1← 97A8 BCC
LDA zp_general ; Element address = base + offset
97AF ADC zp_iwa ; plus the offset low,
97B1 STA zp_iwa ; store the low byte
97B3 LDA zp_general_1 ; base high byte
97B5 ADC zp_iwa_1 ; plus the offset high,
97B7 STA zp_iwa_1 ; store it: element address in IWA
97B9 RTS ; Return

Check a subscript against a dimension extent

Raise Subscript if the subscript in IWA is negative or not less than the two-byte dimension extent at (zp_general),Y, otherwise advance Y past the extent.

On EntryZP_IWA (&2A-&2D)the subscript to check
ZP_GENERAL (&37/&38)a pointer to the dimension data
Yoffset of the two-byte extent
On ExitYadvanced past the two-byte extent
Xpreserved
BRKSubscript if out of range
97BA .check_subscript_bound←3← 971C JSR← 975A JSR← 977A JSR
LDA zp_iwa_1 ; Top bits of the subscript
97BC AND #&c0 ; keep just bits 6-7
97BE ORA zp_iwa_2 ; combine with the high bytes...
97C0 ORA zp_iwa_3 ; and byte 3
97C2 BNE subscript_error ; negative or huge: Subscript
97C4 LDA zp_iwa ; Compare against the dimension extent
97C6 CMP (zp_general),y ; subscript low vs extent low
97C8 INY ; next extent byte
97C9 LDA zp_iwa_1 ; subscript high...
97CB SBC (zp_general),y ; minus the extent high
97CD BCS subscript_error ; not less than the extent: Subscript
97CF INY ; advance past the extent
97D0 RTS ; Return
97D1 .subscript_error←2← 97C2 BNE← 97CD BCS
BRK ; Subscript error
97D2 EQUB &0F
97D3 EQUS "Subscript"
97DC EQUB &00
97DD .chkline_skip←1← 97E5 BEQ
INC zp_text_ptr_off ; Advance
fall through ↓

Test for an embedded line number

Skip spaces at the text pointer; if the next token is a line-number token (&8D), fall into decode_line_number to decode it into IWA and return carry set, otherwise return carry clear.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset of the next character
On ExitCset if a line number was found and decoded
ZP_IWA (&2A/&2B)the line number (when found)
ZP_TEXT_PTR_OFF (&0A)advanced past it (when found)
97DF .check_line_number←10← 8B2D JSR← 8F31 JSR← 8F40 JSR← 8F6E JSR← 8F80 JSR← 9295 JSR← 98E3 JSR← B5AC JSR← B5D8 JSR← B99A JSR
LDY zp_text_ptr_off ; Next character
97E1 LDA (zp_text_ptr),y ; read it
97E3 CMP #' ' ; space?
97E5 BEQ chkline_skip ; skip it
97E7 CMP #&8d ; line-number token?
97E9 BNE not_line_number ; no: carry clear
fall through ↓

Decode a 3-byte line number into IWA

Reverse the three-byte &8D line-number encoding, reconstructing the 16-bit line number in IWA (the two high-bit pairs are recovered from the control byte). The inverse of encode_line_number, and like it exact only for 0-32767.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
Yoffset of the &8D token
On ExitZP_IWA (&2A/&2B)the decoded 16-bit line number
ZP_TEXT_PTR_OFF (&0A)advanced past the 3 bytes
Cset (line number found)
97EB .decode_line_number←2← 903D JSR← B659 JSR
INY ; Control byte
97EC LDA (zp_text_ptr),y ; read the packed top-bits byte
97EE ASL ; recover the top bits
97EF ASL ; (shifted left by two)
97F0 TAX ; keep a copy for the high byte
97F1 AND #&c0 ; low byte top bits
97F3 INY ; next encoded byte
97F4 EOR (zp_text_ptr),y ; XOR the encoded low byte
97F6 STA zp_iwa ; line number low
97F8 TXA ; high byte top bits
97F9 ASL ; shift the high byte top bits into place,
97FA ASL ; (two places)
97FB INY ; next encoded byte
97FC EOR (zp_text_ptr),y ; XOR the encoded high byte
97FE STA zp_iwa_1 ; line number high
9800 INY ; step past the 3 bytes
9801 STY zp_text_ptr_off ; save the advanced text offset
9803 SEC ; carry set: found
9804 RTS ; Return
9805 .not_line_number←1← 97E9 BNE
CLC ; carry clear: not a line number
9806 RTS ; Return
9807 .copy_ptra_to_ptrb←1← 92EB JSR
LDA zp_text_ptr ; Copy PtrA to PtrB
9809 STA zp_text_ptr2 ; (low)
980B LDA zp_text_ptr_1 ; high...
980D STA zp_text_ptr2_1 ; (high)
980F LDA zp_text_ptr_off ; offset...
9811 STA zp_text_ptr2_off ; (offset)
fall through ↓

Expect "=" then evaluate the right-hand side

Skip spaces at PtrB, require an "=" sign, then evaluate the expression that follows, leaving the value in the accumulator with its type in zp_var_type. Raises Mistake if "=" is missing.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB before the "="
On ExitAthe result type (also in zp_var_type, &27)
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the evaluated value
BRKMistake if "=" is missing
9813 .eval_after_eq←4← 8BEE JSR← 8BFE JSR← 981B BEQ← BF34 JSR
LDY zp_text_ptr2_off ; Next character
9815 INC zp_text_ptr2_off ; and advance
9817 LDA (zp_text_ptr2),y ; read it
9819 CMP #' ' ; space?
981B BEQ eval_after_eq ; skip it
981D CMP #'=' ; "="?
981F BEQ eval_rhs ; yes: evaluate
9821 .mistake_error←1← 9846 BNE
BRK ; Mistake error
9822 EQUB &04
9823 EQUS "Mistake"

Raise "Syntax error" error

Raise BASIC error &10, "Syntax error", via a BRK error block. Reached when a statement or expression is malformed (e.g. from check_end_of_statement when unexpected text follows a statement). Does not return.

On ExitCONTROLraises BRK error &10 "Syntax error"; does not return to the caller
982A .syntax_error←7← 8604 JMP← 8855 JMP← 8C0B JMP← 8F2E JMP← 986B BNE← B6A0 JMP← B9C7 JMP
BRK ; Mistake error
982B EQUB &10
982C EQUS "Syntax error"
9838 .escape_error←2← 987D BMI← BC22 JMP
BRK ; Escape error
9839 EQUB &11
983A EQUS "Escape"
9840 EQUB &00

Require "=" at the parser pointer

Skip spaces at PtrB and raise Mistake unless the next character is "=".

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the parser position
On ExitAthe "=" character (consumed)
ZP_TEXT_PTR2_OFF (&1B)advanced past the "="
BRKMistake if "=" is missing
9841 .expect_eq←2← 8BD2 JSR← B7CE JSR
JSR skip_spaces_ptr2 ; Skip spaces
9844 CMP #'=' ; "="?
9846 BNE mistake_error ; no: Mistake
9848 RTS ; Return
9849 .eval_rhs←2← 981F BEQ← BF5F JSR
JSR eval_or_eor ; Evaluate the right-hand side
fall through ↓

Sync PtrB offset and require statement end after an assignment

Tail shared by assignment and function-return paths once the right-hand-side value has been computed: move the pending result byte into A, resync Y to PtrB's offset (zp_text_ptr2_off, &1B), and tail-jump into check_end_of_statement at cend_check to demand the statement terminator against PtrA. Unlike eval_rhs (&9849) it does not evaluate the RHS itself; the value is assumed already in place.

On EntryXthe result byte left by the assignment / RHS evaluation (the value type)
ZP_TEXT_PTR (&0B)the program text pointer (PtrA)
ZP_TEXT_PTR2_OFF (&1B)PtrB's offset at the end of the parsed RHS
On ExitCONTROLjumps into check_end_of_statement (cend_check); returns via its rts with PtrA advanced past the statement, or BRK Syntax error / Escape
984C .assign_check_end←4← 8B56 JMP← B58F JSR← BBB4 JSR← BEDA JMP
TXA ; Result type
984D LDY zp_text_ptr2_off ; Sync the program pointer
984F JMP cend_check ; check the statement ends

Resync PtrA's offset to PtrB, then require statement end

Load the resume position from PtrB's offset (zp_text_ptr2_off, &1B) into Y and tail-jump into check_end_of_statement at cend_back. Used after a sub-parse that tracked its position in PtrB (e.g. FOR / assignment operands): it resumes at PtrB's offset against the PtrA text pointer and demands the statement end there, advancing PtrA to the next statement.

On EntryZP_TEXT_PTR (&0B)the program text pointer (PtrA)
ZP_TEXT_PTR2_OFF (&1B)PtrB offset to resume from
On ExitCONTROLjumps into check_end_of_statement (cend_back); returns via its rts with PtrA advanced past the statement, or BRK Syntax error / Escape
9852 .sync_text_ptr←8← 8F0E JSR← 9315 JSR← 9383 JSR← 9406 JSR← B461 JSR← B484 JSR← B4A3 JSR← BF9C JSR
LDY zp_text_ptr2_off ; Sync the program pointer
9854 JMP cend_back ; check the statement ends

Check for end of statement

Skip spaces and require the statement to end here: a colon, an end-of-line (&0D), or ELSE. Anything else raises "Syntax error". Also polls for Escape.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset of the next character
On ExitZP_TEXT_PTR_OFF (&0A)at the statement terminator
Athe terminator character
BRKSyntax error if more text follows
9857 .check_end_of_statement←25← 8AB6 JSR← 8AC8 JSR← 8AD0 JSR← 8ADA JSR← 8B98 JSR← 8EBD JSR← 8EC4 JSR← 8F45 JSR← 8F8F JMP← 928D JSR← 92A5 JSR← 92B9 JSR← 92C2 JSR← 9362 JMP← 9394 JSR← 93A0 JSR← 9880 JSR← B5E3 JSR← B88B JSR← B8B6 JSR← B8CF JSR← B8E4 JSR← BB07 JSR← BD11 JSR← BFE4 JSR
LDY zp_text_ptr_off ; Program pointer offset
9859 .cend_back←2← 858C JSR← 9854 JMP
DEY ; step back
985A .cend_skip_loop←1← 985F BEQ
INY ; Next character
985B LDA (zp_text_ptr),y ; read it
985D CMP #' ' ; space?
985F BEQ cend_skip_loop ; skip it
9861 .cend_check←1← 984F JMP
CMP #':' ; ':' statement separator?
9863 BEQ skip_to_statement_end ; yes
9865 CMP #&0d ; end of line?
9867 BEQ skip_to_statement_end ; yes
9869 CMP #&8b ; ELSE token?
986B BNE syntax_error ; none: Mistake (syntax error)
fall through ↓

Advance PtrA past the current statement

Add the terminator offset in Y to the PtrA text pointer (zp_text_ptr, &0B/&0C), carrying into the high byte, so PtrA now addresses the statement terminator; reset the character offset zp_text_ptr_off (&0A) to 1; then poll ESCFLG and raise Escape if pressed before returning. The common tail of the end-of-statement check and several statement handlers.

On EntryYoffset of the statement terminator within the line
ZP_TEXT_PTR (&0B)the program text pointer (PtrA)
On ExitZP_TEXT_PTR (&0B)advanced to the terminator (&0B/&0C)
ZP_TEXT_PTR_OFF (&0A)reset to 1
A1 (from the reset offset)
CONTROLrts, unless Escape is pending (jumps to escape_error)
986D .skip_to_statement_end←8← 850F JSR← 8B73 JSR← 9863 BEQ← 9867 BEQ← B16E JSR← B5FF JSR← B8FC JSR← BBEA JSR
CLC ; Advance the program pointer past the statement
986E TYA ; offset to A,
986F ADC zp_text_ptr ; + pointer low,
9871 STA zp_text_ptr ; store low,
9873 BCC reset_offset_1 ; no carry into the high byte
9875 INC zp_text_ptr_1 ; carry into the high byte
9877 .reset_offset_1←4← 9873 BCC← 98EB JSR← B74B JSR← B964 JSR
LDY #1 ; Reset the offset to 1
9879 STY zp_text_ptr_off ; store it (&0A)
987B .check_escape←1← 8F56 JSR
BIT zp_escflg ; Escape pressed (ESCFLG)?
987D BMI escape_error ; yes: raise Escape
987F .return_14←1← 9888 BEQ
RTS ; Return
9880 .check_statement_terminated←1← B837 JSR
JSR check_end_of_statement ; Check this statement is terminated
9883 DEY ; Re-read the terminator
9884 LDA (zp_text_ptr),y ; read it
9886 CMP #':' ; ':' (more on this line)?
9888 BEQ return_14 ; yes: continue on the line
988A LDA zp_text_ptr_1 ; At the end of program memory?
988C CMP #7 ; in the &0700 buffer (immediate)?
988E BEQ to_immediate ; yes: return to immediate mode
fall through ↓

Advance PtrA to the next program line

Step from the current line's terminating CR onto the next line: if the next line-number marker has bit 7 set the program has ended and control jumps to immediate_loop; otherwise, when TRACE is enabled, load the line number into zp_iwa and call trace_line, then skip the 3-byte line header and reset the character offset. Returns with Z clear so callers can branch to run the next statement.

On EntryZP_TEXT_PTR (&0B/&0C)the current program line (PtrA)
Yoffset of the current line's terminating CR within the line
On ExitZP_TEXT_PTR (&0B/&0C)advanced to the next program line
ZP_TEXT_PTR_OFF (&0A)reset to 1
ZP_IWA (&2A/&2B)the new line number, when TRACE is on
CONTROLrts with Z clear (more program follows); at end of program jmps to immediate_loop and does not return
9890 .step_to_next_line←2← 859F JSR← 8B91 JSR
INY ; Next byte (line-number marker)
9891 LDA (zp_text_ptr),y ; read it
9893 BMI to_immediate ; end of program: immediate mode
9895 LDA zp_trace_flag ; TRACE on?
9897 BEQ step_past_line_header ; no: skip the line number
9899 TYA ; Save the offset
989A PHA ; push it
989B INY ; Line number high byte
989C LDA (zp_text_ptr),y ; read it,
989E PHA ; push it
989F DEY ; Line number low byte
98A0 LDA (zp_text_ptr),y ; read it,
98A2 TAY ; low byte to Y,
98A3 PLA ; high byte to A
98A4 JSR iwa_from_ya ; IWA = line number
98A7 JSR trace_line ; trace it
98AA PLA ; Restore the offset
98AB TAY ; into Y
98AC .step_past_line_header←1← 9897 BEQ
INY ; Step past the 3-byte line header
98AD SEC ; set carry (advance by offset + 1),
98AE TYA ; offset to A,
98AF ADC zp_text_ptr ; + pointer low,
98B1 STA zp_text_ptr ; store low,
98B3 BCC line_reset_offset ; no carry into the high byte
98B5 INC zp_text_ptr_1 ; carry into the high byte
98B7 .line_reset_offset←1← 98B3 BCC
LDY #1 ; Reset the offset to 1
98B9 STY zp_text_ptr_off ; store it (&0A)
98BB .return_15←1← 990D BCS
RTS ; Return
98BC .to_immediate←2← 988E BEQ← 9893 BMI
JMP immediate_loop ; End of program: immediate mode
98BF .if_type_error←1← 98C5 BEQ
JMP err_type_mismatch ; Type mismatch error

IF

Conditional execution: evaluate the expression and run the THEN part, else skip to ELSE or the end of the line. IF expr [THEN] ... [ELSE ...].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
98C2 .stmt_if
JSR eval_expr ; Evaluate the condition
98C5 BEQ if_type_error ; string: handle elsewhere
98C7 BPL if_skip_cond ; integer: use as is
98C9 JSR fwa_to_int ; real: convert to integer
98CC .if_skip_cond←1← 98C7 BPL
LDY zp_text_ptr2_off ; Advance the text pointer past the condition
98CE STY zp_text_ptr_off ; copy it to PtrA
98D0 LDA zp_iwa ; Is the condition value zero (false)?
98D2 ORA zp_iwa_1 ; or byte 1,
98D4 ORA zp_iwa_2 ; byte 2,
98D6 ORA zp_iwa_3 ; byte 3
98D8 BEQ if_false ; false: look for ELSE
98DA CPX #&8c ; true: is the next token THEN?
98DC BEQ if_then ; yes
98DE .if_no_then←1← 98E6 BCC
JMP next_statement ; no THEN: execute the statement that follows
98E1 .if_then←1← 98DC BEQ
INC zp_text_ptr_off ; Step past THEN
98E3 .if_then_line←2← 9900 BEQ← B997 JMP
JSR check_line_number ; Is a line number following (THEN <line>)?
98E6 BCC if_no_then ; no: execute the statements after THEN
98E8 JSR flt_find ; yes: set up the line number...
98EB JSR reset_offset_1 ; to the line start, test Escape
98EE JMP goto_trace ; ...and GOTO it
98F1 .if_false←1← 98D8 BEQ
LDY zp_text_ptr_off ; False: scan the rest of the line for ELSE
98F3 .if_scan_else_loop←1← 98FC BNE
LDA (zp_text_ptr),y ; Next character
98F5 CMP #&0d ; end of line?
98F7 BEQ if_no_else ; yes: move to the next line
98F9 INY ; advance
98FA CMP #&8b ; ELSE token?
98FC BNE if_scan_else_loop ; no: keep scanning
98FE STY zp_text_ptr_off ; found ELSE: point past it
9900 BEQ if_then_line ; execute what follows ELSE
9902 .if_no_else←1← 98F7 BEQ
JMP stmt_eol ; No ELSE: continue at the next line

Print [line] when TRACE is active

If the line number in IWA is at or below the TRACE ceiling, print it in brackets; otherwise do nothing.

On EntryZP_IWA (&2A/&2B)the current line number
ZP_TRACE_MAX (&21/&22)the TRACE ceiling
On ExitTHE OUTPUT[line] when traced, nothing otherwise
9905 .trace_line←2← 98A7 JSR← B8D6 JSR
LDA zp_iwa ; Line number vs TRACE ceiling
9907 CMP zp_trace_max ; vs ceiling low,
9909 LDA zp_iwa_1 ; line high
990B SBC zp_trace_max_1 ; minus ceiling high
990D BCS return_15 ; above the ceiling: do not trace
990F LDA #'[' ; Print '['
9911 JSR print_char ; output it
9914 JSR print_line_number ; print the line number
9917 LDA #']' ; Print ']'
9919 JSR print_char ; output it
991C JMP print_space ; and a space

Print a 16-bit line number in decimal

Convert the value in IWA to decimal by repeated subtraction of the powers of ten at &996B/&99B9, suppressing leading zeros, optionally right-justified in a field (the sub_c9923 entry uses a 5-digit TRACE field).

On EntryZP_IWA (&2A/&2B)the line number to print
On ExitTHE OUTPUTthe decimal digits printed
ZP_COUNT (&1E)the print column, advanced
991F .print_line_number←3← 9097 JSR← 9914 JSR← B662 JSR
LDA #0 ; Default: no field padding
9921 BEQ plnum_setwidth ; always: skip the TRACE entry
fall through ↓

Print a line number in a 5-digit field

Alternate entry to print_line_number that prints the value in zp_iwa as a decimal line number right-justified in a 5-character field (used by TRACE and LIST). Loads a field width of 5, then joins print_line_number at plnum_setwidth.

On EntryZP_IWA (&2A/&2B)the line number to print
On ExitZP_COUNT (&1E)the print column, advanced past the 5-character field
9923 .trace_print_number←2← 90B8 JSR← B61D JSR
LDA #5 ; TRACE entry: 5-digit field
9925 .plnum_setwidth←1← 9921 BEQ
STA zp_print_bytes ; Field width
9927 LDX #4 ; Five powers of ten, index 4..0
9929 .plnum_clear_loop←1← 9944 BPL
LDA #0 ; Clear this digit
992B STA zp_fwb_m2,x ; zero its count
992D SEC ; set carry for the subtraction
992E .plnum_sub_loop←1← 9941 BNE
LDA zp_iwa ; Subtract this power of ten from IWA
9930 SBC powers_of_ten_lo,x ; minus the low byte,
9933 TAY ; stash the low result
9934 LDA zp_iwa_1 ; IWA high byte
9936 SBC powers_of_ten_hi,x ; minus the high byte
9939 BCC plnum_next_power ; underflow: digit complete
993B STA zp_iwa_1 ; keep the remainder
993D STY zp_iwa ; (low byte too)
993F INC zp_fwb_m2,x ; count the digit
9941 BNE plnum_sub_loop ; subtract again
9943 .plnum_next_power←1← 9939 BCC
DEX ; Next power of ten
9944 BPL plnum_clear_loop ; until all five powers used
9946 LDX #5 ; Find the most significant non-zero digit
9948 .plnum_digit_loop←1← 994D BEQ
DEX ; step down to the next digit
9949 BEQ plnum_top_digit ; units digit: always shown
994B LDA zp_fwb_m2,x ; is this digit zero?
994D BEQ plnum_digit_loop ; yes: skip the leading zero
994F .plnum_top_digit←1← 9949 BEQ
STX zp_general ; Index of the top digit
9951 LDA zp_print_bytes ; Field padding requested?
9953 BEQ plnum_print_loop ; no
9955 SBC zp_general ; leading spaces = field - digits
9957 BEQ plnum_print_loop ; none
9959 TAY ; space count into Y
995A .plnum_space_loop←1← 995E BNE
JSR print_space ; print a leading space
995D DEY ; one fewer space
995E BNE plnum_space_loop ; until the field is padded
9960 .plnum_print_loop←3← 9953 BEQ← 9957 BEQ← 9968 BPL
LDA zp_fwb_m2,x ; Digit
9962 ORA #'0' ; to ASCII
9964 JSR print_char ; print it
9967 DEX ; next digit
9968 BPL plnum_print_loop ; until all digits printed
996A RTS ; Return
996B .powers_of_ten_lo←1Used as index base by← 9930 SBC
EQUB &01, &0A, &64, &E8, &10

Search the program for a line number

Walk the program text from PAGE looking for the target line number in IWA. Returns a scratch pointer (zp_fwb_exp/zp_fwb_m1) to the matching line, or to the first line with a greater number (the insertion point) when there is no exact match.

On EntryZP_IWA (&2A/&2B)the target line number
ZP_PAGE (&18)PAGE, the start of the program
On Exit(ZP_FWB_EXP) (&3D/&3E)pointer to the line (or insertion point)
Cclear if an exact match was found, set if not found
9970 .find_program_line←3← B5EC JSR← B9AF JSR← BC2D JSR
LDY #0 ; Pointer low = 0
9972 STY zp_fwb_exp ; scratch pointer in fwb_exp/m1
9974 LDA zp_page ; Pointer high = PAGE
9976 STA zp_fwb_m1 ; pointer high
9978 .fpl_check_high←2← 9988 BCC← 998C BCS
LDY #1 ; This line's number: high byte
997A LDA (zp_fwb_exp),y ; read it
997C CMP zp_iwa_1 ; vs the target high byte
997E BCS fpl_found ; >=: a candidate
9980 .fpl_next_loop←1← 9996 BCC
LDY #3 ; Line length
9982 LDA (zp_fwb_exp),y ; read it
9984 ADC zp_fwb_exp ; Advance to the next line
9986 STA zp_fwb_exp ; pointer low
9988 BCC fpl_check_high ; no carry: same page
998A INC zp_fwb_m1 ; carry into the pointer high
998C BCS fpl_check_high ; continue
998E .fpl_found←1← 997E BCS
BNE fpl_return ; high byte greater: found (not exact)
9990 LDY #2 ; This line's number: low byte
9992 LDA (zp_fwb_exp),y ; read it
9994 CMP zp_iwa ; vs the target low byte
9996 BCC fpl_next_loop ; less: next line
9998 BNE fpl_return ; greater: found (not exact)
999A TYA ; Exact match: leave the pointer at this line
999B ADC zp_fwb_exp ; point at the line number
999D STA zp_fwb_exp ; (store)
999F BCC fpl_return ; no carry
99A1 INC zp_fwb_m1 ; carry into the pointer high
99A3 CLC ; flag the exact match (carry clear)
99A4 .fpl_return←3← 998E BNE← 9998 BNE← 999F BCC
LDY #2 ; Point at the line number
99A6 RTS ; Return
99A7 .div_zero_error←2← 99F0 BEQ← A6BB JMP
BRK ; Division by zero error
99A8 EQUB &12
99A9 EQUS "Division by zero"
99B9 .powers_of_ten_hi←1Used as index base by← 9936 SBC
EQUB &00, &00, &00, &03, &27

Unsigned/signed 32-bit integer division

Divide the dividend (IWA, the left operand) by the divisor (the right operand, evaluated here at PtrB). Both are made positive and a 32-step shift-subtract division runs with the dividend/quotient in &39-&3C and the remainder in &3D-&40. The quotient sign is the XOR of the operand signs (&37), the remainder sign the dividend sign (&38). Raises Division by zero. Shared by DIV (reads the quotient) and MOD (the remainder).

On EntryZP_IWA (&2A-&2D)the dividend (left operand)
ZP_TEXT_PTR2 (&19/&1A)PtrB at the divisor operand
On Exit(&39-&3C)the quotient
(&3D-&40)the remainder
(&37) / (&38)the quotient sign / remainder sign
BRKDivision by zero
99BE .iwa_divide←2← 9E01 JSR← 9E0A JSR
TAY ; Coerce the dividend to integer
99BF JSR coerce_to_integer ; IWA = the integer dividend
99C2 LDA zp_iwa_3 ; Save the dividend sign
99C4 PHA ; push it
99C5 JSR iwa_abs ; take |dividend|
99C8 JSR pow_stack_eval ; Stack it, evaluate the divisor
99CB STX zp_var_type ; remember the operator
99CD TAY ; coerce the divisor to integer
99CE JSR coerce_to_integer ; IWA = the integer divisor
99D1 PLA ; Recover the dividend sign
99D2 STA zp_general_1 ; remainder takes the dividend sign
99D4 EOR zp_iwa_3 ; quotient sign = dividend XOR divisor
99D6 STA zp_general ; store the quotient sign (&37)
99D8 JSR iwa_abs ; take |divisor|
99DB LDX #&39 ; Move the dividend to the work area (&39-&3C)
99DD JSR unstack_int_to_zp ; unpack starting at &39
99E0 STY zp_fwb_exp ; Clear the remainder (&3D-&40)
99E2 STY zp_fwb_m1 ; &3E,
99E4 STY zp_fwb_m2 ; &3F,
99E6 STY zp_fwb_m3 ; &40
99E8 LDA zp_iwa_3 ; Divisor zero?
99EA ORA zp_iwa ; OR the low byte,
99EC ORA zp_iwa_1 ; &2B,
99EE ORA zp_iwa_2 ; &2C (all zero => /0)
99F0 BEQ div_zero_error ; yes: Division by zero
99F2 LDY #&20 ; 32 bits
99F4 .idiv_norm_loop←1← 99FF BPL
DEY ; Normalise: count down
99F5 BEQ return_16 ; dividend exhausted: done
99F7 ASL zp_fileblk ; shift the dividend left until the top bit is set
99F9 ROL zp_fileblk_1 ; carrying up through &3A,
99FB ROL zp_fwb_sign ; &3B,
99FD ROL zp_fwb_ovf ; &3C (until bit 7 is set)
99FF BPL idiv_norm_loop ; loop
9A01 .idiv_shift_loop←1← 9A36 BNE
ROL zp_fileblk ; Shift a bit from dividend into the remainder
9A03 ROL zp_fileblk_1 ; dividend &3A,
9A05 ROL zp_fwb_sign ; &3B,
9A07 ROL zp_fwb_ovf ; &3C,
9A09 ROL zp_fwb_exp ; into remainder &3D,
9A0B ROL zp_fwb_m1 ; &3E,
9A0D ROL zp_fwb_m2 ; &3F,
9A0F ROL zp_fwb_m3 ; &40 (remainder high)
9A11 SEC ; Try remainder - divisor
9A12 LDA zp_fwb_exp ; low byte &3D,
9A14 SBC zp_iwa ; minus divisor low,
9A16 PHA ; save the result,
9A17 LDA zp_fwb_m1 ; byte &3E,
9A19 SBC zp_iwa_1 ; minus divisor &2B,
9A1B PHA ; save it,
9A1C LDA zp_fwb_m2 ; byte &3F,
9A1E SBC zp_iwa_2 ; minus divisor &2C,
9A20 TAX ; hold it in X,
9A21 LDA zp_fwb_m3 ; high byte &40,
9A23 SBC zp_iwa_3 ; minus divisor high
9A25 BCC idiv_restore ; doesn't fit: leave the remainder
9A27 STA zp_fwb_m3 ; fits: keep the new remainder (quotient bit = 1)
9A29 STX zp_fwb_m2 ; and &3F,
9A2B PLA ; pull &3E,
9A2C STA zp_fwb_m1 ; store it,
9A2E PLA ; pull &3D,
9A2F STA zp_fwb_exp ; store it
9A31 BCS idiv_next_bit ; next bit
9A33 .idiv_restore←1← 9A25 BCC
PLA ; discard the trial subtraction
9A34 PLA ; (continued)
9A35 .idiv_next_bit←1← 9A31 BCS
DEY ; Next bit
9A36 BNE idiv_shift_loop ; loop
9A38 .return_16←1← 99F5 BEQ
RTS ; Return
9A39 .cmp_int_real←1← 9AAB BMI
STX zp_var_type ; Int vs real: save the operator
9A3B JSR unstack_integer ; unstack the integer
9A3E JSR stack_real ; stack the real
9A41 JSR int_to_fwa ; convert the integer to FWA
9A44 JSR fwb_copy_from_fwa ; FWB = it
9A47 JSR unstack_real ; unstack the real operand
9A4A JSR fwa_unpack_var ; into FWA
9A4D JMP cmp_bytes ; compare
9A50 .cmp_real_real←1← 9AA0 BMI
JSR stack_real ; Real vs real: stack the left
9A53 JSR eval_add_sub ; evaluate the right operand
9A56 STX zp_var_type ; save the operator
9A58 TAY ; type
9A59 JSR ensure_real ; ensure real
9A5C JSR unstack_real ; unstack the left operand
fall through ↓

Compare FWA with a fp variable

Unpack the packed real operand into FWB and compare it with FWA by sign, exponent and mantissa, returning the ordering for the relational operators.

On EntryZP_FWA (&2E-&35)the left operand
(ZP_FP_PTR) (&4B/&4C)the packed real right operand
On ExitZset if the values are equal
Cthe sign-corrected ordering when they differ
Anonzero when they differ
9A5F .fp_compare←1← B78F JSR
JSR fwb_unpack_var ; Unpack the operand into FWB
9A62 .cmp_bytes←1← 9A4D JMP
LDX zp_var_type ; Restore the operator
9A64 LDY #0 ; assume equal
9A66 LDA zp_fwb_sign ; FWB sign
9A68 AND #&80 ; keep the sign bit
9A6A STA zp_fwb_sign ; (store)
9A6C LDA zp_fwa_sign ; FWA sign
9A6E AND #&80 ; keep the sign bit
9A70 CMP zp_fwb_sign ; signs differ?
9A72 BNE return_17 ; yes: unequal
9A74 LDA zp_fwb_exp ; Compare exponents
9A76 CMP zp_fwa_exp ; vs FWA exp
9A78 BNE cmp_combine ; differ
9A7A LDA zp_fwb_m1 ; Compare mantissa byte 1
9A7C CMP zp_fwa_m1 ; vs FWA m1
9A7E BNE cmp_combine ; differ
9A80 LDA zp_fwb_m2 ; byte 2
9A82 CMP zp_fwa_m2 ; vs FWA m2
9A84 BNE cmp_combine ; differ
9A86 LDA zp_fwb_m3 ; byte 3
9A88 CMP zp_fwa_m3 ; vs FWA m3
9A8A BNE cmp_combine ; differ
9A8C LDA zp_fwb_m4 ; byte 4
9A8E CMP zp_fwa_m4 ; vs FWA m4
9A90 BNE cmp_combine ; differ
9A92 .return_17←1← 9A72 BNE
RTS ; Equal: return
9A93 .cmp_combine←5← 9A78 BNE← 9A7E BNE← 9A84 BNE← 9A8A BNE← 9A90 BNE
ROR ; Combine the compare carry...
9A94 EOR zp_fwb_sign ; ...with the sign
9A96 ROL ; ...for the ordering
9A97 LDA #1 ; result nonzero
9A99 RTS ; Return
9A9A .cmp_type_error←2← 9AA9 BEQ← 9AEE BNE
JMP err_type_mismatch ; Type mismatch error

Compare the current value with the next operand

Take the already-evaluated left value (whose type is in X), stack it, evaluate the next arithmetic operand at PtrB, then compare the two - integer, real or string - returning the ordering flags for the relational operators. A one-instruction head (txa) on eval_and_compare that supplies the left type in X.

On EntryXthe type of the left value (already evaluated)
ZP_IWA (&2A) / ZP_FWA (&2E) / STRING_WORK (&0600)the left value
ZP_TEXT_PTR2 (&19/&1A)PtrB at the right operand
On ExitCthe ordering of left vs right
Zset if equal
Xthe next unconsumed operator token
9A9D .compare_values←5← 9BCD JSR← 9BD6 JSR← 9BE1 JSR← 9BF1 JSR← 9BFC JSR
TXA ; Current type
fall through ↓

Evaluate the next operand and compare

Stack the already-evaluated left value, evaluate the next arithmetic operand at PtrB, then compare the two (integer, real or string), returning the ordering flags for the relational operators.

On EntryAthe type of the left value (already evaluated)
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the left value
ZP_TEXT_PTR2 (&19/&1A)PtrB at the right operand
On ExitCthe ordering of left vs right
Zset if equal
Xthe next unconsumed operator token
9A9E .eval_and_compare←1← 9BAF JSR
BEQ str_compare ; string: compare strings
9AA0 BMI cmp_real_real ; float: compare floats
9AA2 JSR stack_integer ; Stack the integer
9AA5 JSR eval_add_sub ; evaluate the next operand
9AA8 TAY ; type
9AA9 BEQ cmp_type_error ; string: Type mismatch
9AAB BMI cmp_int_real ; float: compare floats
fall through ↓

Compare an integer variable with the accumulator

Signed 32-bit compare of the integer on top of the BASIC stack against IWA: bias both sign bits so an unsigned subtract orders signed values, then subtract, returning the ordering in C and Z (Z = equal).

On Entry(ZP_STACK_PTR) (&04/&05)the stacked integer (left operand)
ZP_IWA (&2A-&2D)the accumulator (right operand)
On ExitCset if stacked >= IWA
Zset if equal
ZP_IWA (&2A-&2D)corrupted (holds the difference)
9AAD .iwa_test_var
LDA zp_iwa_3 ; Flip the sign bit of the IWA top byte
9AAF EOR #&80 ; toggle bit 7 (bias to unsigned),
9AB1 STA zp_iwa_3 ; store it back
9AB3 SEC ; Subtract IWA from the stacked integer: byte 0
9AB4 LDY #0 ; from offset 0,
9AB6 LDA (zp_stack_ptr),y ; stacked low byte,
9AB8 SBC zp_iwa ; minus IWA low,
9ABA STA zp_iwa ; store the difference
9ABC INY ; byte 1
9ABD LDA (zp_stack_ptr),y ; stacked byte 1,
9ABF SBC zp_iwa_1 ; minus IWA &2B,
9AC1 STA zp_iwa_1 ; store it
9AC3 INY ; byte 2
9AC4 LDA (zp_stack_ptr),y ; stacked byte 2,
9AC6 SBC zp_iwa_2 ; minus IWA &2C,
9AC8 STA zp_iwa_2 ; store it
9ACA INY ; byte 3 (top)
9ACB LDA (zp_stack_ptr),y ; stacked top byte,
9ACD LDY #0 ; reset Y (bytes done)
9ACF EOR #&80 ; flip its sign bit too
9AD1 SBC zp_iwa_3 ; finish the signed subtract: sets C
9AD3 ORA zp_iwa ; OR the low bytes...
9AD5 ORA zp_iwa_1 ; and &2B,
9AD7 ORA zp_iwa_2 ; ...to set Z when the values are equal
9AD9 PHP ; Save the comparison flags
9ADA CLC ; Drop the integer from the stack
9ADB LDA #4 ; 4 bytes,
9ADD ADC zp_stack_ptr ; + stack pointer low,
9ADF STA zp_stack_ptr ; store low,
9AE1 BCC itest_done ; no carry into the high byte
9AE3 INC zp_stack_ptr_1 ; carry into the high byte
9AE5 .itest_done←1← 9AE1 BCC
PLP ; Restore the flags
9AE6 RTS ; Return
9AE7 .str_compare←1← 9A9E BEQ
JSR stack_string ; String compare: stack the left
9AEA JSR eval_add_sub ; evaluate the right operand
9AED TAY ; type
9AEE BNE cmp_type_error ; number: Type mismatch
9AF0 STX zp_general ; Save the current pointer
9AF2 LDX zp_strbuf_len ; current length
9AF4 LDY #0 ; from 0
9AF6 LDA (zp_stack_ptr),y ; stacked length
9AF8 STA zp_fileblk ; save it
9AFA CMP zp_strbuf_len ; compare lengths
9AFC BCS strcmp_shorter ; use the shorter
9AFE TAX ; stacked shorter: X = its length
9AFF .strcmp_shorter←1← 9AFC BCS
STX zp_fileblk_1 ; shorter length
9B01 LDY #0 ; compare from 0
9B03 .strcmp_loop←1← 9B0D BEQ
CPY zp_fileblk_1 ; Reached the shorter length?
9B05 BEQ strcmp_lengths ; yes: compare lengths
9B07 INY ; Next character
9B08 LDA (zp_stack_ptr),y ; stacked string
9B0A CMP strbuf_base,y ; vs current string
9B0D BEQ strcmp_loop ; equal: continue
9B0F BNE strcmp_result ; differ
9B11 .strcmp_lengths←1← 9B05 BEQ
LDA zp_fileblk ; Compare the lengths
9B13 CMP zp_strbuf_len ; stacked length vs current
9B15 .strcmp_result←1← 9B0F BNE
PHP ; Save the result
9B16 JSR drop_stacked_string ; Drop the stacked string
9B19 LDX zp_general ; restore the saved pointer (X)
9B1B PLP ; Restore the result
9B1C RTS ; Return

Evaluate an expression

The interpreter's main expression entry point. Copies the primary text pointer (PtrA: zp_text_ptr / zp_text_ptr_off) to the secondary pointer (PtrB: zp_text_ptr2 / &1B) and evaluates the expression there.

On ExitZP_VAR_TYPE (&27)result type (integer / real / string)
ZP_IWA / ZP_FWAthe result value
&1Badvanced past the expression
9B1D .eval_expr←11← 8821 JSR← 886D JSR← 8B53 JSR← 8ED2 JSR← 93EB JSR← 98C2 JSR← B58C JSR← B91E JSR← B99F JSR← BBB1 JSR← BED2 JSR
LDA zp_text_ptr ; Start evaluating: copy PtrA to the working PtrB
9B1F STA zp_text_ptr2 ; PtrB low = PtrA
9B21 LDA zp_text_ptr_1 ; PtrA high...
9B23 STA zp_text_ptr2_1 ; ...to PtrB high
9B25 LDA zp_text_ptr_off ; PtrA offset...
9B27 STA zp_text_ptr2_off ; ...to PtrB offset
fall through ↓

Evaluator level 7: OR, EOR

Lowest precedence: bitwise OR (&84) and EOR (&82) on integers.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the (sub)expression to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the value, selected by the type in A
Xthe next unconsumed operator token (lookahead)
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed text
9B29 .eval_or_eor←16← 8D39 JSR← 8DEB JSR← 92DD JSR← 93FA JSR← 9849 JSR← AC1D JSR← ACE2 JSR← ACF0 JSR← AE56 JSR← AFCC JSR← AFEE JSR← B039 JSR← B28E JSR← B4B1 JSR← B84F JSR← B86D JSR
JSR eval_and ; Evaluate the higher-precedence (AND) operand first
9B2C .eval_or_loop←1← 9B53 BNE
CPX #&84 ; Is the next operator OR or EOR at this level?
9B2E BEQ do_or ; yes
9B30 CPX #&82 ; EOR token?
9B32 BEQ do_eor ; yes
9B34 DEC zp_text_ptr2_off ; neither: back up over the token
9B36 TAY ; set the result-type flags
9B37 STA zp_var_type ; store it
9B39 RTS ; Return
9B3A .do_or←1← 9B2E BEQ
JSR coerce_left_int ; OR: stack the left operand, evaluate the right
9B3D TAY ; ensure the right operand is integer
9B3E JSR coerce_to_integer ; coerce it
9B41 LDY #3 ; Four bytes
9B43 .or_byte_loop←1← 9B4C BPL
LDA (zp_stack_ptr),y ; stacked byte
9B45 ORA zp_iwa,y ; OR with IWA
9B48 STA zp_iwa,y ; store back
9B4B DEY ; next byte
9B4C BPL or_byte_loop ; until all four OR-ed
9B4E .or_drop_stack←1← 9B69 BMI
JSR drop_stack_integer ; Drop the stacked operand
9B51 LDA #&40 ; Result type = integer
9B53 BNE eval_or_loop ; Loop to handle any further OR / EOR
9B55 .do_eor←1← 9B32 BEQ
JSR coerce_left_int ; EOR: stack the left operand, evaluate the right
9B58 TAY ; ensure the right operand is integer
9B59 JSR coerce_to_integer ; coerce it
9B5C LDY #3 ; Four bytes
9B5E .eor_byte_loop←1← 9B67 BPL
LDA (zp_stack_ptr),y ; stacked byte
9B60 EOR zp_iwa,y ; EOR with IWA
9B63 STA zp_iwa,y ; store back
9B66 DEY ; next byte
9B67 BPL eor_byte_loop ; until all four EOR-ed
9B69 BMI or_drop_stack ; drop and loop
9B6B .coerce_left_int←2← 9B3A JSR← 9B55 JSR
TAY ; Coerce the left operand to integer
9B6C JSR coerce_to_integer ; coerce it
9B6F JSR stack_integer ; stack it
fall through ↓

Evaluator level 6: AND

Bitwise AND (&80) on integers.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the (sub)expression to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the value, selected by the type in A
Xthe next unconsumed operator token (lookahead)
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed text
9B72 .eval_and←1← 9B29 JSR
JSR eval_relational ; Evaluate a relational operand
9B75 .eval_and_loop←1← 9B9A BNE
CPX #&80 ; Apply AND only if the next operator is AND
9B77 BEQ do_and ; yes
9B79 RTS ; no: return
9B7A .do_and←1← 9B77 BEQ
TAY ; Coerce the left operand to integer
9B7B JSR coerce_to_integer ; coerce it
9B7E JSR stack_integer ; stack it
9B81 JSR eval_relational ; evaluate the right operand
9B84 TAY ; ensure it is integer
9B85 JSR coerce_to_integer ; coerce it
9B88 LDY #3 ; Four bytes
9B8A .and_byte_loop←1← 9B93 BPL
LDA (zp_stack_ptr),y ; stacked byte
9B8C AND zp_iwa,y ; AND with IWA
9B8F STA zp_iwa,y ; store back
9B92 DEY ; next byte
9B93 BPL and_byte_loop ; until all four AND-ed
9B95 JSR drop_stack_integer ; Drop the stacked operand
9B98 LDA #&40 ; Result type = integer
9B9A BNE eval_and_loop ; loop for further AND
fall through ↓

Evaluator level 5: < <= = >= > <>

The relational operators, yielding TRUE (-1) or FALSE (0).

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the (sub)expression to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the value, selected by the type in A
Xthe next unconsumed operator token (lookahead)
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed text
9B9C .eval_relational←2← 9B72 JSR← 9B81 JSR
JSR eval_add_sub ; Evaluate a + - operand
9B9F CPX #'?' ; next token past '>'?
9BA1 BCS return_18 ; yes: not a comparison, return
9BA3 CPX #'<' ; before '<'?
9BA5 BCS rel_lt_family ; a relational operator: handle it
9BA7 .return_18←1← 9BA1 BCS
RTS ; not a comparison: return
9BA8 .rel_lt_family←1← 9BA5 BCS
BEQ rel_lt ; '<' family?
9BAA CPX #'>' ; '>' family?
9BAC BEQ rel_gt ; yes
9BAE TAX ; '=': compare for equality
9BAF JSR eval_and_compare ; evaluate the right operand and compare
9BB2 BNE store_bool ; not equal: result FALSE (0)
9BB4 .rel_true←6← 9BD0 BCC← 9BD9 BEQ← 9BDB BCC← 9BE4 BNE← 9BF6 BCS← 9BFF BCS
DEY ; equal: result TRUE (Y = &FF)
9BB5 .store_bool←7← 9BB2 BNE← 9BD2 BCS← 9BDD BCS← 9BE6 BEQ← 9BF4 BEQ← 9BF8 BCC← 9C01 BCC
STY zp_iwa ; Store 0/-1 in all four IWA bytes
9BB7 STY zp_iwa_1 ; byte 1,
9BB9 STY zp_iwa_2 ; byte 2,
9BBB STY zp_iwa_3 ; byte 3
9BBD LDA #&40 ; Result type = integer
9BBF RTS ; Return
9BC0 .rel_lt←1← 9BA8 BEQ
TAX ; '<' family: discard the operator
9BC1 LDY zp_text_ptr2_off ; Peek at the next source character
9BC3 LDA (zp_text_ptr2),y ; read it
9BC5 CMP #'=' ; '='? (<=)
9BC7 BEQ rel_le ; yes
9BC9 CMP #'>' ; '>'? (<>)
9BCB BEQ rel_ne ; yes
9BCD JSR compare_values ; plain "<": evaluate and compare
9BD0 BCC rel_true ; less: TRUE
9BD2 BCS store_bool ; not less: FALSE
9BD4 .rel_le←1← 9BC7 BEQ
INC zp_text_ptr2_off ; '<=': step past the '='
9BD6 JSR compare_values ; evaluate and compare
9BD9 BEQ rel_true ; equal: TRUE
9BDB BCC rel_true ; less: TRUE
9BDD BCS store_bool ; greater: FALSE
9BDF .rel_ne←1← 9BCB BEQ
INC zp_text_ptr2_off ; '<>': step past the '>'
9BE1 JSR compare_values ; evaluate and compare
9BE4 BNE rel_true ; unequal: TRUE
9BE6 BEQ store_bool ; equal: FALSE
9BE8 .rel_gt←1← 9BAC BEQ
TAX ; '>' family: discard the operator
9BE9 LDY zp_text_ptr2_off ; Peek at the next source character
9BEB LDA (zp_text_ptr2),y ; read it
9BED CMP #'=' ; '='? (>=)
9BEF BEQ rel_ge ; yes
9BF1 JSR compare_values ; plain ">": evaluate and compare
9BF4 BEQ store_bool ; equal: FALSE
9BF6 BCS rel_true ; greater: TRUE
9BF8 BCC store_bool ; less: FALSE
9BFA .rel_ge←1← 9BEF BEQ
INC zp_text_ptr2_off ; '>=': step past the '='
9BFC JSR compare_values ; evaluate and compare
9BFF BCS rel_true ; greater or equal: TRUE
9C01 BCC store_bool ; less: FALSE
9C03 .string_too_long←2← 9C27 BCS← B0FB JMP
BRK ; String too long error
9C04 EQUB &13
9C05 EQUS "String too long"
9C14 EQUB &00
9C15 .relstr_stack_loop←1← 9C4F BEQ
JSR stack_string ; Stack the left string
9C18 JSR eval_power ; Evaluate the right operand
9C1B TAY ; type
9C1C BNE arith_type_error ; number: Type mismatch
9C1E CLC ; New length = left + right
9C1F STX zp_general ; save the pending operator (X)
9C21 LDY #0 ; index the stacked left length
9C23 LDA (zp_stack_ptr),y ; left length
9C25 ADC zp_strbuf_len ; plus the right length
9C27 BCS string_too_long ; over 255: error
9C29 TAX ; Save the new length
9C2A PHA ; keep it on the stack
9C2B LDY zp_strbuf_len ; Move the right string up
9C2D .relstr_cmp_loop←1← 9C35 BNE
LDA strbuf_base,y ; right string byte (at Y)
9C30 STA strbuf_base,x ; to the new tail (at X)
9C33 DEX ; back one destination
9C34 DEY ; back one source
9C35 BNE relstr_cmp_loop ; loop
9C37 JSR unstack_string ; Prepend the left string
9C3A PLA ; Set the new length
9C3B STA zp_strbuf_len ; store it
9C3D LDX zp_general ; restore the pending operator
9C3F TYA ; string result
9C40 BEQ addsub_check ; loop for further + or -
fall through ↓

Evaluator level 4: + -

Addition and subtraction (numeric, or string concatenation).

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the (sub)expression to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the value, selected by the type in A
Xthe next unconsumed operator token (lookahead)
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed text
9C42 .eval_add_sub←4← 9A53 JSR← 9AA5 JSR← 9AEA JSR← 9B9C JSR
JSR eval_mul_div ; Evaluate a * / DIV MOD operand
9C45 .addsub_check←4← 9C40 BEQ← 9C82 BCC← 9C86 BCS← 9CA5 BNE
CPX #'+' ; Apply + or - if that is the next operator
9C47 BEQ do_add ; yes: addition
9C49 CPX #'-' ; next operator "-"?
9C4B BEQ sub_dispatch ; yes: subtraction
9C4D RTS ; neither: expression complete
9C4E .do_add←1← 9C47 BEQ
TAY ; Addition: type of the left operand
9C4F BEQ relstr_stack_loop ; string: concatenate
9C51 BMI add_real_left ; real: handle as float add
9C53 JSR mul_stack_eval ; integer: stack it and evaluate the right operand
9C56 TAY ; Type of the right operand
9C57 BEQ arith_type_error ; string: Type mismatch
9C59 BMI add_int_real ; real: convert the left integer to float
fall through ↓

Integer add

IWA = IWA + the integer operand on the BASIC stack.

On EntryZP_IWA (&2A)one 32-bit operand
(ZP_STACK_PTR) (&04)the other operand on the BASIC stack
X4
On ExitZP_IWAthe sum
9C5B .iwa_add
LDY #0 ; int + int: add the 32-bit stacked value to IWA
9C5D CLC ; ...byte 0
9C5E LDA (zp_stack_ptr),y ; stacked low,
9C60 ADC zp_iwa ; + IWA low,
9C62 STA zp_iwa ; store,
9C64 INY ; ...byte 1
9C65 LDA (zp_stack_ptr),y ; stacked,
9C67 ADC zp_iwa_1 ; + IWA &2B,
9C69 STA zp_iwa_1 ; store,
9C6B INY ; ...byte 2
9C6C LDA (zp_stack_ptr),y ; stacked,
9C6E ADC zp_iwa_2 ; + IWA &2C,
9C70 STA zp_iwa_2 ; store,
9C72 INY ; ...byte 3
9C73 LDA (zp_stack_ptr),y ; stacked,
9C75 ADC zp_iwa_3 ; + IWA &2D (high)
9C77 .iadd_store_drop←1← 9CDE JMP
STA zp_iwa_3 ; store the high byte
9C79 CLC ; Drop the operand (stack += 4),
9C7A LDA zp_stack_ptr ; low byte,
9C7C ADC #4 ; + 4,
9C7E STA zp_stack_ptr ; store low,
9C80 LDA #&40 ; Result type = integer
9C82 BCC addsub_check ; loop for further + or -
9C84 INC zp_stack_ptr_1 ; carry into the high byte,
9C86 BCS addsub_check ; back for a further + or -
9C88 .arith_type_error←6← 9C1C BNE← 9C57 BEQ← 9C92 BEQ← 9CB6 BEQ← 9CBE BEQ← 9CE8 BEQ
JMP err_type_mismatch ; Type mismatch error
9C8B .add_real_left←1← 9C51 BMI
JSR stack_real ; Left real: stack it, evaluate the right operand
9C8E JSR eval_mul_div ; evaluate the right operand
9C91 TAY ; Type of the right operand
9C92 BEQ arith_type_error ; string: Type mismatch
9C94 STX zp_var_type ; remember it for later
9C96 BMI add_floats ; real: no conversion needed
9C98 JSR int_to_fwa ; integer: convert the right operand to float
9C9B .add_floats←2← 9C96 BMI← 9CB2 JMP
JSR unstack_real ; Pop the stacked real as the fp operand
9C9E JSR fwa_add_var ; FWA = left + right
9CA1 .arith_result_loop←2← 9CF7 JMP← 9D0B JMP
LDX zp_var_type ; Restore the next character
9CA3 LDA #&ff ; Result type = real
9CA5 BNE addsub_check ; loop for further + or -
9CA7 .add_int_real←1← 9C59 BMI
STX zp_var_type ; Left int, right real: save the operator
9CA9 JSR unstack_integer ; pop the stacked left integer
9CAC JSR stack_real ; stack the right real
9CAF JSR int_to_fwa ; convert the left integer to float
9CB2 JMP add_floats ; then do float + float
9CB5 .sub_dispatch←1← 9C4B BEQ
TAY ; Subtraction: type of the left operand
9CB6 BEQ arith_type_error ; string: Type mismatch
9CB8 BMI sub_real_left ; real: handle as float subtract
9CBA JSR mul_stack_eval ; integer: stack it, evaluate the right operand
9CBD TAY ; Type of the right operand
9CBE BEQ arith_type_error ; string: Type mismatch
9CC0 BMI sub_int_real ; real: convert and subtract as floats
fall through ↓

Reverse integer subtract

IWA = stacked operand - IWA.

On EntryZP_IWA (&2A)the subtrahend
(ZP_STACK_PTR) (&04)the minuend on the BASIC stack
X4
On ExitZP_IWAoperand - IWA
9CC2 .iwa_rsub
SEC ; int - int: stacked value minus IWA, byte 0
9CC3 LDY #0 ; from offset 0,
9CC5 LDA (zp_stack_ptr),y ; stacked low,
9CC7 SBC zp_iwa ; - IWA low,
9CC9 STA zp_iwa ; store,
9CCB INY ; ...byte 1
9CCC LDA (zp_stack_ptr),y ; stacked,
9CCE SBC zp_iwa_1 ; - IWA &2B,
9CD0 STA zp_iwa_1 ; store,
9CD2 INY ; ...byte 2
9CD3 LDA (zp_stack_ptr),y ; stacked,
9CD5 SBC zp_iwa_2 ; - IWA &2C,
9CD7 STA zp_iwa_2 ; store,
9CD9 INY ; ...byte 3
9CDA LDA (zp_stack_ptr),y ; stacked,
9CDC SBC zp_iwa_3 ; - IWA &2D (high)
9CDE JMP iadd_store_drop ; store byte 3, drop the operand and loop
9CE1 .sub_real_left←1← 9CB8 BMI
JSR stack_real ; Left real: stack it, evaluate the right operand
9CE4 JSR eval_mul_div ; evaluate the right operand
9CE7 TAY ; Type of the right operand
9CE8 BEQ arith_type_error ; string: Type mismatch
9CEA STX zp_var_type ; remember it
9CEC BMI sub_floats ; real: no conversion needed
9CEE JSR int_to_fwa ; integer: convert the right operand to float
9CF1 .sub_floats←1← 9CEC BMI
JSR unstack_real ; Pop the stacked real as the fp operand
9CF4 JSR fwa_rsub_var ; FWA = left - right
9CF7 JMP arith_result_loop ; set result type and loop
9CFA .sub_int_real←1← 9CC0 BMI
STX zp_var_type ; Left int, right real: save the operator
9CFC JSR unstack_integer ; pop the stacked left integer
9CFF JSR stack_real ; stack the right real
9D02 JSR int_to_fwa ; convert the left integer to float
9D05 JSR unstack_real ; pop the stacked right real
9D08 JSR fwa_sub_var ; FWA = left - right
9D0B JMP arith_result_loop ; set result type and loop
9D0E .mul_conv_right←3← 9D60 BNE← 9D67 BNE← 9D6B BMI
JSR int_to_fwa ; Convert the right integer to float
9D11 .mul_conv_both←1← 9D5A BMI
JSR unstack_integer ; Pop the stacked left integer
9D14 JSR stack_real ; stack the right real
9D17 JSR int_to_fwa ; convert the left integer to float
9D1A JMP mul_floats ; then do float * float
9D1D .mul_conv_left←3← 9D45 BNE← 9D4C BNE← 9D50 BMI
JSR int_to_fwa ; Convert the left integer to float
9D20 .mul_real_left←1← 9D3F BMI
JSR stack_real ; Stack the left real
9D23 JSR eval_power ; evaluate the next (^ level) operand
9D26 STX zp_var_type ; remember the operand type
9D28 TAY ; test the operand type
9D29 JSR ensure_real ; ensure the operand is real
9D2C .mul_floats←1← 9D1A JMP
JSR unstack_real ; Pop the stacked left real
9D2F JSR fwa_mul_var ; FWA = left * right
9D32 LDA #&ff ; real result
9D34 LDX zp_var_type ; restore the operator
9D36 JMP muldiv_check ; loop for further * / DIV MOD
9D39 .mul_type_error←2← 9D3D BEQ← 9D58 BEQ
JMP err_type_mismatch ; String operand: Type mismatch
9D3C .mul_dispatch←1← 9DCB JMP
TAY ; Left operand type
9D3D BEQ mul_type_error ; string: Type mismatch
9D3F BMI mul_real_left ; real: floating-point multiply
9D41 LDA zp_iwa_3 ; Integer: does it fit signed 16 bits?
9D43 CMP zp_iwa_2 ; byte 3 == byte 2 (sign-extended)?
9D45 BNE mul_conv_left ; no: floating-point multiply
9D47 TAY ; high word zero (positive)?
9D48 BEQ mul_right_signed ; yes
9D4A CMP #&ff ; high word all ones (negative)?
9D4C BNE mul_conv_left ; no: floating-point multiply
9D4E .mul_right_signed←1← 9D48 BEQ
EOR zp_iwa_1 ; sign consistent with bit 15?
9D50 BMI mul_conv_left ; no: floating-point multiply
9D52 JSR pow_stack_eval ; Stack it, evaluate the right operand
9D55 STX zp_var_type ; remember the operator
9D57 TAY ; right operand type
9D58 BEQ mul_type_error ; string: Type mismatch
9D5A BMI mul_conv_both ; real: floating-point multiply
9D5C LDA zp_iwa_3 ; fits signed 16 bits?
9D5E CMP zp_iwa_2 ; byte 3 == byte 2 (sign-extended)?
9D60 BNE mul_conv_right ; no: floating-point multiply
9D62 TAY ; positive?
9D63 BEQ mul_left_signed ; yes
9D65 CMP #&ff ; negative?
9D67 BNE mul_conv_right ; no: floating-point multiply
9D69 .mul_left_signed←1← 9D63 BEQ
EOR zp_iwa_1 ; sign consistent?
9D6B BMI mul_conv_right ; no: floating-point multiply
fall through ↓

Integer multiply

IWA = IWA * the stacked operand. A product wider than 16 bits is truncated to 16 significant bits.

On EntryZP_IWA (&2A)one factor
(ZP_STACK_PTR) (&04)the other factor on the BASIC stack
ZP_VAR_TYPE (&27)4
On ExitZP_IWAthe product
9D6D .iwa_mul
LDA zp_iwa_3 ; Save the right operand sign
9D6F PHA ; push it
9D70 JSR iwa_abs ; take |right|
9D73 LDX #&39 ; save it (via &39)
9D75 JSR iwa_store_zp ; store it as the multiplier
9D78 JSR unstack_integer ; unstack the left operand
9D7B PLA ; recover the right operand sign
9D7C EOR zp_iwa_3 ; product sign = sign XOR sign
9D7E STA zp_general ; (save it)
9D80 JSR iwa_abs ; take |left|
9D83 LDY #0 ; Clear the running product:
9D85 LDX #0 ; byte 1 (in X),
9D87 STY zp_fwb_m2 ; byte 2,
9D89 STY zp_fwb_m3 ; and byte 3
9D8B .iwamul_loop←1← 9DB2 BNE
LSR zp_fileblk_1 ; Shift the multiplier right: next bit
9D8D ROR zp_fileblk ; low byte; bit 0 -> carry
9D8F BCC iwamul_double ; bit clear: skip the add
9D91 CLC ; bit set: add the multiplicand
9D92 TYA ; byte 0
9D93 ADC zp_iwa ; + multiplicand byte 0,
9D95 TAY ; back to product byte 0
9D96 TXA ; byte 1
9D97 ADC zp_iwa_1 ; + byte 1,
9D99 TAX ; back to product byte 1
9D9A LDA zp_fwb_m2 ; byte 2
9D9C ADC zp_iwa_2 ; + byte 2,
9D9E STA zp_fwb_m2 ; back to product byte 2
9DA0 LDA zp_fwb_m3 ; byte 3
9DA2 ADC zp_iwa_3 ; + byte 3,
9DA4 STA zp_fwb_m3 ; back to product byte 3
9DA6 .iwamul_double←1← 9D8F BCC
ASL zp_iwa ; Shift the multiplicand left
9DA8 ROL zp_iwa_1 ; byte 1,
9DAA ROL zp_iwa_2 ; byte 2,
9DAC ROL zp_iwa_3 ; byte 3 (multiplicand now x2)
9DAE LDA zp_fileblk ; more multiplier bits?
9DB0 ORA zp_fileblk_1 ; OR in the high byte
9DB2 BNE iwamul_loop ; loop
9DB4 STY zp_fwb_exp ; store the product (low 2 bytes)
9DB6 STX zp_fwb_m1 ; and byte 1 (bytes 2-3 already in place)
9DB8 LDA zp_general ; product sign
9DBA PHP ; save its N flag
9DBB .iwamul_result←1← 9E07 JMP
LDX #&3d ; load the product into IWA
9DBD .iwamul_copy←1← 9E1A JMP
JSR iwa_load_zp ; copy &3D..&40 into IWA
9DC0 PLP ; Apply the sign
9DC1 BPL iwamul_restore_op ; positive: done
9DC3 JSR iwa_negate ; negative: negate the product
9DC6 .iwamul_restore_op←1← 9DC1 BPL
LDX zp_var_type ; restore the operator
9DC8 JMP muldiv_check ; loop for further * / DIV MOD
9DCB .iwamul_bounce←1← 9DD6 BEQ
JMP mul_dispatch ; bounce back to the multiply code

Stack the integer, then evaluate a * / DIV / MOD expression

Push the pending integer accumulator onto the arithmetic stack via stack_integer, then fall into eval_mul_div to evaluate the higher-precedence (^) operand and apply any * / DIV / MOD operators. A trampoline used when the caller already holds an integer that must be preserved across the multiply-level evaluation.

On EntryZP_IWA (&2A)integer value to push onto the stack
ZP_TEXT_PTR2 (&19/&1A)PtrB at the expression to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type (<0 float in fwa, >0 integer in iwa, 0 string)
ZP_IWA (&2A) / ZP_FWA (&2E) / STRING_WORK (&0600)the value, selected by the type
Xthe next unconsumed operator token
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed text
9DCE .mul_stack_eval←2← 9C53 JSR← 9CBA JSR
JSR stack_integer ; stack the operand, then multiply
fall through ↓

Evaluator level 3: * / DIV MOD

Multiplication, division and the integer DIV and MOD operators.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the (sub)expression to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the value, selected by the type in A
Xthe next unconsumed operator token (lookahead)
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed text
9DD1 .eval_mul_div←3← 9C42 JSR← 9C8E JSR← 9CE4 JSR
JSR eval_power ; Evaluate the higher level (^, level 2) operand
9DD4 .muldiv_check←3← 9D36 JMP← 9DC8 JMP← 9DFF BNE
CPX #'*' ; next operator "*"?
9DD6 BEQ iwamul_bounce ; yes: multiply
9DD8 CPX #'/' ; "/"?
9DDA BEQ do_divide ; yes: divide
9DDC CPX #&83 ; MOD token?
9DDE BEQ iwa_mod ; yes: integer remainder
9DE0 CPX #&81 ; DIV token?
9DE2 BEQ iwa_div ; yes: integer divide
9DE4 RTS ; no operator: return
9DE5 .do_divide←1← 9DDA BEQ
TAY ; Divide: ensure the left operand is real
9DE6 JSR ensure_real ; convert if integer
9DE9 JSR stack_real ; stack it
9DEC JSR eval_power ; evaluate the right operand
9DEF STX zp_var_type ; remember the operator
9DF1 TAY ; ensure the right operand is real
9DF2 JSR ensure_real ; convert if integer
9DF5 JSR unstack_real ; pop the left operand as the fp operand
9DF8 JSR fwa_rdiv_var ; FWA = left / right
9DFB LDX zp_var_type ; restore the operator
9DFD LDA #&ff ; real result
9DFF BNE muldiv_check ; loop for further * / DIV MOD
fall through ↓

Integer remainder

IWA = IWA MOD the integer operand. Raises "Division by zero" if the divisor is zero.

On EntryZP_IWA (&2A)the dividend
On ExitZP_IWAthe remainder
9E01 .iwa_mod←1← 9DDE BEQ
JSR iwa_divide ; MOD: divide
9E04 LDA zp_general_1 ; remainder takes the dividend sign
9E06 PHP ; save the sign
9E07 JMP iwamul_result ; load the remainder (&3D-&40) and apply the sign

Integer divide

IWA = IWA DIV the integer operand. Raises "Division by zero" if the divisor is zero.

On EntryZP_IWA (&2A)the dividend
On ExitZP_IWAthe quotient
9E0A .iwa_div←1← 9DE2 BEQ
JSR iwa_divide ; DIV: divide
9E0D ROL zp_fileblk ; Shift the quotient up by one
9E0F ROL zp_fileblk_1 ; through &3A,
9E11 ROL zp_fwb_sign ; &3B,
9E13 ROL zp_fwb_ovf ; &3C
9E15 BIT zp_general ; quotient sign
9E17 PHP ; save the sign flags
9E18 LDX #&39 ; load the quotient (&39-&3C)
9E1A JMP iwamul_copy ; ...and apply the sign

Stack the integer, then evaluate a ^ expression

Push the pending integer accumulator onto the arithmetic stack via stack_integer, then fall into eval_power to evaluate the next factor and apply any ^ operators. A trampoline used when the caller already holds an integer that must be preserved across the power-level evaluation.

On EntryZP_IWA (&2A)integer value to push onto the stack
ZP_TEXT_PTR2 (&19/&1A)PtrB at the expression to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type (<0 float in fwa, >0 integer in iwa, 0 string)
ZP_IWA (&2A) / ZP_FWA (&2E) / STRING_WORK (&0600)the value, selected by the type
Xthe next unconsumed operator token
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed text
9E1D .pow_stack_eval←2← 99C8 JSR← 9D52 JSR
JSR stack_integer ; Stack the integer, evaluate the next ^ operand
fall through ↓

Expression Level 2 - the ^ operator

Evaluate a factor, then for each ^ raise it to the power: an integer exponent uses repeated multiplication, otherwise x^y = x^int * exp(frac * ln x).

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the (sub)expression to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the value, selected by the type in A
Xthe next unconsumed operator token (lookahead)
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed text
9E20 .eval_power←4← 9C18 JSR← 9D23 JSR← 9DD1 JSR← 9DEC JSR
JSR eval_factor ; Evaluate the base
9E23 .pow_result←2← 9E57 BNE← 9E86 BNE
PHA ; Save the result type
9E24 .pow_op_loop←1← 9E2C BEQ
LDY zp_text_ptr2_off ; Next character
9E26 INC zp_text_ptr2_off ; advance the offset
9E28 LDA (zp_text_ptr2),y ; read it
9E2A CMP #' ' ; space?
9E2C BEQ pow_op_loop ; skip it
9E2E TAX ; keep the operator
9E2F PLA ; restore the type
9E30 CPX #'^' ; '^'?
9E32 BEQ pow_apply ; yes
9E34 RTS ; no: return
9E35 .pow_apply←1← 9E32 BEQ
TAY ; Ensure the base is real
9E36 JSR ensure_real ; convert if integer
9E39 JSR stack_real ; stack the base
9E3C JSR eval_real ; evaluate the exponent as a real
9E3F LDA zp_fwa_exp ; Exponent magnitude
9E41 CMP #&87 ; large (>= 2^7)?
9E43 BCS pow_large ; yes: use exp(y*ln x)
9E45 JSR fp_split_int_frac ; Split into integer and fractional parts
9E48 BNE pow_frac ; fractional part nonzero?
9E4A JSR unstack_real ; no: integer power - unstack the base
9E4D JSR fwa_unpack_var ; load it into FWA
9E50 LDA zp_int_exp ; integer exponent
9E52 JSR fwa_int_power ; FWA = base ^ int
9E55 LDA #&ff ; real result
9E57 BNE pow_result ; continue
9E59 .pow_frac←1← 9E48 BNE
JSR fwa_pack_temp3 ; Fractional exponent: save the fraction
9E5C LDA zp_stack_ptr ; point at the stacked base
9E5E STA zp_fp_ptr ; low byte
9E60 LDA zp_stack_ptr_1 ; high byte
9E62 STA zp_fp_ptr_1 ; set the fp pointer
9E64 JSR fwa_unpack_var ; load the base
9E67 LDA zp_int_exp ; integer part of the exponent
9E69 JSR fwa_int_power ; FWA = base ^ int
9E6C .pow_combine←1← 9E8E BNE
JSR fwa_pack_temp2 ; save base^int in TEMP2
9E6F JSR unstack_real ; unstack the base
9E72 JSR fwa_unpack_var ; load it into FWA
9E75 JSR ln_compute ; ln(base)
9E78 JSR mul_by_temp3 ; times the fractional exponent
9E7B JSR exp_compute ; exp(that) = base^frac
9E7E JSR point_fp_temp2 ; point at base^int
9E81 JSR fwa_mul_var ; FWA = base^int * base^frac
9E84 LDA #&ff ; real result
9E86 BNE pow_result ; continue
9E88 .pow_large←1← 9E43 BCS
JSR fwa_pack_temp3 ; Large exponent: x^y = exp(y * ln x)
9E8B JSR fwa_set_one ; base^int = 1 (no integer part)
9E8E BNE pow_combine ; evaluate it
9E90 .hex_convert←1← 9F07 BMI
TYA ; Real?
9E91 BPL hex_expand ; no
9E93 JSR fwa_to_int ; convert to integer
9E96 .hex_expand←1← 9E91 BPL
LDX #0 ; Expand 4 bytes into 8 nibbles
9E98 LDY #0 ; byte index = 0
9E9A .hex_expand_loop←1← 9EAE BNE
LDA zp_iwa,y ; Byte
9E9D PHA ; save it
9E9E AND #&0f ; low nibble
9EA0 STA zp_fwb_m2,x ; store it
9EA2 PLA ; high nibble
9EA3 LSR ; shift the high nibble down,
9EA4 LSR ; (continued)
9EA5 LSR ; (continued)
9EA6 LSR ; (continued)
9EA7 INX ; next nibble slot
9EA8 STA zp_fwb_m2,x ; store it
9EAA INX ; next nibble slot
9EAB INY ; next byte
9EAC CPY #4 ; all four bytes?
9EAE BNE hex_expand_loop ; no: continue
9EB0 .hex_skip_zeros←1← 9EB5 BEQ
DEX ; Skip leading zero nibbles
9EB1 BEQ hex_digit_loop ; all zero: output one zero
9EB3 LDA zp_fwb_m2,x ; this nibble zero?
9EB5 BEQ hex_skip_zeros ; yes: skip it
9EB7 .hex_digit_loop←2← 9EB1 BEQ← 9EC5 BPL
LDA zp_fwb_m2,x ; Next nibble
9EB9 CMP #&0a ; above 9?
9EBB BCC hex_to_ascii ; no
9EBD ADC #6 ; adjust for A-F
9EBF .hex_to_ascii←1← 9EBB BCC
ADC #'0' ; to ASCII
9EC1 JSR output_char ; output the digit
9EC4 DEX ; next
9EC5 BPL hex_digit_loop ; loop
9EC7 RTS ; Return
9EC8 .num_sign←1← 9F12 BNE
BPL num_normalize_loop ; positive?
9ECA LDA #'-' ; negative: output '-'
9ECC STA zp_fwa_sign ; also clears the sign bit (bit 7 = 0)
9ECE JSR output_char ; print it
9ED1 .num_normalize_loop←3← 9EC8 BPL← 9EDC JMP← 9F36 JMP
LDA zp_fwa_exp ; Exponent
9ED3 CMP #&81 ; >= 1?
9ED5 BCS nta_check_mag ; yes: output the integer part
9ED7 JSR fwa_mul10 ; < 1: multiply by 10
9EDA DEC zp_dec_exp ; decrement the decimal exponent
9EDC JMP num_normalize_loop ; loop

Convert the current value to an ASCII number

Convert the integer (IWA) or real (FWA) value to an ASCII string in the string work area, in decimal or hex per the radix flag and the @% print format. Underlies PRINT and STR$.

On EntryZP_PRINT_FLAG (&15)0 for decimal, -1 for hexadecimal
@% (&0400)print format fields
Y&FF
On ExitSTRING WORK AREA (&0600)the ASCII result
ZP_STRBUF_LEN (&36)length of the result
9EDF .number_to_ascii←2← 8DFB JSR← B0B9 JSR
LDX resint_at_2 ; Get the @% format byte
9EE2 CPX #3 ; valid (< 3)?
9EE4 BCC nta_store_format ; yes: use it
9EE6 LDX #0 ; invalid: use General format
9EE8 .nta_store_format←1← 9EE4 BCC
STX zp_general ; store the format type
9EEA LDA resint_at_1 ; digit count from @%
9EED BEQ nta_check_fixed ; zero: check the format
9EEF CMP #&0a ; >= 10 digits?
9EF1 BCS nta_default_digits ; yes: cap at 10
9EF3 BCC nta_store_count ; use the specified count
9EF5 .nta_check_fixed←1← 9EED BEQ
CPX #2 ; fixed format?
9EF7 BEQ nta_store_count ; yes: zero digits
9EF9 .nta_default_digits←2← 9EF1 BCS← B0B3 JSR
LDA #&0a ; default to 10 digits
9EFB .nta_store_count←2← 9EF3 BCC← 9EF7 BEQ
STA zp_general_1 ; store the digit count
9EFD STA zp_coeff_ptr_1 ; (copy)
9EFF LDA #0 ; output length = 0...
9F01 STA zp_strbuf_len ; (length = 0)
9F03 STA zp_dec_exp ; decimal exponent = 0 (&49)
9F05 BIT zp_print_flag ; hex mode (radix flag bit 7)?
9F07 BMI hex_convert ; yes: hex conversion
9F09 TYA ; an integer value?
9F0A BMI nta_sign ; already a real
9F0C JSR int_to_fwa ; convert the integer to a real
9F0F .nta_sign←1← 9F0A BMI
JSR fwa_sign ; sign of the value
9F12 BNE num_sign ; non-zero: format it
9F14 LDA zp_general ; zero, not General format?
9F16 BNE nta_output_zero ; fixed/exponential zero
9F18 LDA #'0' ; output a single '0'
9F1A JMP output_char ; ...and return
9F1D .nta_output_zero←1← 9F16 BNE
JMP nta_check_fixed2 ; output zero in fixed/exp format
9F20 .nta_set_one←1← 9F96 BCS
JSR fwa_set_one ; FWA = 1.0
9F23 BNE nta_count_up ; always taken (count a place)
9F25 .nta_check_mag←1← 9ED5 BCS
CMP #&84 ; exponent < 4 (value < 10)?
9F27 BCC nta_save_temp1 ; yes: ready to convert
9F29 BNE nta_div10 ; exponent != 4: too big, divide
9F2B LDA zp_fwa_m1 ; mantissa top byte
9F2D CMP #&a0 ; less than &A0 (value < 10)?
9F2F BCC nta_save_temp1 ; yes: ready to convert
9F31 .nta_div10←1← 9F29 BNE
JSR fwa_div10 ; FWA = FWA / 10
9F34 .nta_count_up←1← 9F23 BNE
INC zp_dec_exp ; count one decimal place up
9F36 JMP num_normalize_loop ; check the magnitude again
9F39 .nta_save_temp1←2← 9F27 BCC← 9F2F BCC
LDA zp_fwa_rnd ; Save FWA in TEMP1 (a fraction in [1,10)):
9F3B STA zp_var_type ; stash the rounding byte,
9F3D JSR fwa_pack_temp1 ; pack the mantissa
9F40 LDA zp_coeff_ptr_1 ; digit count
9F42 STA zp_general_1 ; (store)
9F44 LDX zp_general ; print format
9F46 CPX #2 ; not fixed format?
9F48 BNE nta_round_const ; do exponent/general
9F4A ADC zp_dec_exp ; fixed: digits + decimal exponent
9F4C BMI nta_zero ; negative: round to zero
9F4E STA zp_general_1 ; (store the digit count)
9F50 CMP #&0b ; > 10?
9F52 BCC nta_round_const ; no
9F54 LDA #&0a ; cap at 10
9F56 STA zp_general_1 ; (store)
9F58 LDA #0 ; switch to General
9F5A STA zp_general ; (store)
9F5C .nta_round_const←2← 9F48 BNE← 9F52 BCC
JSR fwa_clear ; Build a rounding constant 0.5e-n:
9F5F LDA #&a0 ; mantissa = &A0...
9F61 STA zp_fwa_m1 ; (MSB)
9F63 LDA #&83 ; exponent = &83 (0.5),
9F65 STA zp_fwa_exp ; (store)
9F67 LDX zp_general_1 ; shift it down by the digit count:
9F69 BEQ nta_point_temp1 ; no shift needed
9F6B .nta_round_loop←1← 9F6F BNE
JSR fwa_div10 ; rounding /= 10
9F6E DEX ; count
9F6F BNE nta_round_loop ; loop
9F71 .nta_point_temp1←1← 9F69 BEQ
JSR point_fp_temp1 ; point at the saved value (TEMP1)
9F74 JSR fwb_unpack_var ; FWB = the value
9F77 LDA zp_var_type ; the saved rnd byte...
9F79 STA zp_fwb_rnd ; (into FWB)
9F7B JSR fwa_add_fwb_raw ; add the rounding constant
9F7E .nta_renorm_loop←1← 9F90 BNE
LDA zp_fwa_exp ; Re-normalise to [1,10): exponent
9F80 CMP #&84 ; < 4?
9F82 BCS nta_renorm_mant ; in range
9F84 ROR zp_fwa_m1 ; shift the mantissa right:
9F86 ROR zp_fwa_m2 ; m2,
9F88 ROR zp_fwa_m3 ; m3,
9F8A ROR zp_fwa_m4 ; m4,
9F8C ROR zp_fwa_rnd ; rnd
9F8E INC zp_fwa_exp ; and bump the exponent
9F90 BNE nta_renorm_loop ; loop
9F92 .nta_renorm_mant←1← 9F82 BCS
LDA zp_fwa_m1 ; mantissa top byte
9F94 CMP #&a0 ; >= 10 after rounding?
9F96 BCS nta_set_one ; yes: re-divide
9F98 LDA zp_general_1 ; digit count
9F9A BNE nta_check_general ; non-zero
9F9C .nta_check_fixed2←1← 9F1D JMP
CMP #1 ; fixed format?
9F9E BEQ nta_digit_pos ; output the value
9FA0 .nta_zero←1← 9F4C BMI
JSR fwa_clear ; Clear FWA (zero / underflow):
9FA3 LDA #0 ; 0...
9FA5 STA zp_dec_exp ; decimal exponent = 0,
9FA7 LDA zp_coeff_ptr_1 ; original digit count...
9FA9 STA zp_general_1 ; (store)
9FAB INC zp_general_1 ; plus one
9FAD .nta_check_general←1← 9F9A BNE
LDA #1 ; General format?
9FAF CMP zp_general ; is the format General (1)?
9FB1 BEQ nta_digit_pos ; output the value
9FB3 LDY zp_dec_exp ; decimal exponent
9FB5 BMI nta_check_fixed3 ; negative: leading zeros
9FB7 CPY zp_general_1 ; within the digit count?
9FB9 BCS nta_digit_pos ; no: use E-notation
9FBB LDA #0 ; within range: clear the exponent,
9FBD STA zp_dec_exp ; (store)
9FBF INY ; one more digit
9FC0 TYA ; output the value
9FC1 BNE nta_digit_pos ; output the value
9FC3 .nta_check_fixed3←1← 9FB5 BMI
LDA zp_general ; fixed format?
9FC5 CMP #2 ; fixed format (2)?
9FC7 BEQ nta_leading_zero ; yes
9FC9 LDA #1 ; (General marker)
9FCB CPY #&ff ; far below: use E-notation
9FCD BNE nta_digit_pos ; not far below: output
9FCF .nta_leading_zero←1← 9FC7 BEQ
LDA #'0' ; leading '0'
9FD1 JSR output_char ; emit it
9FD4 LDA #'.' ; decimal '.'
9FD6 JSR output_char ; emit it
9FD9 LDA #'0' ; prepare '0'
9FDB .nta_frac_zeros_loop←1← 9FE2 BNE
INC zp_dec_exp ; leading zeros for the fraction:
9FDD BEQ nta_all_digits ; exponent reached 0: done
9FDF JSR output_char ; output a zero
9FE2 BNE nta_frac_zeros_loop ; loop
9FE4 .nta_all_digits←1← 9FDD BEQ
LDA #&80 ; all digits (no point yet)
9FE6 .nta_digit_pos←5← 9F9E BEQ← 9FB1 BEQ← 9FB9 BCS← 9FC1 BNE← 9FCD BNE
STA zp_coeff_ptr_1 ; digit position counter
9FE8 .nta_emit_loop←1← 9FF6 BNE
JSR output_top_digit ; Emit each digit:
9FEB DEC zp_coeff_ptr_1 ; at the decimal point?
9FED BNE nta_more_digits ; no
9FEF LDA #'.' ; emit '.'
9FF1 JSR output_char ; emit it
9FF4 .nta_more_digits←1← 9FED BNE
DEC zp_general_1 ; more digits?
9FF6 BNE nta_emit_loop ; loop
9FF8 LDY zp_general ; General format: trim trailing zeros
9FFA DEY ; general 1 (E)?
9FFB BEQ nta_output_e ; yes: go to the exponent
9FFD DEY ; general 2 (fixed)?
9FFE BEQ nta_check_exp ; fixed: skip trimming
A000 LDY zp_strbuf_len ; scan back from the end:
A002 .nta_trim_loop←1← A008 BEQ
DEY ; previous char
A003 LDA string_work,y ; a character
A006 CMP #'0' ; a '0'?
A008 BEQ nta_trim_loop ; yes: trim it
A00A CMP #'.' ; a '.'?
A00C BEQ nta_set_length ; trim it too
A00E INY ; keep this one
A00F .nta_set_length←1← A00C BEQ
STY zp_strbuf_len ; set the trimmed length
A011 .nta_check_exp←1← 9FFE BEQ
LDA zp_dec_exp ; a decimal exponent to print?
A013 BEQ return_19 ; no: done
A015 .nta_output_e←1← 9FFB BEQ
LDA #'E' ; output 'E'
A017 JSR output_char ; emit it
A01A LDA zp_dec_exp ; the exponent
A01C BPL nta_output_exp ; positive
A01E LDA #'-' ; negative: output '-'
A020 JSR output_char ; emit it
A023 SEC ; negate the exponent
A024 LDA #0 ; 0...
A026 SBC zp_dec_exp ; minus the exponent
A028 .nta_output_exp←1← A01C BPL
JSR output_byte_decimal ; output the exponent in decimal
A02B LDA zp_general ; General format?
A02D BEQ return_19 ; done
A02F LDA #' ' ; pad: a space
A031 LDY zp_dec_exp ; check the exponent sign
A033 BMI nta_field_pad ; negative: no pad
A035 JSR output_char ; output it
A038 .nta_field_pad←1← A033 BMI
CPX #0 ; any field width left?
A03A BNE return_19 ; done
A03C JMP output_char ; output a final space
A03F .return_19←3← A013 BEQ← A02D BEQ← A03A BNE
RTS ; Return

Output the leading decimal digit of FWA

Emit the integer part (top nibble of the mantissa MSB) as a decimal digit, mask it off, then multiply the remaining fraction by 10 ready for the next digit. The inner step of the real-to-ASCII conversion.

On EntryZP_FWA_M1 (&31)mantissa MSB; top nibble is the digit
On ExitSTRING_WORK (&0600)the digit appended
ZP_STRBUF_LEN (&36)incremented
ZP_FWA (&31-&35)fraction times ten for the next digit
A040 .output_top_digit←1← 9FE8 JSR
LDA zp_fwa_m1 ; Integer part: top nibble
A042 LSR ; shift the top nibble down,
A043 LSR ; (continued)
A044 LSR ; (continued)
A045 LSR ; (continued)
A046 JSR output_digit ; output it as a digit
A049 LDA zp_fwa_m1 ; mask off the integer part:
A04B AND #&0f ; keep the low nibble
A04D STA zp_fwa_m1 ; (store)
A04F JMP mant_mul10 ; multiply the fraction by 10

Output a byte (0-99) in decimal

Append a byte 0-99 to the output string as one or two decimal digits (a leading-zero tens digit is dropped) by repeated subtraction of ten.

On EntryAthe value 0-99 to output
On ExitSTRING_WORK (&0600)the digits appended
ZP_STRBUF_LEN (&36)advanced
Xthe tens count
A052 .output_byte_decimal←1← A028 JSR
LDX #&ff ; count the tens:
A054 SEC ; ready the subtract
A055 .obd_tens_loop←1← A058 BCS
INX ; subtract 10...
A056 SBC #&0a ; minus 10
A058 BCS obd_tens_loop ; until it goes negative
A05A ADC #&0a ; add 10 back (the units)
A05C PHA ; save the units
A05D TXA ; the tens digit
A05E BEQ obd_units ; no tens: skip
A060 JSR output_digit ; output the tens digit
A063 .obd_units←1← A05E BEQ
PLA ; units digit
fall through ↓

Output A as a decimal digit (A + "0")

Convert the digit value in A to ASCII ("0" + A) and append it via output_char.

On EntryAa digit value 0-9
On ExitSTRING_WORK (&0600)the digit appended
ZP_STRBUF_LEN (&36)incremented
Xpreserved
A064 .output_digit←2← A046 JSR← A060 JSR
ORA #'0' ; make it a digit ("0" + A)
fall through ↓

Append a character to the output string

Append the character in A to the end of the output string buffer and bump its length.

On EntryAthe character to append
ZP_STRBUF_LEN (&36)the current buffer length
On ExitSTRING_WORK (&0600)A stored at the old length
ZP_STRBUF_LEN (&36)incremented
Xpreserved
A066 .output_char←11← 9EC1 JSR← 9ECE JSR← 9F1A JMP← 9FD1 JSR← 9FD6 JSR← 9FDF JSR← 9FF1 JSR← A017 JSR← A020 JSR← A035 JSR← A03C JMP
STX zp_fwb_sign ; save X
A068 LDX zp_strbuf_len ; append at the end of the buffer
A06A STA string_work,x ; write it
A06D LDX zp_fwb_sign ; restore X
A06F INC zp_strbuf_len ; one longer
A071 RTS ; Return
A072 .make_real_result←2← A091 BCS← A095 BMI
CLC ; set the rounding byte and test the sign
A073 STX zp_fwa_rnd ; (rnd = 0)
A075 JSR fwa_sign ; test the sign
A078 LDA #&ff ; real result
A07A RTS ; Return

Parse an unsigned number at PtrB

Parse an unsigned decimal number at PtrB into the accumulator: digits, a "." and an "E" exponent, choosing integer or real form (a decimal point or exponent forces real). Decimal only - there is no "&" hex path here; "&" constants are read separately by factor_hex, and a non-digit first character yields 0. Shared by the expression decimal factor (eval_factor) and VAL (via ascii_to_number).

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the first digit
On ExitAresult type: <0 float in fwa, >0 integer in iwa
ZP_IWA / ZP_FWAthe parsed value
ZP_TEXT_PTR2_OFF (&1B)advanced past the number
A07B .parse_number←3← AC60 JSR← AC6B JSR← AE2A JSR
LDX #0 ; Clear FWA:
A07D STX zp_fwa_m1 ; mantissa byte 1 (top),
A07F STX zp_fwa_m2 ; byte 2,
A081 STX zp_fwa_m3 ; byte 3,
A083 STX zp_fwa_m4 ; byte 4,
A085 STX zp_fwa_rnd ; and the guard byte
A087 STX zp_dp_flag ; clear the decimal-point flag
A089 STX zp_dec_exp ; decimal exponent = 0
A08B CMP #'.' ; a leading decimal point?
A08D BEQ pn_dup_point ; yes
A08F CMP #':' ; not a digit (>= ':')?
A091 BCS make_real_result ; finish
A093 SBC #&2f ; convert to binary 0-9
A095 BMI make_real_result ; not a digit: finish
A097 STA zp_fwa_rnd ; store the digit
A099 .pn_loop←8← A0A6 BNE← A0BC BNE← A0C0 BCS← A0CF BCC← A0D3 BNE← A0D7 BNE← A0DB BNE← A0DF BNE
INY ; next character
A09A LDA (zp_text_ptr2),y ; read it
A09C CMP #'.' ; a decimal point?
A09E BNE pn_check_e ; no
A0A0 .pn_dup_point←1← A08D BEQ
LDA zp_dp_flag ; already had one?
A0A2 BNE pn_store_offset ; yes: end of the number
A0A4 INC zp_dp_flag ; set the decimal-point flag
A0A6 BNE pn_loop ; next char
A0A8 .pn_check_e←1← A09E BNE
CMP #'E' ; 'E' (exponent)?
A0AA BEQ pn_scan_e ; yes: scan the exponent
A0AC CMP #':' ; not a digit?
A0AE BCS pn_store_offset ; finish
A0B0 SBC #&2f ; convert to binary
A0B2 BCC pn_store_offset ; not a digit: finish
A0B4 LDX zp_fwa_m1 ; mantissa top byte
A0B6 CPX #&18 ; room for another digit?
A0B8 BCC pn_check_point ; yes: add it
A0BA LDX zp_dp_flag ; too big: had a decimal point?
A0BC BNE pn_loop ; yes: just skip the digit
A0BE INC zp_dec_exp ; no: bump the exponent and skip
A0C0 BCS pn_loop ; skip this over-large digit
A0C2 .pn_check_point←1← A0B8 BCC
LDX zp_dp_flag ; had a decimal point?
A0C4 BEQ pn_mant_x10 ; no
A0C6 DEC zp_dec_exp ; yes: decrement the exponent
A0C8 .pn_mant_x10←1← A0C4 BEQ
JSR mant_mul10 ; FWA mantissa *= 10
A0CB ADC zp_fwa_rnd ; add the digit to the low byte
A0CD STA zp_fwa_rnd ; store the new low mantissa byte
A0CF BCC pn_loop ; no carry: next digit
A0D1 INC zp_fwa_m4 ; carry up through the mantissa
A0D3 BNE pn_loop ; absorbed in byte 4: next digit
A0D5 INC zp_fwa_m3 ; wrapped: carry into byte 3,
A0D7 BNE pn_loop ; absorbed: next digit
A0D9 INC zp_fwa_m2 ; byte 2,
A0DB BNE pn_loop ; absorbed: next digit
A0DD INC zp_fwa_m1 ; byte 1 (top)
A0DF BNE pn_loop ; next digit
A0E1 .pn_scan_e←1← A0AA BEQ
JSR parse_exponent ; scan the E exponent
A0E4 ADC zp_dec_exp ; add to the decimal exponent
A0E6 STA zp_dec_exp ; store it back
A0E8 .pn_store_offset←3← A0A2 BNE← A0AE BCS← A0B2 BCC
STY zp_text_ptr2_off ; store the text offset
A0EA LDA zp_dec_exp ; any exponent or decimal point?
A0EC ORA zp_dp_flag ; combine with the decimal-point flag
A0EE BEQ pn_check_int ; no: return an integer
A0F0 JSR fwa_sign ; value zero?
A0F3 BEQ pn_real_result ; yes: done
A0F5 .pn_set_exp_loop←1← A127 BNE
LDA #&a8 ; Set the FWA exponent (40-bit mantissa)...
A0F7 STA zp_fwa_exp ; store the exponent
A0F9 LDA #0 ; zero for overflow and sign
A0FB STA zp_fwa_ovf ; clear overflow
A0FD STA zp_fwa_sign ; clear sign
A0FF JSR fwa_normalise ; normalise
A102 LDA zp_dec_exp ; apply the decimal exponent:
A104 BMI pn_exp_div ; negative: divide
A106 BEQ pn_round ; zero: done
A108 .pn_exp_mul_loop←1← A10D BNE
JSR fwa_mul10 ; positive: multiply by 10
A10B DEC zp_dec_exp ; one fewer power of ten to apply
A10D BNE pn_exp_mul_loop ; loop
A10F BEQ pn_round ; done
A111 .pn_exp_div←2← A104 BMI← A116 BNE
JSR fwa_div10 ; divide by 10
A114 INC zp_dec_exp ; count the exponent up toward zero
A116 BNE pn_exp_div ; loop
A118 .pn_round←2← A106 BEQ← A10F BEQ
JSR fwa_round ; round the result
A11B .pn_real_result←1← A0F3 BEQ
SEC ; real result
A11C LDA #&ff ; A = &FF: real result
A11E RTS ; Return (real)
A11F .pn_check_int←1← A0EE BEQ
LDA zp_fwa_m2 ; Integer: does it fit in 32 signed bits?
A121 STA zp_iwa_3 ; stash byte 2 as the IWA top byte
A123 AND #&80 ; isolate its sign bit
A125 ORA zp_fwa_m1 ; top byte set (too big)?
A127 BNE pn_set_exp_loop ; yes: use a real instead
A129 LDA zp_fwa_rnd ; Copy the mantissa to IWA:
A12B STA zp_iwa ; guard byte -> IWA low,
A12D LDA zp_fwa_m4 ; byte 4
A12F STA zp_iwa_1 ; -> IWA byte 1,
A131 LDA zp_fwa_m3 ; byte 3
A133 STA zp_iwa_2 ; -> IWA byte 2
A135 LDA #&40 ; integer result
A137 SEC ; carry set: a valid number
A138 RTS ; Return (integer)
A139 .pn_neg_exp_loop←1← A145 BEQ
JSR pe_skip_sign ; negative exponent: scan the digits
A13C EOR #&ff ; negate it
A13E SEC ; carry so caller adc completes the negate
A13F RTS ; Return

Parse the "E" exponent (optional sign, 1-2 digits)

Read the one- or two-digit decimal exponent after "E" at PtrB, returning its magnitude in A (0 if no digits). The sign is handled on a separate entry path; the caller adds the result to the FWA exponent.

On EntryZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
Yoffset of the "E" character
On ExitAthe exponent magnitude (0-99)
Yadvanced past the exponent digits
Cclear (ready for the caller to add it)
A140 .parse_exponent←1← A0E1 JSR
INY ; Scan exponent: next character
A141 LDA (zp_text_ptr2),y ; read it
A143 CMP #'-' ; '-'?
A145 BEQ pn_neg_exp_loop ; yes: negative exponent
A147 CMP #'+' ; '+'?
A149 BNE pe_digit ; no sign
A14B .pe_skip_sign←1← A139 JSR
INY ; skip the sign
A14C LDA (zp_text_ptr2),y ; read the digit after the sign
A14E .pe_digit←1← A149 BNE
CMP #':' ; a digit?
A150 BCS pe_no_exp ; no: exponent = 0
A152 SBC #&2f ; convert
A154 BCC pe_no_exp ; not a digit: exponent = 0
A156 STA zp_int_exp ; store the first exponent digit
A158 INY ; second digit?
A159 LDA (zp_text_ptr2),y ; read it
A15B CMP #':' ; above 9?
A15D BCS pe_one_digit ; one digit only
A15F SBC #&2f ; convert
A161 BCC pe_one_digit ; not a digit
A163 INY ; two digits: exp = d1*10 + d2
A164 STA zp_fp_temp ; save d2
A166 LDA zp_int_exp ; d1
A168 ASL ; d1 * 10...
A169 ASL ; (d1 * 4)
A16A ADC zp_int_exp ; + d1 (now * 5)
A16C ASL ; * 2 (now * 10)
A16D ADC zp_fp_temp ; + d2
A16F RTS ; Return the exponent
A170 .pe_one_digit←2← A15D BCS← A161 BCC
LDA zp_int_exp ; 1-digit exponent
A172 CLC ; carry clear for the caller ADC
A173 RTS ; Return
A174 .pe_no_exp←2← A150 BCS← A154 BCC
LDA #0 ; no exponent: 0
A176 CLC ; carry clear for the caller ADC
A177 RTS ; Return

Add FWB into FWA

Add the FWB mantissa (rnd, m4-m1) into the FWA mantissa with a single carry chain. Mantissa-only: exponents must already be aligned by the caller. Used by multiply, *10 and /10.

On EntryZP_FWA (&31-&35)mantissa + rnd of accumulator A
ZP_FWB (&3E-&42)mantissa + rnd of accumulator B
Cclear (it is the carry-in to the addition)
On ExitZP_FWA (&31-&35)the summed mantissa
Ccarry out of the mantissa MSB (overflow)
Xpreserved
Ypreserved
A178 .fwa_acc_fwb←2← A208 JSR← A64F JSR
LDA zp_fwa_rnd ; Add the mantissas: rounding byte
A17A ADC zp_fwb_rnd ; + FWB rounding
A17C STA zp_fwa_rnd ; (store)
A17E LDA zp_fwa_m4 ; m4
A180 ADC zp_fwb_m4 ; + FWB m4
A182 STA zp_fwa_m4 ; (store)
A184 LDA zp_fwa_m3 ; m3
A186 ADC zp_fwb_m3 ; - FWB m3
A188 STA zp_fwa_m3 ; (store)
A18A LDA zp_fwa_m2 ; m2
A18C ADC zp_fwb_m2 ; - FWB m2
A18E STA zp_fwa_m2 ; (store)
A190 LDA zp_fwa_m1 ; m1
A192 ADC zp_fwb_m1 ; - FWB m1
A194 STA zp_fwa_m1 ; (store)
A196 RTS ; Return (carry = overflow)

Multiply the FWA mantissa by 10

Multiply the FWA mantissa (m1-m4 and the rounding byte) by ten as x4 + x1 = x5 then x2, leaving the exponent untouched. Used by the number<->ASCII conversions, which track the decimal point separately.

On EntryZP_FWA (&31-&35)the mantissa to scale
On ExitZP_FWA (&31-&35)the mantissa times ten
Ccarry out of the mantissa MSB
Apreserved
Ypreserved
Xcorrupted (holds the original m4)
A197 .mant_mul10←2← A04F JMP← A0C8 JSR
PHA ; Save A
A198 LDX zp_fwa_m4 ; keep m4 in X
A19A LDA zp_fwa_m1 ; save the mantissa (m1..m3):
A19C PHA ; push m1,
A19D LDA zp_fwa_m2 ; m2,
A19F PHA ; push m2,
A1A0 LDA zp_fwa_m3 ; m3,
A1A2 PHA ; push m3 (m4 already in X)
A1A3 LDA zp_fwa_rnd ; x2: shift the mantissa left
A1A5 ASL ; shift the low byte left,
A1A6 ROL zp_fwa_m4 ; carrying up through m4,
A1A8 ROL zp_fwa_m3 ; m3,
A1AA ROL zp_fwa_m2 ; m2,
A1AC ROL zp_fwa_m1 ; m1 (mantissa now x2)
A1AE ASL ; x2 again (now x4)
A1AF ROL zp_fwa_m4 ; m4,
A1B1 ROL zp_fwa_m3 ; m3,
A1B3 ROL zp_fwa_m2 ; m2,
A1B5 ROL zp_fwa_m1 ; m1 (now x4)
A1B7 ADC zp_fwa_rnd ; add the saved original (x4 + x1 = x5): rnd
A1B9 STA zp_fwa_rnd ; (store)
A1BB TXA ; m4
A1BC ADC zp_fwa_m4 ; + original m4 (was in X)
A1BE STA zp_fwa_m4 ; (store)
A1C0 PLA ; m3
A1C1 ADC zp_fwa_m3 ; + original m3 (pulled)
A1C3 STA zp_fwa_m3 ; (store)
A1C5 PLA ; m2
A1C6 ADC zp_fwa_m2 ; + original m2 (pulled)
A1C8 STA zp_fwa_m2 ; (store)
A1CA PLA ; m1
A1CB ADC zp_fwa_m1 ; + original m1 -> kept in A
A1CD ASL zp_fwa_rnd ; x2 (now x10): shift left
A1CF ROL zp_fwa_m4 ; carrying up through m4,
A1D1 ROL zp_fwa_m3 ; m3,
A1D3 ROL zp_fwa_m2 ; m2,
A1D5 ROL ; m1 (still in A): mantissa now x10
A1D6 STA zp_fwa_m1 ; store m1
A1D8 PLA ; restore A
A1D9 RTS ; Return

Get the sign of the FP accumulator

Determine the sign of the floating-point accumulator by OR-ing the mantissa and rounding bytes (&31-&35) to test for zero, then reading the sign byte. Forces a clean zero (sign, exponent and overflow byte cleared) when the mantissa is empty.

On EntryZP_FWA (&2E-&35)the floating-point accumulator A
On ExitA+1 positive, 0 zero, &FF negative
Nset when FWA is negative
Zset when FWA is zero
Xpreserved
Ypreserved
A1DA .fwa_sign←17← 9F0F JSR← A075 JSR← A0F0 JSR← A405 JSR← A48E JMP← A50B JSR← A606 JSR← A6AD JSR← A6E7 JSR← A7B7 JSR← A801 JSR← A8DD JSR← A8F0 JSR← A90A JSR← AB7F JSR← AD77 JSR← AD7E JSR
LDA zp_fwa_m1 ; OR the mantissa and rounding bytes to test for zero
A1DC ORA zp_fwa_m2 ; (mantissa byte 2)
A1DE ORA zp_fwa_m3 ; (mantissa byte 3)
A1E0 ORA zp_fwa_m4 ; (mantissa byte 4)
A1E2 ORA zp_fwa_rnd ; (rounding) - a non-zero value is never all zero
A1E4 BEQ sign_force_zero ; All zero: clean up and return zero
A1E6 LDA zp_fwa_sign ; Non-zero: the sign is in bit 7 of the sign byte
A1E8 BNE return_20 ; Bit 7 set: negative, return with A < 0
A1EA LDA #1 ; Otherwise positive: return A = +1
A1EC RTS ; A and the flags now give the sign
A1ED .sign_force_zero←1← A1E4 BEQ
STA zp_fwa_sign ; Zero path: force a clean zero - sign,
A1EF STA zp_fwa_exp ; exponent,
A1F1 STA zp_fwa_ovf ; and overflow byte, then return A = 0
A1F3 .return_20←1← A1E8 BNE
RTS ; Return (shared)

FWA = FWA * 10

Multiply FWA by ten as x8 + x2: scale the exponent by eight, build x2 in FWB, and add. Left unnormalised and unrounded (the caller normalises). Uses FWB as scratch.

On EntryZP_FWA (&2E-&35)the value to scale
On ExitZP_FWA (&2E-&35)ten times FWA, unnormalised
ZP_FWB (&3B-&42)corrupted (scratch)
A1F4 .fwa_mul10←2← 9ED7 JSR← A108 JSR
CLC ; x*8: add 3 to the exponent
A1F5 LDA zp_fwa_exp ; exponent...
A1F7 ADC #3 ; + 3
A1F9 STA zp_fwa_exp ; (store)
A1FB BCC mul10_x8 ; no carry
A1FD INC zp_fwa_ovf ; carry into overflow
A1FF .mul10_x8←1← A1FB BCC
JSR fwb_copy_from_fwa ; FWB = x*8
A202 JSR fwb_div2 ; FWB = x*4
A205 JSR fwb_div2 ; FWB = x*2
fall through ↓

Add FWB into FWA with carry renormalisation

Add FWB into FWA via fwa_acc_fwb, then renormalise (fwa_carry_renorm): if the mantissa addition overflowed, shift FWA's mantissa right one bit and increment the exponent. Used repeatedly by the x10 / decimal-conversion routines to accumulate shifted terms into FWA.

On EntryZP_FWA (&2E-&35)floating-point accumulator A
ZP_FWB (&3B-&42)floating-point accumulator B, the term to add
On ExitZP_FWA (&2E-&35)FWA + FWB, renormalised (mantissa shifted right and exponent bumped on overflow)
Xpreserved
A208 .fwa_acc10←5← A25B JSR← A26A JSR← A284 JSR← A29C JSR← A5E0 JMP
JSR fwa_acc_fwb ; FWA = x8 + x2 = x*10
A20B .fwa_carry_renorm←1← A2BA JMP
BCC return_21 ; no overflow: done
A20D ROR zp_fwa_m1 ; Overflow: shift right, bump exponent: m1
A20F ROR zp_fwa_m2 ; m2
A211 ROR zp_fwa_m3 ; m3
A213 ROR zp_fwa_m4 ; m4
A215 ROR zp_fwa_rnd ; rounding
A217 INC zp_fwa_exp ; exponent + 1
A219 BNE return_21 ; done
A21B INC zp_fwa_ovf ; exponent overflow
A21D .return_21←2← A20B BCC← A219 BNE
RTS ; Return

FWB = FWA

Copy all ten bytes of FWA into FWB.

On EntryZP_FWA (&2E-&35)the floating-point accumulator A
On ExitZP_FWB (&3B-&42)a copy of FWA
Xpreserved
A21E .fwb_copy_from_fwa←5← 9A44 JSR← A1FF JSR← A23F JSR← A3F8 JSR← A6B2 JSR
LDA zp_fwa_sign ; Copy FWA's sign...
A220 STA zp_fwb_sign ; ...into FWB
A222 LDA zp_fwa_ovf ; Copy the overflow byte...
A224 STA zp_fwb_ovf ; ...into FWB
A226 LDA zp_fwa_exp ; Copy the exponent...
A228 STA zp_fwb_exp ; ...into FWB
A22A LDA zp_fwa_m1 ; Copy mantissa byte 1...
A22C STA zp_fwb_m1 ; ...into FWB
A22E LDA zp_fwa_m2 ; Copy mantissa byte 2...
A230 STA zp_fwb_m2 ; ...into FWB
A232 LDA zp_fwa_m3 ; Copy mantissa byte 3...
A234 STA zp_fwb_m3 ; ...into FWB
A236 LDA zp_fwa_m4 ; Copy mantissa byte 4...
A238 STA zp_fwb_m4 ; ...into FWB
A23A LDA zp_fwa_rnd ; Copy the rounding byte...
A23C STA zp_fwb_rnd ; ...into FWB
A23E RTS ; FWB is now a copy of FWA

FWB = FWA / 2

Copy FWA into FWB (fwb_copy_from_fwa), then fall into fwb_div2 to shift the FWB mantissa (including the rounding byte) right one bit. The exponent is left unchanged, so the result is de-normalised; used to build the progressively shifted terms in fwa_div10.

On EntryZP_FWA (&2E-&35)floating-point accumulator A
On ExitZP_FWB (&3B-&42)FWA with its mantissa shifted right one bit (rounding byte included)
Xpreserved
Cthe bit shifted out of the rounding byte
A23F .fwb_half_fwa←2← A258 JSR← A25E JSR
JSR fwb_copy_from_fwa ; FWB = FWA, then halve it
fall through ↓

Divide FWB by two

Shift the FWB mantissa right one bit (FWB = FWB / 2). Leaves the exponent alone, so the result is left de-normalised; used to align exponents before adding.

On EntryZP_FWB (&3B-&42)the floating-point accumulator B
On ExitZP_FWBmantissa shifted right one bit (rnd byte included)
Xpreserved
Cthe bit shifted out of the rnd byte
A242 .fwb_div2←5← A202 JSR← A205 JSR← A261 JSR← A264 JSR← A267 JSR
LSR zp_fwb_m1 ; Shift FWB right one bit (/2): mantissa MSB
A244 ROR zp_fwb_m2 ; byte 2
A246 ROR zp_fwb_m3 ; byte 3
A248 ROR zp_fwb_m4 ; byte 4
A24A ROR zp_fwb_rnd ; rounding byte
A24C RTS ; FWB halved

FWA = FWA / 10

Divide FWA by ten using the binary expansion of 1/10 (x/16 plus progressively shifted terms accumulated via FWB). Left unnormalised and unrounded (the caller normalises). Uses FWB as scratch.

On EntryZP_FWA (&2E-&35)the value to divide
On ExitZP_FWA (&2E-&35)FWA / 10, unnormalised
ZP_FWB (&3B-&42)corrupted (scratch)
; Divide FWA by ten. In binary 1/10 = 0.000110011001100...,
; so x/10 is x/16 plus a series of progressively shifted terms.
; Each term is built in FWB as a byte/bit-shifted copy of the
; mantissa and accumulated into FWA. The result is unnormalised
; and unrounded (the caller normalises).
A24D .fwa_div10←3← 9F31 JSR← 9F6B JSR← A111 JSR
SEC ; x/16: subtract 4 from the exponent
A24E LDA zp_fwa_exp ; (exponent)
A250 SBC #4 ; (- 4)
A252 STA zp_fwa_exp ; (store)
A254 BCS div10_step ; no borrow: continue
A256 DEC zp_fwa_ovf ; borrow into the overflow byte
A258 .div10_step←1← A254 BCS
JSR fwb_half_fwa ; FWB = x/2
A25B JSR fwa_acc10 ; accumulate a shifted term into FWA
A25E JSR fwb_half_fwa ; FWB = x/2 again
A261 JSR fwb_div2 ; FWB /= 2
A264 JSR fwb_div2 ; FWB /= 2
A267 JSR fwb_div2 ; FWB /= 2
A26A JSR fwa_acc10 ; accumulate the next term
A26D LDA #0 ; Form a byte-shifted copy in FWB: clear MSB
A26F STA zp_fwb_m1 ; (store)
A271 LDA zp_fwa_m1 ; mantissa m1...
A273 STA zp_fwb_m2 ; ...to FWB m2
A275 LDA zp_fwa_m2 ; m2...
A277 STA zp_fwb_m3 ; ...to m3
A279 LDA zp_fwa_m3 ; m3...
A27B STA zp_fwb_m4 ; ...to m4
A27D LDA zp_fwa_m4 ; m4...
A27F STA zp_fwb_rnd ; ...to the rounding byte
A281 LDA zp_fwa_rnd ; next mantissa bit...
A283 ROL ; ...into the carry
A284 JSR fwa_acc10 ; accumulate this term
A287 LDA #0 ; Form a copy shifted down two bytes: clear
A289 STA zp_fwb_m1 ; the top two mantissa bytes
A28B STA zp_fwb_m2 ; (store)
A28D LDA zp_fwa_m1 ; m1...
A28F STA zp_fwb_m3 ; ...to m3
A291 LDA zp_fwa_m2 ; m2...
A293 STA zp_fwb_m4 ; ...to m4
A295 LDA zp_fwa_m3 ; m3...
A297 STA zp_fwb_rnd ; ...to rounding
A299 LDA zp_fwa_m4 ; next bit...
A29B ROL ; ...into the carry
A29C JSR fwa_acc10 ; accumulate this term
A29F LDA zp_fwa_m2 ; continue propagating the shifted mantissa bits
A2A1 ROL ; ...into the carry
A2A2 LDA zp_fwa_m1 ; next bit...
fall through ↓

Add to the rounding byte and ripple the carry up

Add A (plus the carry-in) to the FWA rounding byte, then propagate any carry up through the mantissa (m4 -> m1). A carry out of the top renormalises the exponent (shift right, exponent + 1) and may set the overflow byte. Used to round the mantissa up.

On EntryAthe rounding increment to add to the rnd byte
Ccarry-in to the addition
ZP_FWA (&31-&35)the mantissa + rnd to round
On ExitZP_FWA (&30-&35)the rounded mantissa (exponent may step up)
Xpreserved
Ypreserved
A2A4 .fwa_round_carry←1← A666 JSR
ADC zp_fwa_rnd ; Add the round increment to the rounding byte
A2A6 STA zp_fwa_rnd ; Store it back
A2A8 BCC return_22 ; No carry out: done
A2AA INC zp_fwa_m4 ; Carry: bump mantissa byte 4
A2AC BNE return_22 ; No further carry: done
A2AE INC zp_fwa_m3 ; Ripple into byte 3
A2B0 BNE return_22 ; done if no carry
A2B2 INC zp_fwa_m2 ; into byte 2
A2B4 BNE return_22 ; done if no carry
A2B6 INC zp_fwa_m1 ; into byte 1 (the MSB)
A2B8 BNE return_22 ; done if no carry
A2BA JMP fwa_carry_renorm ; Carry out of the mantissa: renormalise (exponent up)
A2BD .return_22←5← A2A8 BCC← A2AC BNE← A2B0 BNE← A2B4 BNE← A2B8 BNE
RTS ; Return (shared)

Convert the integer accumulator to floating point

Convert the signed 32-bit integer in IWA to a normalised real in FWA: record the sign (negating IWA if needed), copy the magnitude into the mantissa with exponent 32, and normalise.

On EntryZP_IWA (&2A-&2D)the signed integer to convert
On ExitZP_FWA (&2E-&35)the value as a normalised real
ZP_IWAmade positive if it was negative
Xcorrupted
A2BE .int_to_fwa←12← 9301 JMP← 9A41 JSR← 9C98 JSR← 9CAF JSR← 9CEE JSR← 9D02 JSR← 9D0E JSR← 9D17 JSR← 9D1D JSR← 9F0C JSR← AF24 JSR← B4E6 JSR
LDX #0 ; Clear the rounding...
A2C0 STX zp_fwa_rnd ; (rnd = 0)
A2C2 STX zp_fwa_ovf ; ...and overflow bytes
A2C4 LDA zp_iwa_3 ; Sign of IWA (top byte)
A2C6 BPL itf_store_sign ; positive: sign byte = 0
A2C8 JSR iwa_negate ; negative: make it positive...
A2CB LDX #&ff ; ...and set the sign byte
A2CD .itf_store_sign←1← A2C6 BPL
STX zp_fwa_sign ; (store the sign)
A2CF LDA zp_iwa ; Copy IWA into the mantissa (MSB first):
A2D1 STA zp_fwa_m4 ; byte 0 -> m4
A2D3 LDA zp_iwa_1 ; read it
A2D5 STA zp_fwa_m3 ; byte 1 -> m3
A2D7 LDA zp_iwa_2 ; read it
A2D9 STA zp_fwa_m2 ; byte 2 -> m2
A2DB LDA zp_iwa_3 ; read it
A2DD STA zp_fwa_m1 ; byte 3 -> m1 (MSB)
A2DF LDA #&a0 ; Exponent &A0 (= 32): a 32-bit integer
A2E1 STA zp_fwa_exp ; (store)
A2E3 JMP fwa_normalise ; normalise the result
A2E6 .itf_zero←1← A30F BEQ
STA zp_fwa_sign ; Zero: clear sign,
A2E8 STA zp_fwa_exp ; exponent,
A2EA STA zp_fwa_ovf ; overflow
A2EC .return_23←4← A2F2 BEQ← A305 BMI← A315 BMI← A338 BMI
RTS ; Return

Convert a small (8-bit) integer in A to FWA

Convert the signed 8-bit integer in A to a normalised real in FWA (clears FWA, places the magnitude in the mantissa MSB with exponent 8, sets the sign, and normalises).

On EntryAthe signed 8-bit integer to convert
On ExitZP_FWA (&2E-&35)the value as a normalised real
A2ED .small_int_to_fwa←1← A852 JSR
PHA ; Small int to FWA: save it
A2EE JSR fwa_clear ; clear FWA
A2F1 PLA ; recover it
A2F2 BEQ return_23 ; zero: done
A2F4 BPL si8_set_mantissa ; positive
A2F6 STA zp_fwa_sign ; negative: set the sign...
A2F8 LDA #0 ; 0...
A2FA SEC ; minus the value:
A2FB SBC zp_fwa_sign ; ...and negate the value
A2FD .si8_set_mantissa←1← A2F4 BPL
STA zp_fwa_m1 ; value into the mantissa MSB
A2FF LDA #&88 ; Exponent &88 (= 8): an 8-bit value
A301 STA zp_fwa_exp ; (store; falls into normalise)
fall through ↓

Normalise FWA

Shift the FWA mantissa left until bit 7 of the MSB is set, decrementing the exponent by 8 per whole-byte shift and by 1 per bit, so the implied leading 1 is restored. An all-zero mantissa is forced to a clean zero. Underflow is handled via the overflow byte.

On EntryZP_FWA (&2E-&35)an unnormalised accumulator
On ExitZP_FWA (&2E-&35)normalised (mantissa MSB bit 7 set) or a clean zero
A303 .fwa_normalise←8← A0FF JSR← A2E3 JMP← A4B3 JMP← A5DC JMP← A602 JMP← A659 JSR← AA0E JSR← AF33 JSR
LDA zp_fwa_m1 ; Mantissa MSB...
A305 BMI return_23 ; Top bit already set: nothing to do
A307 ORA zp_fwa_m2 ; OR in the lower mantissa bytes (byte 2)
A309 ORA zp_fwa_m3 ; (byte 3)
A30B ORA zp_fwa_m4 ; (byte 4) to test the whole mantissa
A30D ORA zp_fwa_rnd ; Mantissa entirely zero: the value is zero
A30F BEQ itf_zero ; so jump away to handle the zero value
A311 LDA zp_fwa_exp ; Load the exponent to adjust as we shift
A313 .norm_byte_loop←2← A330 BCS← A334 BCC
LDY zp_fwa_m1 ; Shift up a whole byte while the MSB byte is zero
A315 BMI return_23 ; Bit 7 set: already normalised, return
A317 BNE norm_bit_loop ; MSB byte non-zero but bit 7 clear: bit-shift
A319 LDX zp_fwa_m2 ; Shift the mantissa up a byte: load m2
A31B STX zp_fwa_m1 ; store as m1
A31D LDX zp_fwa_m3 ; load m3
A31F STX zp_fwa_m2 ; store as m2
A321 LDX zp_fwa_m4 ; load m4
A323 STX zp_fwa_m3 ; store as m3
A325 LDX zp_fwa_rnd ; load the rounding byte
A327 STX zp_fwa_m4 ; store as m4
A329 STY zp_fwa_rnd ; rounding <- 0
A32B SEC ; Prepare to reduce the exponent
A32C SBC #8 ; each byte shift advances the exponent by 8
A32E STA zp_fwa_exp ; Store the reduced exponent
A330 BCS norm_byte_loop ; Loop to shift another byte if MSB still zero
A332 DEC zp_fwa_ovf ; Borrow into the overflow byte
A334 BCC norm_byte_loop ; Continue the byte-shift loop
A336 .norm_bit_check←2← A348 BCS← A34C BCC
LDY zp_fwa_m1 ; Bit-shift loop: re-check the MSB
A338 BMI return_23 ; Bit 7 set: normalised, return
A33A .norm_bit_loop←1← A317 BNE
ASL zp_fwa_rnd ; Then shift left one bit at a time to normalise
A33C ROL zp_fwa_m4 ; Shift the mantissa left one bit: byte 4
A33E ROL zp_fwa_m3 ; byte 3
A340 ROL zp_fwa_m2 ; byte 2
A342 ROL zp_fwa_m1 ; byte 1 (MSB)
A344 SBC #0 ; Reduce the exponent by one
A346 STA zp_fwa_exp ; Store it
A348 BCS norm_bit_check ; Loop until the MSB bit is set
A34A DEC zp_fwa_ovf ; Borrow into the overflow byte
A34C BCC norm_bit_check ; Continue the bit-shift loop
fall through ↓

Unpack a fp variable into FWB

Expand the packed five-byte float addressed by zp_fp_ptr into the unpacked FWB accumulator: split the sign out of the mantissa MSB, restore the implied leading 1 (unless the value is zero), and clear the overflow and rounding bytes.

On Entry(ZP_FP_PTR) (&4B/&4C)a packed five-byte float
On ExitZP_FWB (&3B-&42)the unpacked value
Xpreserved
A34E .fwb_unpack_var←7← 9A5F JSR← 9F74 JSR← A4D6 JSR← A500 JSR← A60B JSR← A6EC JSR← A9DF JSR
LDY #4 ; Copy the packed value, exponent last
A350 LDA (zp_fp_ptr),y ; Packed byte 4...
A352 STA zp_fwb_m4 ; ...mantissa byte 4
A354 DEY ; next byte
A355 LDA (zp_fp_ptr),y ; byte 3...
A357 STA zp_fwb_m3 ; ...mantissa byte 3
A359 DEY ; next byte
A35A LDA (zp_fp_ptr),y ; byte 2...
A35C STA zp_fwb_m2 ; ...mantissa byte 2
A35E DEY ; next byte
A35F LDA (zp_fp_ptr),y ; byte 1 (sign + mantissa MSB)...
A361 STA zp_fwb_sign ; ...into the sign byte
A363 DEY ; next byte
A364 STY zp_fwb_rnd ; Clear the rounding byte (Y=0)
A366 STY zp_fwb_ovf ; and the overflow byte
A368 LDA (zp_fp_ptr),y ; byte 0...
A36A STA zp_fwb_exp ; ...is the exponent
A36C ORA zp_fwb_sign ; Test for zero: OR exponent with the mantissa...
A36E ORA zp_fwb_m2 ; m2,
A370 ORA zp_fwb_m3 ; m3,
A372 ORA zp_fwb_m4 ; m4
A374 BEQ fwb_unpack_msb ; All zero: leave the mantissa MSB clear
A376 LDA zp_fwb_sign ; Non-zero: restore the implied leading 1...
A378 ORA #&80 ; set the top bit
A37A .fwb_unpack_msb←1← A374 BEQ
STA zp_fwb_m1 ; Store the true mantissa MSB
A37C RTS ; FWB now holds the unpacked value

Pack FWA into TEMP2

Point zp_fp_ptr at FP TEMP2 (&0471), then pack FWA into it via fwa_pack_var.

On EntryZP_FWA (&2E-&35)the value to store
On Exit(&0471)FWA packed into five bytes
ZP_FP_PTR (&4B/&4C)= &0471
Xpreserved
A37D .fwa_pack_temp2←2← 9E6C JSR← AA11 JSR
LDA #&71 ; Point at FP TEMP2 (&0471): low byte
A37F BNE pack_temp_tail ; join the common pack code
fall through ↓

Pack FWA into TEMP3

Point zp_fp_ptr at FP TEMP3 (&0476), then pack FWA into it via fwa_pack_var.

On EntryZP_FWA (&2E-&35)the value to store
On Exit(&0476)FWA packed into five bytes
ZP_FP_PTR (&4B/&4C)= &0476
Xpreserved
A381 .fwa_pack_temp3←6← 9E59 JSR← 9E88 JSR← A8EA JSR← A93C JSR← A9C3 JSR← AABE JSR
LDA #&76 ; Point at FP TEMP3 (&0476): low byte
A383 BNE pack_temp_tail ; join the common pack code
fall through ↓

Pack FWA into TEMP1

Point zp_fp_ptr at FP TEMP1 (&046C), then pack FWA into it via fwa_pack_var.

On EntryZP_FWA (&2E-&35)the value to store
On Exit(&046C)FWA packed into five bytes
ZP_FP_PTR (&4B/&4C)= &046C
Xpreserved
A385 .fwa_pack_temp1←9← 8D3C JSR← 9F3D JSR← A6A5 JSR← A7BE JSR← A84B JSR← A89B JSR← A9B1 JSR← A9D9 JSR← AB1F JSR
LDA #&6c ; Point at FP TEMP1 (&046C): low byte
A387 .pack_temp_tail←3← A37F BNE← A383 BNE← A835 JSR
STA zp_fp_ptr ; set the fp-variable pointer
A389 LDA #4 ; high byte &04
A38B STA zp_fp_ptr_1 ; then fall into fwa_pack_var
fall through ↓

Pack FWA into a fp variable

Compress the unpacked FWA accumulator into the packed five-byte form addressed by zp_fp_ptr: exponent, then the sign folded into bit 7 of the mantissa MSB (the implied leading 1 is dropped), then mantissa bytes 2-4.

On EntryZP_FWA (&2E-&35)the value to store
ZP_FP_PTR (&4B/&4C)where to write the five bytes
On Exit(ZP_FP_PTR)the packed five-byte float
ZP_FWA_SIGN (&2E)reduced to its sign bit
Xpreserved
A38D .fwa_pack_var←7← A4D9 JSR← A6CA JSR← A7CF JSR← A9B7 JSR← AA20 JSR← B860 JSR← B882 JSR
LDY #0 ; Write packed bytes from offset 0
A38F LDA zp_fwa_exp ; Packed byte 0 is the exponent
A391 STA (zp_fp_ptr),y ; (store it)
A393 INY ; next byte
A394 LDA zp_fwa_sign ; Take the sign byte
A396 AND #&80 ; Isolate the sign bit
A398 STA zp_fwa_sign ; keep only the sign bit
A39A LDA zp_fwa_m1 ; Mantissa MSB
A39C AND #&7f ; Drop the implied leading 1 from the mantissa MSB
A39E ORA zp_fwa_sign ; and fold the sign back into its bit 7
A3A0 STA (zp_fp_ptr),y ; Packed byte 1 = sign + mantissa MSB
A3A2 LDA zp_fwa_m2 ; Mantissa byte 2
A3A4 INY ; next byte
A3A5 STA (zp_fp_ptr),y ; Packed byte 2
A3A7 LDA zp_fwa_m3 ; Mantissa byte 3
A3A9 INY ; next byte
A3AA STA (zp_fp_ptr),y ; Packed byte 3
A3AC LDA zp_fwa_m4 ; Mantissa byte 4
A3AE INY ; next byte
A3AF STA (zp_fp_ptr),y ; Packed byte 4
A3B1 RTS ; The 5-byte packed value is stored

Unpack TEMP1 into FWA

Point zp_fp_ptr at FP TEMP1 (&046C), then unpack it into FWA via fwa_unpack_var.

On Entry(&046C)a packed five-byte float
On ExitZP_FWA (&2E-&35)the unpacked value
ZP_FP_PTR (&4B/&4C)= &046C
Xpreserved
A3B2 .fwa_unpack_temp1←2← AA35 JMP← BA36 JSR
JSR point_fp_temp1 ; Point at FP TEMP1, then unpack it into FWA
fall through ↓

Unpack a floating-point variable into FWA

Expand the packed five-byte float addressed by zp_fp_ptr into the unpacked FWA accumulator: split the sign out of the mantissa MSB, restore the implied leading 1 (unless the value is zero), and clear the overflow and rounding bytes.

On Entry(ZP_FP_PTR) (&4B/&4C)a packed five-byte float
On ExitZP_FWA (&2E-&35)the unpacked value
Xpreserved
A3B5 .fwa_unpack_var←10← 9A4A JSR← 9E4D JSR← 9E64 JSR← 9E72 JSR← A6B5 JSR← A8B2 JSR← A901 JSR← AA26 JSR← AAC9 JSR← B2EA JSR
LDY #4 ; Copy the packed value, exponent last
A3B7 LDA (zp_fp_ptr),y ; Packed byte 4...
A3B9 STA zp_fwa_m4 ; ...is mantissa byte 4
A3BB DEY ; next byte
A3BC LDA (zp_fp_ptr),y ; byte 3...
A3BE STA zp_fwa_m3 ; ...mantissa byte 3
A3C0 DEY ; next byte
A3C1 LDA (zp_fp_ptr),y ; byte 2...
A3C3 STA zp_fwa_m2 ; ...mantissa byte 2
A3C5 DEY ; next byte
A3C6 LDA (zp_fp_ptr),y ; byte 1 (sign + mantissa MSB)...
A3C8 STA zp_fwa_sign ; Packed mantissa MSB holds the sign in bit 7
A3CA DEY ; next byte
A3CB LDA (zp_fp_ptr),y ; byte 0...
A3CD STA zp_fwa_exp ; ...is the exponent
A3CF STY zp_fwa_rnd ; Clear the rounding and overflow bytes (Y=0)
A3D1 STY zp_fwa_ovf ; clear the overflow byte too
A3D3 ORA zp_fwa_sign ; A=exponent; OR the mantissa to test for zero
A3D5 ORA zp_fwa_m2 ; (mantissa byte 2)
A3D7 ORA zp_fwa_m3 ; (mantissa byte 3)
A3D9 ORA zp_fwa_m4 ; (mantissa byte 4)
A3DB BEQ fwa_unpack_msb ; All zero: leave the mantissa MSB clear
A3DD LDA zp_fwa_sign ; Non-zero: take the packed MSB (carrying the sign)
A3DF ORA #&80 ; Non-zero: restore the implied leading 1
A3E1 .fwa_unpack_msb←1← A3DB BEQ
STA zp_fwa_m1 ; Store the true mantissa MSB
A3E3 RTS ; FWA now holds the unpacked value

Convert the FP accumulator to an integer

Convert FWA to a 4-byte integer in IWA by denormalising (fwa_to_int2) and copying the mantissa into IWA. Truncates toward zero.

On EntryZP_FWA (&2E-&35)the real to convert
On ExitZP_IWA (&2A-&2D)the integer part of FWA
ZP_FWB (&3B-&42)corrupted (holds the fraction)
A3E4 .fwa_to_int←5← 92F4 JMP← 98C9 JSR← 9E93 JSR← AF36 JSR← B4C3 JSR
JSR fwa_to_int2 ; Denormalise FWA to a fixed integer
A3E7 .mantissa_to_iwa←1← AC95 JSR
LDA zp_fwa_m1 ; Copy the 32-bit mantissa into IWA: byte 3
A3E9 STA zp_iwa_3 ; store it
A3EB LDA zp_fwa_m2 ; byte 2
A3ED STA zp_iwa_2 ; store it
A3EF LDA zp_fwa_m3 ; byte 1
A3F1 STA zp_iwa_1 ; store it
A3F3 LDA zp_fwa_m4 ; byte 0
A3F5 STA zp_iwa ; store it
A3F7 RTS ; Return
A3F8 .fti_small←1← A400 BPL
JSR fwb_copy_from_fwa ; |x| < 1: FWB = FWA
A3FB JMP fwa_clear ; integer is zero

Denormalise FWA to a fixed-point integer

Shift the FWA mantissa so it represents the integer part at a fixed scale (exponent &A0), pushing the fractional bits out into FWB. The shared core of fwa_to_int and fp_split_int_frac; the caller reads the integer from the mantissa.

On EntryZP_FWA (&2E-&35)the real to convert
On ExitZP_FWA (&31-&34)the integer part in the mantissa
ZP_FWB (&3B-&42)the fractional bits shifted out
A3FE .fwa_to_int2←4← A3E4 JSR← A491 JSR← A9EE JSR← AC82 JSR
LDA zp_fwa_exp ; Exponent of FWA
A400 BPL fti_small ; < 1: result is zero
A402 JSR fwb_clear ; Clear the low-order extension (FWB)
A405 JSR fwa_sign ; Sign of FWA
A408 BNE fti2_shift_loop ; non-zero: denormalise
A40A BEQ fti2_positive ; zero: nothing to do
A40C .fti2_exp←2← A43A BEQ← A44E BNE
LDA zp_fwa_exp ; Exponent
A40E CMP #&a0 ; already a 32-bit integer (exp = +32)?
A410 BCS fti2_exp_over ; yes: check it fits
A412 CMP #&99 ; within a byte-shift of integer?
A414 BCS fti2_shift_loop ; yes: finish with bit shifts
A416 ADC #8 ; Fast path: shift right one byte (exp += 8)
A418 STA zp_fwa_exp ; (store)
A41A LDA zp_fwb_m3 ; shift the mantissa down a byte, low byte into FWB
A41C STA zp_fwb_m4 ; into fwb m4,
A41E LDA zp_fwb_m2 ; m2...
A420 STA zp_fwb_m3 ; into fwb m3,
A422 LDA zp_fwb_m1 ; m1...
A424 STA zp_fwb_m2 ; into fwb m2,
A426 LDA zp_fwa_m4 ; fwa m4...
A428 STA zp_fwb_m1 ; into fwb m1,
A42A LDA zp_fwa_m3 ; m3...
A42C STA zp_fwa_m4 ; into fwa m4,
A42E LDA zp_fwa_m2 ; m2...
A430 STA zp_fwa_m3 ; into fwa m3,
A432 LDA zp_fwa_m1 ; m1...
A434 STA zp_fwa_m2 ; into fwa m2,
A436 LDA #0 ; zero...
A438 STA zp_fwa_m1 ; into fwa m1 (top)
A43A BEQ fti2_exp ; loop
A43C .fti2_shift_loop←2← A408 BNE← A414 BCS
LSR zp_fwa_m1 ; Slow path: shift FWA:FWB right one bit
A43E ROR zp_fwa_m2 ; through fwa m2,
A440 ROR zp_fwa_m3 ; m3,
A442 ROR zp_fwa_m4 ; m4,
A444 ROR zp_fwb_m1 ; then fwb m1,
A446 ROR zp_fwb_m2 ; m2,
A448 ROR zp_fwb_m3 ; m3,
A44A ROR zp_fwb_m4 ; m4
A44C INC zp_fwa_exp ; exp += 1
A44E BNE fti2_exp ; loop until an exact integer
A450 .fti2_too_big←2← A466 BNE← A4C4 BEQ
JMP err_too_big ; Magnitude too large: Too big error

FWB = 0

Zero every byte of FWB. Exponent 0 is the special encoding for the value zero.

On ExitZP_FWB (&3B-&42)0.0
A0
Xpreserved
Ypreserved
A453 .fwb_clear←3← A402 JSR← A814 JSR← A93F JSR
LDA #0 ; Zero every byte of FWB
A455 STA zp_fwb_sign ; ...sign
A457 STA zp_fwb_ovf ; ...overflow
A459 STA zp_fwb_exp ; ...exponent
A45B STA zp_fwb_m1 ; ...mantissa byte 1
A45D STA zp_fwb_m2 ; ...byte 2
A45F STA zp_fwb_m3 ; ...byte 3
A461 STA zp_fwb_m4 ; ...byte 4
A463 STA zp_fwb_rnd ; ...rounding byte
A465 RTS ; Return
A466 .fti2_exp_over←1← A410 BCS
BNE fti2_too_big ; Exponent > 32: Too big error
A468 .fti2_positive←1← A40A BEQ
LDA zp_fwa_sign ; Positive: integer ready, return
A46A BPL return_24 ; positive: nothing to do
A46C .fti2_negate←4← A4B0 JSR← A4C7 JSR← A4CD JMP← AA0B JSR
SEC ; Negative: negate the 32-bit mantissa
A46D LDA #0 ; low byte first: 0...
A46F SBC zp_fwa_m4 ; minus m4,
A471 STA zp_fwa_m4 ; (store)
A473 LDA #0 ; 0...
A475 SBC zp_fwa_m3 ; minus m3 (borrow in),
A477 STA zp_fwa_m3 ; (store)
A479 LDA #0 ; 0...
A47B SBC zp_fwa_m2 ; minus m2 (borrow in),
A47D STA zp_fwa_m2 ; (store)
A47F LDA #0 ; 0...
A481 SBC zp_fwa_m1 ; minus m1 (borrow in)
A483 STA zp_fwa_m1 ; (store)
A485 .return_24←1← A46A BPL
RTS ; Return

Split FWA into integer part (&4A) and fraction (FWA)

Round FWA to the nearest integer, leaving that integer in &4A and the signed remainder (in [-0.5, 0.5]) in FWA. Used by EXP to separate the integer and fractional powers.

On EntryZP_FWA (&2E-&35)the value to split
On Exit(&4A)the integer part (low byte)
ZP_FWA (&2E-&35)the signed fraction in [-0.5, 0.5]
ZP_FWB (&3B-&42)corrupted (scratch)
A486 .fp_split_int_frac←2← 9E45 JSR← AAB8 JSR
LDA zp_fwa_exp ; Exponent of FWA
A488 BMI sif_convert ; |x| >= 1: has an integer part
A48A LDA #0 ; |x| < 1: integer part is zero
A48C STA zp_int_exp ; store it
A48E JMP fwa_sign ; return the fraction (= x)
A491 .sif_convert←1← A488 BMI
JSR fwa_to_int2 ; Convert to a fixed integer (fraction into FWB)
A494 LDA zp_fwa_m4 ; Low byte of the integer part...
A496 STA zp_int_exp ; ...kept in &4A
A498 JSR copy_fwb_mantissa ; Bring the fractional bits back into FWA
A49B LDA #&80 ; Exponent for a value in [0,1)
A49D STA zp_fwa_exp ; set it
A49F LDX zp_fwa_m1 ; Top fraction bit set (fraction >= 0.5)?
A4A1 BPL sif_normalise_frac ; no: fraction already in range
A4A3 EOR zp_fwa_sign ; Round to nearest: flip the fraction sign
A4A5 STA zp_fwa_sign ; store it back
A4A7 BPL sif_round_down ; remainder positive: round the integer down
A4A9 INC zp_int_exp ; remainder negative: round the integer up
A4AB JMP sif_negate_frac ; done: go negate the remainder
A4AE .sif_round_down←1← A4A7 BPL
DEC zp_int_exp ; negative: round the integer part down
A4B0 .sif_negate_frac←1← A4AB JMP
JSR fti2_negate ; Negate the fraction mantissa
A4B3 .sif_normalise_frac←1← A4A1 BPL
JMP fwa_normalise ; Normalise the fraction
A4B6 .inc_int_mantissa←1← A4CA JSR
INC zp_fwa_m4 ; Increment the integer-part mantissa (carry up)
A4B8 BNE return_25 ; done if byte 4 did not wrap
A4BA INC zp_fwa_m3 ; wrapped: carry into byte 3,
A4BC BNE return_25 ; done if no wrap
A4BE INC zp_fwa_m2 ; byte 2,
A4C0 BNE return_25 ; done if no wrap
A4C2 INC zp_fwa_m1 ; byte 1 (top)
A4C4 BEQ fti2_too_big ; overflow: Too big
A4C6 .return_25←3← A4B8 BNE← A4BC BNE← A4C0 BNE
RTS ; Return
A4C7 .negate_mantissa←1← AC92 JSR
JSR fti2_negate ; Decrement the mantissa magnitude (negate, +1, negate)
A4CA JSR inc_int_mantissa ; add one to the magnitude
A4CD JMP fti2_negate ; negate back

FWA = FWA - fp var

Subtract the packed real operand from FWA: compute operand - FWA (fwa_rsub_var) then negate the result.

On EntryZP_FWA (&2E-&35)the minuend
(ZP_FP_PTR) (&4B/&4C)the packed real subtrahend
On ExitZP_FWA (&2E-&35)FWA - operand
ZP_FWB (&3B-&42)corrupted (scratch)
A4D0 .fwa_sub_var←2← 9D08 JSR← A9BD JSR
JSR fwa_rsub_var ; operand - FWA...
A4D3 JMP fwa_negate ; ...then negate to give FWA - operand

Swap FWA and a fp variable

Exchange FWA with the packed real at zp_fp_ptr: unpack the operand into FWB, pack FWA into the variable, then copy FWB into FWA.

On EntryZP_FWA (&2E-&35)the accumulator value
(ZP_FP_PTR) (&4B/&4C)the packed real variable
On ExitZP_FWA (&2E-&35)the former variable value
(ZP_FP_PTR)the former FWA value
ZP_FWB (&3B-&42)corrupted (scratch)
A4D6 .fwa_swap_var←1← A6D5 JSR
JSR fwb_unpack_var ; FWB = the fp variable
A4D9 JSR fwa_pack_var ; Store FWA into the variable; FWA = old variable
fall through ↓

FWA = FWB

Copy all ten bytes of FWB into FWA.

On EntryZP_FWB (&3B-&42)the floating-point accumulator B
On ExitZP_FWA (&2E-&35)a copy of FWB
Xpreserved
A4DC .fwa_copy_from_fwb←2← A50E BEQ← A559 BCS
LDA zp_fwb_sign ; Copy FWB's sign...
A4DE STA zp_fwa_sign ; ...into FWA
A4E0 LDA zp_fwb_ovf ; Copy the overflow byte...
A4E2 STA zp_fwa_ovf ; ...into FWA
A4E4 LDA zp_fwb_exp ; Copy the exponent...
A4E6 STA zp_fwa_exp ; ...into FWA
A4E8 .copy_fwb_mantissa←1← A498 JSR
LDA zp_fwb_m1 ; Copy mantissa byte 1...
A4EA STA zp_fwa_m1 ; ...into FWA
A4EC LDA zp_fwb_m2 ; Copy mantissa byte 2...
A4EE STA zp_fwa_m2 ; ...into FWA
A4F0 LDA zp_fwb_m3 ; Copy mantissa byte 3...
A4F2 STA zp_fwa_m3 ; ...into FWA
A4F4 LDA zp_fwb_m4 ; Copy mantissa byte 4...
A4F6 STA zp_fwa_m4 ; ...into FWA
A4F8 LDA zp_fwb_rnd ; Copy the rounding byte...
A4FA STA zp_fwa_rnd ; ...into FWA
A4FC .return_26←2← A503 BEQ← A51D BCS
RTS ; FWA is now a copy of FWB

FWA = fp var - FWA

Reverse subtract: negate FWA then add the packed real operand, giving operand - FWA (normalised, rounded).

On EntryZP_FWA (&2E-&35)the subtrahend
(ZP_FP_PTR) (&4B/&4C)the packed real operand
On ExitZP_FWA (&2E-&35)operand - FWA
ZP_FWB (&3B-&42)corrupted (scratch)
A4FD .fwa_rsub_var←2← 9CF4 JSR← A4D0 JSR
JSR fwa_negate ; Negate FWA, then add the variable: var - FWA
fall through ↓

FWA = FWA + fp var

Unpack the packed real operand into FWB and add it to FWA (normalised, rounded).

On EntryZP_FWA (&2E-&35)the augend
(ZP_FP_PTR) (&4B/&4C)the packed real operand
On ExitZP_FWA (&2E-&35)the sum
ZP_FWB (&3B-&42)corrupted (scratch)
A500 .fwa_add_var←10← 9C9E JSR← A7DD JSR← A848 JSR← A863 JSR← A8CC JSR← A92A JSR← A930 JSR← AA1D JSR← AA32 JMP← B774 JSR
JSR fwb_unpack_var ; FWB = the fp variable
A503 BEQ return_26 ; Adding zero leaves FWA unchanged
fall through ↓

FWA = FWA + FWB

Add FWB to FWA (fwa_add_fwb_raw then fwa_round), normalised and rounded.

On EntryZP_FWA (&2E-&35)the augend
ZP_FWB (&3B-&42)the addend
On ExitZP_FWA (&2E-&35)the rounded sum
A505 .fwa_add_fwb←3← A830 JSR← A94A JSR← A9E8 JSR
JSR fwa_add_fwb_raw ; FWA = FWA + FWB (raw)
A508 JMP fwa_round ; then round

FWA = FWA + FWB (unrounded)

Add FWB to FWA: align the smaller operand to the larger exponent, add the mantissas and normalise. Left unrounded (the caller rounds). Operands differing by >= 37 bits leave FWA unchanged.

On EntryZP_FWA (&2E-&35)the augend
ZP_FWB (&3B-&42)the addend
On ExitZP_FWA (&2E-&35)the sum, normalised but unrounded
A50B .fwa_add_fwb_raw←2← 9F7B JSR← A505 JSR
JSR fwa_sign ; Is FWA zero?
A50E BEQ fwa_copy_from_fwb ; FWA is zero: the sum is simply FWB
A510 LDY #0 ; Y = 0 (the byte shifted in)
A512 SEC ; prepare the exponent compare
A513 LDA zp_fwa_exp ; FWA exponent...
A515 SBC zp_fwb_exp ; Exponent difference is the alignment shift
A517 BEQ addf_compare_signs ; Equal exponents: already aligned
A519 BCC addf_align_fwa ; FWA the smaller: align it to FWB instead
A51B CMP #&25 ; differ by >= 37 bits?
A51D BCS return_26 ; Differ by >= 37 bits: FWB too small to count
A51F PHA ; save the shift count
A520 AND #&38 ; Whole-byte part of the shift (difference / 8)
A522 BEQ addf_fwb_bits ; no whole-byte shift: go to the bit shift
A524 LSR ; shift count / 8...
A525 LSR ; (continued)
A526 LSR ; = whole-byte shifts
A527 TAX ; X = byte-shift count
A528 .addf_shift_fwb_byte←1← A53B BNE
LDA zp_fwb_m4 ; Shift FWB down a byte at a time
A52A STA zp_fwb_rnd ; shift FWB down a byte: m4 -> rnd
A52C LDA zp_fwb_m3 ; m3...
A52E STA zp_fwb_m4 ; -> m4
A530 LDA zp_fwb_m2 ; m2...
A532 STA zp_fwb_m3 ; -> m3
A534 LDA zp_fwb_m1 ; m1...
A536 STA zp_fwb_m2 ; -> m2
A538 STY zp_fwb_m1 ; m1 = 0
A53A DEX ; count
A53B BNE addf_shift_fwb_byte ; loop
A53D .addf_fwb_bits←1← A522 BEQ
PLA ; recover the shift count
A53E AND #7 ; then the remaining bits, to finish aligning FWB
A540 BEQ addf_compare_signs ; no bit shift: add the mantissas
A542 TAX ; X = bit-shift count
A543 .addf_shift_fwb_bit←1← A54E BNE
LSR zp_fwb_m1 ; shift FWB right one bit: m1
A545 ROR zp_fwb_m2 ; m2
A547 ROR zp_fwb_m3 ; m3
A549 ROR zp_fwb_m4 ; m4
A54B ROR zp_fwb_rnd ; rnd
A54D DEX ; count
A54E BNE addf_shift_fwb_bit ; loop
A550 BEQ addf_compare_signs ; aligned: add the mantissas
A552 .addf_align_fwa←1← A519 BCC
SEC ; FWA the smaller: shift FWA down to align
A553 LDA zp_fwb_exp ; FWB exponent - FWA exponent
A555 SBC zp_fwa_exp ; minus FWA exponent
A557 CMP #&25 ; differ by >= 37 bits?
A559 BCS fwa_copy_from_fwb ; FWA negligible: result is FWB
A55B PHA ; save the shift count
A55C AND #&38 ; whole-byte part
A55E BEQ addf_fwa_bits ; none: go to the bit shift
A560 LSR ; / 8...
A561 LSR ; (continued)
A562 LSR ; = whole-byte shifts
A563 TAX ; X = byte-shift count
A564 .addf_shift_fwa_byte←1← A577 BNE
LDA zp_fwa_m4 ; shift FWA down a byte: m4 -> rnd
A566 STA zp_fwa_rnd ; (store)
A568 LDA zp_fwa_m3 ; m3 -> m4
A56A STA zp_fwa_m4 ; (store)
A56C LDA zp_fwa_m2 ; m2 -> m3
A56E STA zp_fwa_m3 ; (store)
A570 LDA zp_fwa_m1 ; m1 -> m2
A572 STA zp_fwa_m2 ; (store)
A574 STY zp_fwa_m1 ; m1 = 0
A576 DEX ; count
A577 BNE addf_shift_fwa_byte ; loop
A579 .addf_fwa_bits←1← A55E BEQ
PLA ; recover the shift count
A57A AND #7 ; bit part
A57C BEQ addf_take_exp ; none: take the larger exponent
A57E TAX ; X = bit-shift count
A57F .addf_shift_fwa_bit←1← A58A BNE
LSR zp_fwa_m1 ; shift FWA right one bit: m1
A581 ROR zp_fwa_m2 ; m2
A583 ROR zp_fwa_m3 ; m3
A585 ROR zp_fwa_m4 ; m4
A587 ROR zp_fwa_rnd ; rnd
A589 DEX ; count
A58A BNE addf_shift_fwa_bit ; loop
A58C .addf_take_exp←1← A57C BEQ
LDA zp_fwb_exp ; Result takes the larger exponent
A58E STA zp_fwa_exp ; store the larger exponent
A590 .addf_compare_signs←3← A517 BEQ← A540 BEQ← A550 BEQ
LDA zp_fwa_sign ; Compare the signs: load FWA sign
A592 EOR zp_fwb_sign ; Compare the operand signs
A594 BPL fp_mantissas_add ; Same sign: add; opposite: subtract smaller from larger
A596 LDA zp_fwa_m1 ; Opposite signs: compare magnitudes (m1)
A598 CMP zp_fwb_m1 ; against FWB
A59A BNE fp_mantissas_sub ; differ: subtract
A59C LDA zp_fwa_m2 ; m2
A59E CMP zp_fwb_m2 ; against FWB
A5A0 BNE fp_mantissas_sub ; differ: subtract
A5A2 LDA zp_fwa_m3 ; m3
A5A4 CMP zp_fwb_m3 ; against FWB
A5A6 BNE fp_mantissas_sub ; differ: subtract
A5A8 LDA zp_fwa_m4 ; m4
A5AA CMP zp_fwb_m4 ; against FWB
A5AC BNE fp_mantissas_sub ; differ: subtract
A5AE LDA zp_fwa_rnd ; rnd
A5B0 CMP zp_fwb_rnd ; against FWB
A5B2 BNE fp_mantissas_sub ; differ: subtract
A5B4 JMP fwa_clear ; Equal magnitudes of opposite sign cancel to zero
A5B7 .fp_mantissas_sub←5← A59A BNE← A5A0 BNE← A5A6 BNE← A5AC BNE← A5B2 BNE
BCS addf_subtract ; FWA >= FWB? choose the subtraction order
A5B9 SEC ; FWB - FWA: rnd
A5BA LDA zp_fwb_rnd ; FWB rnd...
A5BC SBC zp_fwa_rnd ; - FWA rnd,
A5BE STA zp_fwa_rnd ; (store)
A5C0 LDA zp_fwb_m4 ; m4
A5C2 SBC zp_fwa_m4 ; - FWA m4,
A5C4 STA zp_fwa_m4 ; (store)
A5C6 LDA zp_fwb_m3 ; m3
A5C8 SBC zp_fwa_m3 ; - FWA m3,
A5CA STA zp_fwa_m3 ; (store)
A5CC LDA zp_fwb_m2 ; m2
A5CE SBC zp_fwa_m2 ; - FWA m2,
A5D0 STA zp_fwa_m2 ; (store)
A5D2 LDA zp_fwb_m1 ; m1
A5D4 SBC zp_fwa_m1 ; - FWA m1
A5D6 STA zp_fwa_m1 ; (store)
A5D8 LDA zp_fwb_sign ; result takes FWB's sign
A5DA STA zp_fwa_sign ; (store)
A5DC JMP fwa_normalise ; normalise the difference
A5DF .fp_mantissas_add←1← A594 BPL
CLC ; Same sign: add the mantissas
A5E0 JMP fwa_acc10 ; FWA += FWB
A5E3 .addf_subtract←1← A5B7 BCS
SEC ; FWA - FWB: rnd
A5E4 LDA zp_fwa_rnd ; FWA rnd...
A5E6 SBC zp_fwb_rnd ; - FWB rnd,
A5E8 STA zp_fwa_rnd ; (store)
A5EA LDA zp_fwa_m4 ; m4
A5EC SBC zp_fwb_m4 ; - FWB m4,
A5EE STA zp_fwa_m4 ; (store)
A5F0 LDA zp_fwa_m3 ; m3
A5F2 SBC zp_fwb_m3 ; - FWB m3,
A5F4 STA zp_fwa_m3 ; (store)
A5F6 LDA zp_fwa_m2 ; m2
A5F8 SBC zp_fwb_m2 ; - FWB m2,
A5FA STA zp_fwa_m2 ; (store)
A5FC LDA zp_fwa_m1 ; m1
A5FE SBC zp_fwb_m1 ; - FWB m1,
A600 STA zp_fwa_m1 ; (store)
A602 JMP fwa_normalise ; normalise the difference
A605 .return_27←1← A609 BEQ
RTS ; Return

FWA = FWA * fp var (raw)

Multiply FWA by the packed real operand (sign XOR, exponents added, mantissas multiplied by shift-and-add). A zero operand gives zero. Left unnormalised and unrounded (the caller finishes).

On EntryZP_FWA (&2E-&35)the multiplicand
(ZP_FP_PTR) (&4B/&4C)the packed real multiplier
On ExitZP_FWA (&2E-&35)the product, unnormalised
ZP_FWB (&3B-&42)corrupted (scratch)
A606 .fwa_mul_var_raw←2← A656 JSR← AF30 JSR
JSR fwa_sign ; Is FWA zero?
A609 BEQ return_27 ; zero: the product is zero
A60B JSR fwb_unpack_var ; FWB = the fp variable (the multiplier)
A60E BNE mulf_add_exp ; non-zero: multiply
A610 JMP fwa_clear ; multiplier zero: the product is zero
A613 .mulf_add_exp←1← A60E BNE
CLC ; Add the exponents:
A614 LDA zp_fwa_exp ; FWA exp...
A616 ADC zp_fwb_exp ; + FWB exp
A618 BCC mulf_unbias ; no carry
A61A INC zp_fwa_ovf ; carry into overflow
A61C CLC ; (clear carry)
A61D .mulf_unbias←1← A618 BCC
SBC #&7f ; remove the excess-128 bias (added twice)
A61F STA zp_fwa_exp ; store the product exponent
A621 BCS mulf_setup ; no borrow
A623 DEC zp_fwa_ovf ; borrow into overflow
A625 .mulf_setup←1← A621 BCS
LDX #5 ; Move FWA aside as the multiplicand and clear FWA:
A627 LDY #0 ; Y = 0 (to clear FWA with)
A629 .mulf_copy_loop←1← A630 BNE
LDA zp_fwa_exp,x ; copy a FWA byte...
A62B STA zp_fwb_rnd,x ; ...to the multiplicand
A62D STY zp_fwa_exp,x ; clear the FWA byte
A62F DEX ; count
A630 BNE mulf_copy_loop ; loop
A632 LDA zp_fwa_sign ; Product sign = FWA sign XOR FWB sign:
A634 EOR zp_fwb_sign ; XOR FWB sign
A636 STA zp_fwa_sign ; (store)
A638 LDY #&20 ; 32 iterations, one per multiplier bit
A63A .mulf_bit_loop←1← A653 BNE
LSR zp_fwb_m1 ; Shift the multiplier right: next bit into carry
A63C ROR zp_fwb_m2 ; m2,
A63E ROR zp_fwb_m3 ; m3,
A640 ROR zp_fwb_m4 ; m4,
A642 ROR zp_fwb_rnd ; rnd
A644 ASL zp_fp_temp_3 ; Shift the running product left (&43-&46):
A646 ROL zp_fp_temp_2 ; through &45,
A648 ROL zp_fp_temp_1 ; &44,
A64A ROL zp_fp_temp ; &43 (high)
A64C BCC mulf_count ; multiplier bit clear: skip the add
A64E CLC ; bit set: add the multiplicand
A64F JSR fwa_acc_fwb ; FWA += FWB
A652 .mulf_count←1← A64C BCC
DEY ; count
A653 BNE mulf_bit_loop ; loop
A655 RTS ; Return the product

FWA = FWA * fp var

Multiply FWA by the packed real operand (fwa_mul_var_raw then normalise and round).

On EntryZP_FWA (&2E-&35)the multiplicand
(ZP_FP_PTR) (&4B/&4C)the packed real multiplier
On ExitZP_FWA (&2E-&35)the product, normalised and rounded
ZP_FWB (&3B-&42)corrupted (scratch)
A656 .fwa_mul_var←12← 9D2F JSR← 9E81 JSR← A842 JSR← A845 JSR← A85D JSR← A9B4 JSR← A9C6 JSR← AA17 JSR← AA2C JSR← AAD4 JSR← AB2C JSR← ABBC JSR
JSR fwa_mul_var_raw ; FWA = FWA * fp var (raw)
A659 .mulf_normalise←2← A7A6 JMP← AF81 JSR
JSR fwa_normalise ; normalise (round below)
fall through ↓

Round FWA

Round FWA to the mantissa LSB using the rounding byte: round to nearest, ties handled by the LSB. A carry out of the top renormalises and can raise Too big.

On EntryZP_FWA (&2E-&35)the value to round (rnd byte holds the bits below the LSB)
On ExitZP_FWA (&2E-&34)the rounded value
BRKToo big on overflow
A65C .fwa_round←2← A118 JSR← A508 JMP
LDA zp_fwa_rnd ; The rounding byte holds the bits below the LSB
A65E CMP #&80 ; compare with half (&80)
A660 BCC round_clear ; Below half: round down (truncate)
A662 BEQ round_half ; Exactly half: special-case the LSB
A664 LDA #&ff ; Above half: round up by adding 1
A666 JSR fwa_round_carry ; add 1 via the carry ripple
A669 JMP round_clear ; then finish
A66C .err_too_big←2← A450 JMP← A684 BPL
BRK ; BRK error block: "Too big"
A66D EQUB &14
A66E EQUS "Too big"
A675 EQUB &00
A676 .round_half←1← A662 BEQ
LDA zp_fwa_m4 ; Exactly half: force the mantissa LSB
A678 ORA #1 ; set the LSB
A67A STA zp_fwa_m4 ; (store)
A67C .round_clear←2← A660 BCC← A669 JMP
LDA #0 ; Clear the now-spent rounding byte
A67E STA zp_fwa_rnd ; clear the rounding byte
A680 LDA zp_fwa_ovf ; A carry may have overflowed the mantissa
A682 BEQ return_28 ; no overflow: done
A684 BPL err_too_big ; Overflowed the exponent range: Too big
fall through ↓

FWA = 0

Zero every byte of FWA. Exponent 0 is the special encoding for the value zero.

On ExitZP_FWA (&2E-&35)0.0
A0
Xpreserved
Ypreserved
A686 .fwa_clear←8← 9F5C JSR← 9FA0 JSR← A2EE JSR← A3FB JMP← A5B4 JMP← A610 JMP← A699 JSR← AAA6 JSR
LDA #0 ; Zero to write into every field
A688 STA zp_fwa_sign ; Clear the sign
A68A STA zp_fwa_ovf ; Clear the overflow byte
A68C STA zp_fwa_exp ; Exponent 0 is the special "value is zero"
A68E STA zp_fwa_m1 ; Clear mantissa byte 1 (most significant)
A690 STA zp_fwa_m2 ; Clear mantissa byte 2
A692 STA zp_fwa_m3 ; Clear mantissa byte 3
A694 STA zp_fwa_m4 ; Clear mantissa byte 4 (least significant)
A696 STA zp_fwa_rnd ; Clear the rounding byte
A698 .return_28←2← A682 BEQ← A6EA BEQ
RTS ; FWA now holds 0.0

FWA = 1

Set the floating-point accumulator to 1.0 (mantissa MSB &80, exponent &81).

On ExitZP_FWA (&2E-&35)1.0
Anonzero (flags a real result)
A699 .fwa_set_one←6← 9E8B JSR← 9F20 JSR← A6A8 JSR← A9BA JSR← AB22 JSR← B863 JSR
JSR fwa_clear ; Start from 0.0
A69C LDY #&80 ; Mantissa MSB &80 is the implied leading 1
A69E STY zp_fwa_m1 ; Set mantissa byte 1
A6A0 INY ; Y = &81
A6A1 STY zp_fwa_exp ; Exponent &81 makes the value exactly 1.0
A6A3 TYA ; Return non-zero (A = exponent) to flag a real
A6A4 RTS ; FWA now holds 1.0

FWA = 1 / FWA

Reciprocal: save FWA in TEMP1, set FWA = 1, then divide by the saved value (normalised, rounded). Raises Division by zero if FWA was zero.

On EntryZP_FWA (&2E-&35)the value to invert
On ExitZP_FWA (&2E-&35)1 / FWA
(&046C)corrupted (TEMP1 scratch)
BRKDivision by zero if FWA was zero
A6A5 .fwa_reciprocal←2← A921 JSR← AB1A JSR
JSR fwa_pack_temp1 ; Save FWA (the divisor) in TEMP1
A6A8 JSR fwa_set_one ; FWA = 1
A6AB BNE fp_divide ; divide 1 by the saved value
fall through ↓

FWA = fp var / FWA

Reverse divide: copy FWA (the divisor) into FWB, unpack the packed real operand (the dividend) into FWA, then divide, giving operand / FWA. Raises Division by zero if FWA is zero.

On EntryZP_FWA (&2E-&35)the divisor
(ZP_FP_PTR) (&4B/&4C)the packed real dividend
On ExitZP_FWA (&2E-&35)operand / FWA
BRKDivision by zero if FWA was zero
A6AD .fwa_rdiv_var←4← 9DF8 JSR← A7D6 JSR← A8B8 JSR← A8F8 JSR
JSR fwa_sign ; Is FWA (the divisor) zero?
A6B0 BEQ fp_div_zero ; yes: Division by zero
A6B2 JSR fwb_copy_from_fwa ; FWB = FWA (the divisor)
A6B5 JSR fwa_unpack_var ; FWA = the fp variable (the dividend)
A6B8 BNE div_signs ; non-zero: do the division
A6BA RTS ; dividend zero: result is zero
A6BB .fp_div_zero←2← A6B0 BEQ← A6EF BEQ
JMP div_zero_error ; Division by zero error

TAN

FWA = tan(FWA), argument in radians. Pure routine at &A6C1.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
A6BE .fn_tan
JSR eval_real ; Evaluate the argument as a real
A6C1 JSR sin_cos_reduce ; Compute the trig kernel
A6C4 LDA zp_int_exp ; save the quadrant
A6C6 PHA ; push it
A6C7 JSR point_fp_temp4 ; save the first result (cos) in TEMP4
A6CA JSR fwa_pack_var ; pack FWA (cos) there
A6CD INC zp_int_exp ; shift the quadrant (cos -> sin)
A6CF JSR sin_quadrant ; compute the other (sin)
A6D2 JSR point_fp_temp4 ; point at TEMP4
A6D5 JSR fwa_swap_var ; swap FWA (sin) with TEMP4 (cos)
A6D8 PLA ; restore the quadrant
A6D9 STA zp_int_exp ; store it
A6DB JSR sin_quadrant ; finish the trig (cos in FWA)
A6DE JSR point_fp_temp4 ; point at TEMP4 (sin)
A6E1 JSR fp_divide ; TAN = sin / cos
A6E4 LDA #&ff ; real result
A6E6 RTS ; Return TAN

FWA = FWA / divisor (restoring long division)

Floating-point divide of FWA by the packed real divisor: sign XOR, exponents subtracted, then 32 quotient bits plus guard bits by restoring long division (compare/subtract/shift). Raises Division by zero on a zero divisor; a zero dividend gives zero.

On EntryZP_FWA (&2E-&35)the dividend
(ZP_FP_PTR) (&4B/&4C)the packed real divisor
On ExitZP_FWA (&2E-&35)the quotient
ZP_FWB (&3B-&42)corrupted (scratch)
BRKDivision by zero on a zero divisor
A6E7 .fp_divide←3← A6AB BNE← A6E1 JSR← A9EB JSR
JSR fwa_sign ; Is the dividend (FWA) zero?
A6EA BEQ return_28 ; zero: result is zero
A6EC JSR fwb_unpack_var ; unpack the divisor into FWB
A6EF BEQ fp_div_zero ; divisor zero: Division by zero
A6F1 .div_signs←1← A6B8 BNE
LDA zp_fwa_sign ; result sign = sign XOR sign
A6F3 EOR zp_fwb_sign ; XOR the divisor sign
A6F5 STA zp_fwa_sign ; (store)
A6F7 SEC ; result exponent = dividend - divisor:
A6F8 LDA zp_fwa_exp ; dividend exp...
A6FA SBC zp_fwb_exp ; minus divisor exp
A6FC BCS div_rebias ; no borrow
A6FE DEC zp_fwa_ovf ; borrow into overflow
A700 SEC ; (set carry again)
A701 .div_rebias←1← A6FC BCS
ADC #&80 ; re-bias the exponent
A703 STA zp_fwa_exp ; (store)
A705 BCC div_quotient ; no carry
A707 INC zp_fwa_ovf ; carry into overflow
A709 CLC ; (clear carry)
A70A .div_quotient←1← A705 BCC
LDX #&20 ; 32 quotient bits (restoring long division):
A70C .div_cmp1←1← A750 BNE
BCS div_sub1 ; remainder >= divisor?
A70E LDA zp_fwa_m1 ; compare FWA (remainder) with FWB (divisor):
A710 CMP zp_fwb_m1 ; vs divisor m1,
A712 BNE div_bit0_1 ; differ: decided
A714 LDA zp_fwa_m2 ; else m2...
A716 CMP zp_fwb_m2 ; vs m2,
A718 BNE div_bit0_1 ; differ: decided
A71A LDA zp_fwa_m3 ; else m3...
A71C CMP zp_fwb_m3 ; vs m3,
A71E BNE div_bit0_1 ; differ: decided
A720 LDA zp_fwa_m4 ; else m4...
A722 CMP zp_fwb_m4 ; vs m4
A724 .div_bit0_1←3← A712 BNE← A718 BNE← A71E BNE
BCC div_shift1 ; less: quotient bit 0
A726 .div_sub1←1← A70C BCS
LDA zp_fwa_m4 ; subtract the divisor from the remainder:
A728 SBC zp_fwb_m4 ; - divisor m4,
A72A STA zp_fwa_m4 ; (store)
A72C LDA zp_fwa_m3 ; m3...
A72E SBC zp_fwb_m3 ; - divisor m3,
A730 STA zp_fwa_m3 ; (store)
A732 LDA zp_fwa_m2 ; m2...
A734 SBC zp_fwb_m2 ; - divisor m2,
A736 STA zp_fwa_m2 ; (store)
A738 LDA zp_fwa_m1 ; m1...
A73A SBC zp_fwb_m1 ; - divisor m1,
A73C STA zp_fwa_m1 ; (store)
A73E SEC ; quotient bit 1
A73F .div_shift1←1← A724 BCC
ROL zp_fp_temp_3 ; shift the quotient left, bring in the bit:
A741 ROL zp_fp_temp_2 ; through &45,
A743 ROL zp_fp_temp_1 ; &44,
A745 ROL zp_fp_temp ; &43 (high)
A747 ASL zp_fwa_m4 ; shift the remainder left:
A749 ROL zp_fwa_m3 ; m3,
A74B ROL zp_fwa_m2 ; m2,
A74D ROL zp_fwa_m1 ; m1
A74F DEX ; count
A750 BNE div_cmp1 ; loop 32 times
A752 LDX #7 ; 7 guard bits:
A754 .div_cmp2←1← A792 BNE
BCS div_sub2 ; remainder >= divisor?
A756 LDA zp_fwa_m1 ; compare remainder vs divisor: m1...
A758 CMP zp_fwb_m1 ; vs m1,
A75A BNE div_bit0_2 ; differ: decided
A75C LDA zp_fwa_m2 ; else m2...
A75E CMP zp_fwb_m2 ; vs m2,
A760 BNE div_bit0_2 ; differ: decided
A762 LDA zp_fwa_m3 ; else m3...
A764 CMP zp_fwb_m3 ; vs m3,
A766 BNE div_bit0_2 ; differ: decided
A768 LDA zp_fwa_m4 ; else m4...
A76A CMP zp_fwb_m4 ; vs m4
A76C .div_bit0_2←3← A75A BNE← A760 BNE← A766 BNE
BCC div_shift2 ; less: bit 0
A76E .div_sub2←1← A754 BCS
LDA zp_fwa_m4 ; subtract:
A770 SBC zp_fwb_m4 ; - divisor m4,
A772 STA zp_fwa_m4 ; (store)
A774 LDA zp_fwa_m3 ; m3...
A776 SBC zp_fwb_m3 ; - divisor m3,
A778 STA zp_fwa_m3 ; (store)
A77A LDA zp_fwa_m2 ; m2...
A77C SBC zp_fwb_m2 ; - divisor m2,
A77E STA zp_fwa_m2 ; (store)
A780 LDA zp_fwa_m1 ; m1...
A782 SBC zp_fwb_m1 ; - divisor m1,
A784 STA zp_fwa_m1 ; (store)
A786 SEC ; guard bit 1
A787 .div_shift2←1← A76C BCC
ROL zp_fwa_rnd ; shift in the guard bit
A789 ASL zp_fwa_m4 ; shift the remainder:
A78B ROL zp_fwa_m3 ; m3,
A78D ROL zp_fwa_m2 ; m2,
A78F ROL zp_fwa_m1 ; m1
A791 DEX ; count
A792 BNE div_cmp2 ; loop
A794 ASL zp_fwa_rnd ; final guard bit
A796 LDA zp_fp_temp_3 ; move the quotient into the FWA mantissa:
A798 STA zp_fwa_m4 ; -> m4,
A79A LDA zp_fp_temp_2 ; &45...
A79C STA zp_fwa_m3 ; -> m3,
A79E LDA zp_fp_temp_1 ; &44...
A7A0 STA zp_fwa_m2 ; -> m2,
A7A2 LDA zp_fp_temp ; &43...
A7A4 STA zp_fwa_m1 ; -> m1
A7A6 JMP mulf_normalise ; normalise and round
A7A9 .sqr_neg_error←1← A7BC BMI
BRK ; SQR of a negative: error block
A7AA EQUB &15
A7AB EQUS "-ve root"
A7B3 EQUB &00

SQR

FWA = square root of FWA. Pure routine at &A7B7.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
A7B4 .fn_sqr
JSR eval_real ; Evaluate the argument as a real
A7B7 .sqr_sign←1← A9C0 JMP
JSR fwa_sign ; Sign of the argument
A7BA BEQ sqr_done ; zero: the root is zero
A7BC BMI sqr_neg_error ; negative: -ve root error
A7BE JSR fwa_pack_temp1 ; Save the argument in TEMP1
A7C1 LDA zp_fwa_exp ; Initial guess: halve the exponent
A7C3 LSR ; halve the exponent
A7C4 ADC #&40 ; (re-bias)
A7C6 STA zp_fwa_exp ; store the guess exponent
A7C8 LDA #5 ; 5 Newton iterations
A7CA STA zp_int_exp ; (counter)
A7CC JSR point_fp_temp2 ; point at TEMP2 (the guess)
A7CF .sqr_iter_loop←1← A7E4 BNE
JSR fwa_pack_var ; save the current guess
A7D2 LDA #&6c ; point at TEMP1 (the argument)
A7D4 STA zp_fp_ptr ; store the pointer low
A7D6 JSR fwa_rdiv_var ; FWA = argument / guess
A7D9 LDA #&71 ; point at TEMP2 (the guess)
A7DB STA zp_fp_ptr ; store the pointer low
A7DD JSR fwa_add_var ; FWA = arg/guess + guess
A7E0 DEC zp_fwa_exp ; halve it: next guess
A7E2 DEC zp_int_exp ; count
A7E4 BNE sqr_iter_loop ; iterate
A7E6 .sqr_done←1← A7BA BEQ
LDA #&ff ; real result
A7E8 RTS ; Return the root

Point zp_fp_ptr at FP TEMP4 (&047B)

Set zp_fp_ptr to FP TEMP4, ready for a pack/unpack.

On ExitZP_FP_PTR (&4B/&4C)= &047B
Xpreserved
Ypreserved
A7E9 .point_fp_temp4←4← A6C7 JSR← A6D2 JSR← A6DE JSR← A83F JSR
LDA #&7b ; TEMP4 low (&7B)
A7EB BNE set_fp_ptr ; set it (shared tail)
fall through ↓

Point zp_fp_ptr at FP TEMP2 (&0471)

Set zp_fp_ptr to FP TEMP2, ready for a pack/unpack.

On ExitZP_FP_PTR (&4B/&4C)= &0471
Xpreserved
Ypreserved
A7ED .point_fp_temp2←3← 9E7E JSR← A7CC JSR← AA23 JSR
LDA #&71 ; TEMP2 low (&71)
A7EF BNE set_fp_ptr ; set it (shared tail)
fall through ↓

Point zp_fp_ptr at FP TEMP3 (&0476)

Set zp_fp_ptr to FP TEMP3, ready for a pack/unpack.

On ExitZP_FP_PTR (&4B/&4C)= &0476
Xpreserved
Ypreserved
A7F1 .point_fp_temp3←2← A8F5 JSR← AAD1 JSR
LDA #&76 ; TEMP3 low (&76)
A7F3 BNE set_fp_ptr ; set it (shared tail)
fall through ↓

Point zp_fp_ptr at FP TEMP1 (&046C)

Set zp_fp_ptr to FP TEMP1, ready for a pack/unpack.

On ExitZP_FP_PTR (&4B/&4C)= &046C
Xpreserved
Ypreserved
A7F5 .point_fp_temp1←6← 9F71 JSR← A3B2 JSR← A860 JSR← A8B5 JSR← AA1A JSR← AA2F JSR
LDA #&6c ; TEMP1 low (&6C)
A7F7 .set_fp_ptr←3← A7EB BNE← A7EF BNE← A7F3 BNE
STA zp_fp_ptr ; set the pointer low
A7F9 LDA #4 ; high &04
A7FB STA zp_fp_ptr_1 ; pointer high
A7FD RTS ; Return

LN

FWA = natural log of FWA. Pure routine at &A801.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
A7FE .fn_ln←1← ABA8 JSR
JSR eval_real ; Evaluate the argument as a real
A801 .ln_compute←1← 9E75 JSR
JSR fwa_sign ; Sign of x
A804 BEQ log_range_error ; zero: error
A806 BPL ln_setup ; positive: compute
A808 .log_range_error←1← A804 BEQ
BRK ; zero or negative: Log range error
A809 EQUB &16
A80A EQUS "Log range"
A813 EQUB &00
A814 .ln_setup←1← A806 BPL
JSR fwb_clear ; Set FWB = -1 (to form mantissa - 1)
A817 LDY #&80 ; Y = &80,
A819 STY zp_fwb_sign ; sign = negative,
A81B STY zp_fwb_m1 ; mantissa top = &80,
A81D INY ; Y = &81,
A81E STY zp_fwb_exp ; exponent = &81 (value -1.0)
A820 LDX zp_fwa_exp ; Binary exponent of x
A822 BEQ ln_scale ; zero mantissa exponent?
A824 LDA zp_fwa_m1 ; Mantissa top byte
A826 CMP #&b5 ; below sqrt(2)?
A828 BCC ln_save_exp ; yes: keep this exponent
A82A .ln_scale←1← A822 BEQ
INX ; no: scale mantissa to [sqrt(1/2), sqrt(2)]
A82B DEY ; ...and adjust the exponent
A82C .ln_save_exp←1← A828 BCC
TXA ; Save the adjusted binary exponent
A82D PHA ; push it
A82E STY zp_fwa_exp ; Set the mantissa exponent
A830 JSR fwa_add_fwb ; FWA = mantissa - 1
A833 LDA #&7b ; Save (m-1) in TEMP4
A835 JSR pack_temp_tail ; pack FWA there
A838 LDA #&73 ; Point at the ln coefficient table: low byte
A83A LDY #&a8 ; ...high
A83C JSR fp_eval_cont_frac ; Evaluate the ln continued fraction
A83F JSR point_fp_temp4 ; Point at (m-1) in TEMP4
A842 JSR fwa_mul_var ; Scale by (m-1)...
A845 JSR fwa_mul_var ; ...and again
A848 JSR fwa_add_var ; Add (m-1): FWA = ln(mantissa)
A84B JSR fwa_pack_temp1 ; Save ln(mantissa) in TEMP1
A84E PLA ; Recover the adjusted exponent
A84F SEC ; set carry for the subtract
A850 SBC #&81 ; Binary exponent e = adjusted - &81
A852 JSR small_int_to_fwa ; FWA = e
A855 LDA #&6e ; Point at the constant ln 2: low byte
A857 STA zp_fp_ptr ; store the pointer low
A859 LDA #&a8 ; high byte
A85B STA zp_fp_ptr_1 ; store the pointer high
A85D JSR fwa_mul_var ; FWA = e * ln 2
A860 JSR point_fp_temp1 ; Point at ln(mantissa) in TEMP1
A863 JSR fwa_add_var ; LN(x) = e*ln2 + ln(mantissa)
A866 LDA #&ff ; real result
A868 RTS ; Return
A869 EQUB &7F, &5E, &5B, ; float40 0.434294482 log10(e) = &D8, &AA ; 1/ln(10)
A86E EQUB &80, &31, &72, &17, &F8 ; float40 0.6931471806 ln 2
A873 EQUB &06
A874 EQUB &7A, &12, &38, &A5, &0B ; float40 0.008924637961 ln c0
A879 EQUB &88, &79, &0E, &9F, &F3 ; float40 249.0571281 ln c1
A87E EQUB &7C, &2A, &AC, &3F, &B5 ; float40 0.04166817556 ln c2
A883 EQUB &86, &34, &01, &A2, &7A ; float40 45.00159636 ln c3
A888 EQUB &7F, &63, &8E, &37, &EC ; float40 0.4444444156 ln c4
A88D EQUB &82, &3F, &FF, &FF, &C1 ; float40 2.999999941 ln c5
A892 EQUB &7F, &FF, &FF, &FF, &FF ; float40 -0.4999999999 -0.5

Evaluate a continued-fraction approximation

A (low) and Y (high) point at a coefficient table: a count byte followed by that many five-byte fp coefficients. The argument is stashed in TEMP1; the routine folds the table from the top, alternating FWA = arg / FWA and FWA = FWA + next coefficient, to evaluate the continued fraction used by the trig kernels.

On EntryAcoefficient table address low byte
Ycoefficient table address high byte
ZP_FWA (&2E-&35)the argument
On ExitZP_FWA (&2E-&35)the continued-fraction value
(&046C)corrupted (TEMP1 holds the argument)
A897 .fp_eval_cont_frac←4← A83C JSR← A951 JSR← A9CD JSR← AADE JSR
STA zp_coeff_ptr ; Save the coefficient table pointer (low)
A899 STY zp_coeff_ptr_1 ; ...and high
A89B JSR fwa_pack_temp1 ; Stash the argument in TEMP1
A89E LDY #0 ; First table byte is the coefficient count
A8A0 LDA (zp_coeff_ptr),y ; read it
A8A2 STA zp_dp_flag ; use it as the loop counter
A8A4 INC zp_coeff_ptr ; Advance past the count byte to coefficient 0
A8A6 BNE cfrac_first ; no carry into the high byte
A8A8 INC zp_coeff_ptr_1 ; else bump the high byte
A8AA .cfrac_first←1← A8A6 BNE
LDA zp_coeff_ptr ; Point the fp pointer at the first coefficient
A8AC STA zp_fp_ptr ; low byte -> fp_ptr
A8AE LDA zp_coeff_ptr_1 ; high byte
A8B0 STA zp_fp_ptr_1 ; -> fp_ptr+1
A8B2 JSR fwa_unpack_var ; FWA = coefficient 0
A8B5 .cfrac_loop←1← A8D1 BNE
JSR point_fp_temp1 ; Point at the argument in TEMP1
A8B8 JSR fwa_rdiv_var ; FWA = arg / FWA
A8BB CLC ; Advance the pointer to the next coefficient
A8BC LDA zp_coeff_ptr ; ...(five bytes per coefficient)
A8BE ADC #5 ; low byte + 5
A8C0 STA zp_coeff_ptr ; store the table pointer low
A8C2 STA zp_fp_ptr ; and the fp pointer low
A8C4 LDA zp_coeff_ptr_1 ; high byte
A8C6 ADC #0 ; plus any carry
A8C8 STA zp_coeff_ptr_1 ; store the table pointer high
A8CA STA zp_fp_ptr_1 ; and the fp pointer high
A8CC JSR fwa_add_var ; FWA = FWA + next coefficient
A8CF DEC zp_dp_flag ; One coefficient done
A8D1 BNE cfrac_loop ; loop until the table is exhausted
A8D3 RTS ; Return the continued-fraction value

ACS

FWA = arccos(FWA), result in radians. Computed as arcsin then adjusted at &A927.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
A8D4 .fn_acs
JSR fn_asn ; Compute asn(x)
A8D7 JMP fwa_complement_half_pi ; Result = pi/2 - asn(x)

ASN

FWA = arcsin(FWA), result in radians. Pure routine at &A8DD.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
A8DA .fn_asn←1← A8D4 JSR
JSR eval_real ; Evaluate the argument as a real
A8DD JSR fwa_sign ; Sign of the argument
A8E0 BPL asn_save ; positive: compute directly
A8E2 LSR zp_fwa_sign ; negative: take |x|...
A8E4 JSR asn_save ; compute asn(|x|)
A8E7 JMP atn_neg ; ...and negate the result
A8EA .asn_save←2← A8E0 BPL← A8E4 JSR
JSR fwa_pack_temp3 ; Save x in TEMP3
A8ED JSR sin_series ; compute sqrt(1 - x^2)
A8F0 JSR fwa_sign ; is it zero (x = 1)?
A8F3 BEQ asn_one ; yes: result is pi/2
A8F5 JSR point_fp_temp3 ; point at x (TEMP3)
A8F8 JSR fwa_rdiv_var ; FWA = x / sqrt(1 - x^2)
A8FB JMP atn_sign ; asn(x) = atn(that)
A8FE .asn_one←2← A8F3 BEQ← ABCB JSR
JSR point_const_half_pi ; x = 1: load pi/2
A901 JSR fwa_unpack_var ; unpack pi/2 into FWA
A904 .asn_done←2← A90D BEQ← A93A BCC
LDA #&ff ; real result
A906 RTS ; Return

ATN

FWA = arctan(FWA), result in radians. Pure routine at &A90A.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
A907 .fn_atn
JSR eval_real ; Evaluate the argument as a real
A90A .atn_sign←1← A8FB JMP
JSR fwa_sign ; Sign of the argument
A90D BEQ asn_done ; zero: atn(0) = 0, return it
A90F BPL atn_large ; positive: compute directly
A911 LSR zp_fwa_sign ; negative: clear the sign to take |x|
A913 JSR atn_large ; compute atn(|x|)
A916 .atn_neg←1← A8E7 JMP
LDA #&80 ; set the result sign negative
A918 STA zp_fwa_sign ; ...so atn(-x) = -atn(x)
A91A RTS ; Return
A91B .atn_large←2← A90F BPL← A913 JSR
LDA zp_fwa_exp ; Exponent of |x|
A91D CMP #&81 ; |x| < 1?
A91F BCC chp_exp ; yes: evaluate the series directly
A921 JSR fwa_reciprocal ; |x| >= 1: FWA = 1 / x
A924 JSR chp_exp ; atn(1/x) via the series
fall through ↓

FWA = pi/2 - FWA

Subtract FWA from pi/2 using the two-part constant at &AA59/&AA5E for extra precision, then negate. Used for ACS (pi/2 - ASN) and the large-argument arctan identity atn(x) = pi/2 - atn(1/x).

On EntryZP_FWA (&2E-&35)the angle to complement
On ExitZP_FWA (&2E-&35)pi/2 - FWA
ZP_FWB (&3B-&42)corrupted (scratch)
A927 .fwa_complement_half_pi←1← A8D7 JMP
JSR point_half_pi_hi ; atn(x) = pi/2 - atn(1/x)
A92A JSR fwa_add_var ; add the high part of pi/2
A92D JSR point_half_pi_lo ; point at the low part
A930 JSR fwa_add_var ; add the low part
A933 JMP fwa_negate ; negate: result is pi/2 - FWA (tail)
A936 .chp_exp←2← A91F BCC← A924 JSR
LDA zp_fwa_exp ; Exponent of the (reduced) argument
A938 CMP #&73 ; very small (|x| < 2^-13)?
A93A BCC asn_done ; yes: atn(x) = x to working precision
A93C JSR fwa_pack_temp3 ; Save the argument x in TEMP3
A93F JSR fwb_clear ; Set FWB = 1...
A942 LDA #&80 ; the constant &80
A944 STA zp_fwb_exp ; exponent,
A946 STA zp_fwb_m1 ; mantissa top byte,
A948 STA zp_fwb_sign ; and sign byte
A94A JSR fwa_add_fwb ; Add it to the argument
A94D LDA #&5a ; Evaluate the arctan continued fraction
A94F LDY #&a9 ; (coefficients at &A95A)
A951 JSR fp_eval_cont_frac ; evaluate the fraction
A954 JSR mul_by_temp3 ; Scale by x (in TEMP3): atn(x) = x * P
A957 LDA #&ff ; real result
A959 RTS ; Return
A95A EQUB &09
A95B EQUB &85, &A3, &59, &E8, &67 ; float40 -20.4189003 atn c0
A960 EQUB &80, &1C, &9D, &07, &36 ; float40 0.6117710597 atn c1
A965 EQUB &80, &57, &BB, &78, &DF ; float40 0.842704348 atn c2
A96A EQUB &80, &CA, &9A, &0E, &83 ; float40 -0.7914132185 atn c3
A96F EQUB &84, &8C, &BB, &CA, &6E ; float40 -8.795847349 atn c4
A974 EQUB &81, &95, &96, &06, &DE ; float40 -1.168640955 atn c5
A979 EQUB &81, &0A, &C7, &6C, &52 ; float40 1.084210911 atn c6
A97E EQUB &7F, &7D, &AD, &90, &A1 ; float40 0.4954648205 atn c7
A983 EQUB &82, &FB, &62, &57, &2F ; float40 -3.927877232 atn c8
A988 EQUB &80, &6D, &63, &38, &2C ; float40 0.9272952182

COS

FWA = cos(FWA), argument in radians. Pure routine at &A990.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
A98D .fn_cos
JSR eval_real ; Evaluate the argument as a real
A990 JSR sin_cos_reduce ; Compute the SIN/COS kernel
A993 INC zp_int_exp ; Shift the quadrant: cos x = sin(x + pi/2)
A995 JMP sin_quadrant ; Finish (shared with SIN)

SIN

FWA = sin(FWA), argument in radians. Pure routine at &A99B.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
A998 .fn_sin
JSR eval_real ; Evaluate the argument as a real
A99B JSR sin_cos_reduce ; Range-reduce: leaves the angle in FWA, quadrant in &4A
A99E .sin_quadrant←3← A6CF JSR← A6DB JSR← A995 JMP
LDA zp_int_exp ; Quadrant
A9A0 AND #2 ; second or third quadrant (result negative)?
A9A2 BEQ sin_odd_quad ; no: compute and return directly
A9A4 JSR sin_odd_quad ; compute the magnitude...
A9A7 JMP fwa_negate ; ...then negate it
A9AA .sin_odd_quad←2← A9A2 BEQ← A9A4 JSR
LSR zp_int_exp ; Odd quadrant (use cosine instead of sine)?
A9AC BCC sin_save_r ; even: evaluate the sine series
A9AE JSR sin_save_r ; odd: evaluate the sine series...
A9B1 .sin_series←1← A8ED JSR
JSR fwa_pack_temp1 ; Save sin into TEMP1
A9B4 JSR fwa_mul_var ; FWA = sin^2
A9B7 JSR fwa_pack_var ; save sin^2 to the work var
A9BA JSR fwa_set_one ; FWA = 1
A9BD JSR fwa_sub_var ; FWA = 1 - sin^2
A9C0 JMP sqr_sign ; cos = sqrt(1 - sin^2)
A9C3 .sin_save_r←2← A9AC BCC← A9AE JSR
JSR fwa_pack_temp3 ; Save the reduced angle r in TEMP3
A9C6 JSR fwa_mul_var ; FWA = r^2
A9C9 LDA #&72 ; Point at the sine coefficient table: low byte
A9CB LDY #&aa ; ...high
A9CD JSR fp_eval_cont_frac ; Evaluate the sine continued fraction in r^2
A9D0 JMP mul_by_temp3 ; Scale by r (in TEMP3): sin(r) = r * P

Range-reduce the SIN/COS argument

Divide the argument by pi/2 to find the quadrant (stored in &4A) and Cody-Waite reduce to the principal range using the two-part pi/2 constant, leaving the reduced angle in FWA for the series. Errors if the argument is too large to reduce.

On EntryZP_FWA (&2E-&35)the angle in radians
On ExitZP_FWA (&2E-&35)the reduced angle
(&4A)the quadrant (0-3)
BRKon an argument too large to reduce
A9D3 .sin_cos_reduce←3← A6C1 JSR← A990 JSR← A99B JSR
LDA zp_fwa_exp ; Exponent of the argument
A9D5 CMP #&98 ; too large to reduce accurately?
A9D7 BCS reduce_too_big ; yes: report the error
A9D9 JSR fwa_pack_temp1 ; Save the argument in TEMP1
A9DC JSR point_const_half_pi ; Point at pi/2...
A9DF JSR fwb_unpack_var ; ...and unpack it into FWB
A9E2 LDA zp_fwa_sign ; Take the argument sign...
A9E4 STA zp_fwb_sign ; ...for FWB
A9E6 DEC zp_fwb_exp ; Halve FWB to +/- pi/4 (rounding bias)
A9E8 JSR fwa_add_fwb ; FWA = argument +/- pi/4
A9EB JSR fp_divide ; FWA = that / (pi/2): integer part is the quadrant
A9EE JSR fwa_to_int2 ; Take the integer part
A9F1 LDA zp_fwa_m4 ; Low byte of the quadrant count
A9F3 STA zp_int_exp ; save it in &4A
A9F5 ORA zp_fwa_m3 ; Is the whole quadrant count zero?
A9F7 ORA zp_fwa_m2 ; or byte 2,
A9F9 ORA zp_fwa_m1 ; or byte 1
A9FB BEQ reduce_in_range ; yes: argument already in range, use it as is
A9FD LDA #&a0 ; Rebuild the quadrant count as a real: exponent
A9FF STA zp_fwa_exp ; set it
AA01 LDY #0 ; clear the rounding byte
AA03 STY zp_fwa_rnd ; store it
AA05 LDA zp_fwa_m1 ; Sign of the count
AA07 STA zp_fwa_sign ; into the sign byte
AA09 BPL reduce_norm_quad ; positive: skip
AA0B JSR fti2_negate ; negative: negate the mantissa
AA0E .reduce_norm_quad←1← AA09 BPL
JSR fwa_normalise ; Normalise the quadrant count
AA11 JSR fwa_pack_temp2 ; Save it in TEMP2
AA14 JSR point_half_pi_hi ; Cody-Waite reduce: FWA = count * (pi/2 high part)
AA17 JSR fwa_mul_var ; multiply by it
AA1A JSR point_fp_temp1 ; FWA = argument - count*(pi/2 high)
AA1D JSR fwa_add_var ; add the argument
AA20 JSR fwa_pack_var ; Save the partial reduction in TEMP1
AA23 JSR point_fp_temp2 ; Reload the quadrant count
AA26 JSR fwa_unpack_var ; into FWA
AA29 JSR point_half_pi_lo ; FWA = count * (pi/2 low part)
AA2C JSR fwa_mul_var ; multiply by it
AA2F JSR point_fp_temp1 ; Subtract the low part too: FWA = reduced angle
AA32 JMP fwa_add_var ; add it (tail call)
AA35 .reduce_in_range←1← A9FB BEQ
JMP fwa_unpack_temp1 ; Argument in range: reduced angle is the argument
AA38 .reduce_too_big←1← A9D7 BCS
BRK ; Argument too large: error
AA39 EQUB &17
AA3A EQUS "Accuracy lost"
AA47 EQUB &00

Point the fp pointer at the high part of pi/2 (&AA59)

Set zp_fp_ptr to the high half of the extended-precision pi/2 constant, ready to unpack.

On ExitZP_FP_PTR (&4B/&4C)= &AA59
Xpreserved
Ypreserved
AA48 .point_half_pi_hi←2← A927 JSR← AA14 JSR
LDA #&59 ; High part of pi/2: low byte
AA4A BNE set_pi_ptr ; ...(shared tail)
fall through ↓

Point the fp pointer at the low part of pi/2 (&AA5E)

Set zp_fp_ptr to the low half of the extended-precision pi/2 constant, ready to unpack.

On ExitZP_FP_PTR (&4B/&4C)= &AA5E
Xpreserved
Ypreserved
AA4C .point_half_pi_lo←2← A92D JSR← AA29 JSR
LDA #&5e ; Low part of pi/2: low byte
AA4E .set_pi_ptr←2← AA4A BNE← AA57 BNE
STA zp_fp_ptr ; Set the fp pointer low
AA50 LDA #&aa ; high &AA
AA52 STA zp_fp_ptr_1 ; pointer high
AA54 RTS ; Return

Point the fp pointer at the pi/2 constant (&AA63)

Set zp_fp_ptr to the packed pi/2 constant, ready to unpack (used by the range reduction in SIN/COS).

On ExitZP_FP_PTR (&4B/&4C)= &AA63
Xpreserved
Ypreserved
AA55 .point_const_half_pi←2← A8FE JSR← A9DC JSR
LDA #&63 ; pi/2 constant: low byte
AA57 BNE set_pi_ptr ; ...(shared tail)
AA59 EQUB &81, &C9, &10, ; float40 -1.570800781 pi/2, high part &00, &00 ; (Cody-Waite)
AA5E EQUB &6F, &15, &77, ; float40 4.454455111e-06 pi/2, low &7A, &61 ; part (Cody-Waite)
AA63 EQUB &81, &49, &0F, &DA, &A2 ; float40 1.570796327 pi/2
AA68 EQUB &7B, &0E, &FA, ; float40 0.01745329252 pi/180 (degrees &35, &12 ; -> radians, RAD)
AA6D EQUB &86, &65, &2E, ; float40 57.29577951 180/pi (radians &E0, &D3 ; -> degrees, DEG)
AA72 EQUB &05
AA73 EQUB &84, &8A, &EA, &0C, &1B ; float40 -8.682140451 sin c0
AA78 EQUB &84, &1A, &BE, &BB, &2B ; float40 9.671565216 sin c1
AA7D EQUB &84, &37, &45, &55, &AB ; float40 11.4544274 sin c2
AA82 EQUB &82, &D5, &55, &57, &7C ; float40 -3.333333846 sin c3
AA87 EQUB &83, &C0, &00, &00, &05 ; float40 -6.000000009 sin c4
AA8C EQUB &81, &00, &00, &00, &00 ; float40 1 1.0
fall through ↓

EXP

FWA = e to the power FWA. Pure routine at &AA94.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AA91 .fn_exp
JSR eval_real ; Evaluate the argument as a real
AA94 .exp_compute←1← 9E7B JSR
LDA zp_fwa_exp ; Exponent of x
AA96 CMP #&87 ; x small enough to compute directly?
AA98 BCC exp_split ; yes: compute
AA9A BNE exp_sign ; much too large: handle the overflow case
AA9C LDY zp_fwa_m1 ; borderline: test the mantissa
AA9E CPY #&b3 ; ...against the overflow threshold
AAA0 BCC exp_split ; below it: still computable
AAA2 .exp_sign←1← AA9A BNE
LDA zp_fwa_sign ; Sign of x
AAA4 BPL exp_range_error ; x very negative: e^x underflows to 0
AAA6 JSR fwa_clear ; FWA = 0
AAA9 LDA #&ff ; real result
AAAB RTS ; Return
AAAC .exp_range_error←1← AAA4 BPL
BRK ; x very positive: Exp range error
AAAD EQUB &18
AAAE EQUS "Exp range"
AAB7 EQUB &00
AAB8 .exp_split←2← AA98 BCC← AAA0 BCC
JSR fp_split_int_frac ; Split x into integer (&4A) and fraction
AABB JSR exp_coeff_table ; FWA = e^frac via the series
AABE JSR fwa_pack_temp3 ; Save e^frac in TEMP3
AAC1 LDA #&e4 ; Point at the constant e: low byte
AAC3 STA zp_fp_ptr ; store the pointer low
AAC5 LDA #&aa ; high byte
AAC7 STA zp_fp_ptr_1 ; store the pointer high
AAC9 JSR fwa_unpack_var ; FWA = e
AACC LDA zp_int_exp ; Integer part of x
AACE JSR fwa_int_power ; FWA = e^int
AAD1 .mul_by_temp3←3← 9E78 JSR← A954 JSR← A9D0 JMP
JSR point_fp_temp3 ; Point at the saved argument in TEMP3
AAD4 JSR fwa_mul_var ; FWA = series * argument
AAD7 LDA #&ff ; real result
AAD9 RTS ; Return
AADA .exp_coeff_table←1← AABB JSR
LDA #&e9 ; Point at the coefficient table: low byte
AADC LDY #&aa ; ...high
AADE JSR fp_eval_cont_frac ; Evaluate the continued fraction
AAE1 LDA #&ff ; real result
AAE3 RTS ; Return
AAE4 EQUB &82, &2D, &F8, &54, &58 ; float40 2.718281828 e
AAE9 EQUB &07
AAEA EQUB &83, &E0, &20, &86, &5B ; float40 -7.003970316 exp c0
AAEF EQUB &82, &80, &53, &93, &B8 ; float40 -2.005101137 exp c1
AAF4 EQUB &83, &20, &00, &06, &A1 ; float40 5.000003161 exp c2
AAF9 EQUB &82, &00, &00, &21, &63 ; float40 2.00000796 exp c3
AAFE EQUB &82, &C0, &00, &00, &02 ; float40 -3.000000002 exp c4
AB03 EQUB &82, &80, &00, &00, &0C ; float40 -2.000000011 exp c5
AB08 EQUB &81, &00, &00, &00, &00 ; float40 1 exp c6
AB0D EQUB &81, &00, &00, &00, &00 ; float40 1 1.0

FWA = FWA ^ n (n in A)

Raise FWA to an integer power by repeated multiplication; a negative exponent reciprocates first. Used by EXP to form e^(integer part).

On EntryAthe signed integer exponent n
ZP_FWA (&2E-&35)the base
On ExitZP_FWA (&2E-&35)FWA raised to the n-th power
ZP_FWB (&3B-&42)corrupted (scratch)
AB12 .fwa_int_power←3← 9E52 JSR← 9E69 JSR← AACE JSR
TAX ; Exponent n
AB13 BPL intpow_loop ; positive?
AB15 DEX ; negative: use |n| and reciprocate
AB16 TXA ; n-1 into A
AB17 EOR #&ff ; complement to give |n|
AB19 PHA ; save the count
AB1A JSR fwa_reciprocal ; FWA = 1 / FWA
AB1D PLA ; restore the count
AB1E .intpow_loop←1← AB13 BPL
PHA ; Save the count
AB1F JSR fwa_pack_temp1 ; Stash the base in TEMP1
AB22 JSR fwa_set_one ; FWA = 1 (running product)
AB25 .intpow_check←1← AB2F JMP
PLA ; Count remaining
AB26 BEQ return_29 ; zero: done
AB28 SEC ; One multiplication fewer
AB29 SBC #1 ; decrement the count
AB2B PHA ; save it back
AB2C JSR fwa_mul_var ; FWA = FWA * base
AB2F JMP intpow_check ; loop
AB32 .return_29←1← AB26 BEQ
RTS ; Return

ADVAL

Read an analogue (A/D) channel or a buffer status. ADVAL numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AB33 .fn_adval
JSR eval_factor_integer ; Evaluate the integer argument
AB36 LDX zp_iwa ; X = the channel / buffer number
AB38 LDA #osbyte_read_adc_or_get_buffer_status ; OSBYTE &80: read ADC / buffer status
AB3A JSR osbyte ; Read ADC channel X or buffer status
AB3D TXA ; result low byte (X)
AB3E JMP iwa_from_ya ; Return as an integer

POINT

Logical colour of a graphics point. POINT(x, y).

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AB41 .fn_point
JSR eval_expr_integer ; Evaluate x
AB44 JSR stack_integer ; stack it
AB47 JSR skip_spaces_expect_comma ; Expect a comma
AB4A JSR eval_subexpr ; Evaluate y, expect )
AB4D JSR coerce_to_integer ; coerce to integer
AB50 LDA zp_iwa ; Save y
AB52 PHA ; push the low byte
AB53 LDA zp_iwa_1 ; high byte
AB55 PHA ; push it
AB56 JSR unstack_integer ; Recover x
AB59 PLA ; pull y high
AB5A STA zp_iwa_3 ; into the OSWORD block
AB5C PLA ; pull y low
AB5D STA zp_iwa_2 ; y low into the block
AB5F LDX #&2a ; point X at the block (&2A)
AB61 LDA #osword_read_pixel ; Read the pixel colour
AB63 JSR osword ; Read pixel value
AB66 LDA zp_fwa_sign ; Result sign (off-screen = -1)
AB68 BMI sgn_negative ; off-screen?
AB6A JMP int_result_a ; return as an integer

POS

Horizontal text-cursor position in the window. POS.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AB6D .fn_pos
LDA #osbyte_read_text_cursor_pos ; OSBYTE &86: read the text cursor position
AB6F JSR osbyte ; Read input cursor position (Sets X=POS and Y=VPOS)
AB72 TXA ; X is the horizontal text position ('POS') / Y is the vertical text position ('VPOS')
AB73 JMP int_result_a ; Return the column as an integer

VPOS

Vertical text-cursor position in the window. VPOS.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AB76 .fn_vpos
LDA #osbyte_read_text_cursor_pos ; OSBYTE &86: read the cursor position
AB78 JSR osbyte ; Read input cursor position (Sets X=POS and Y=VPOS)
AB7B TYA ; X is the horizontal text position ('POS') / Y is the vertical text position ('VPOS')
AB7C JMP int_result_a ; Return the row (Y) as an integer
AB7F .sgn_real_loop←1← AB8D BMI
JSR fwa_sign ; Sign of the real
AB82 BEQ sgn_return ; zero: 0
AB84 BPL sgn_positive ; positive: 1
AB86 BMI sgn_negative ; negative: -1
fall through ↓

SGN

Sign of a number: -1, 0 or +1. SGN numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AB88 .fn_sgn
JSR eval_factor ; Evaluate the argument
AB8B BEQ usr_type_error ; string: error
AB8D BMI sgn_real_loop ; real: use the FP sign
AB8F LDA zp_iwa_3 ; Integer: test for zero
AB91 ORA zp_iwa_2 ; OR &2C,
AB93 ORA zp_iwa_1 ; &2B,
AB95 ORA zp_iwa ; &2A (all zero => SGN 0)
AB97 BEQ sgn_zero ; zero: 0
AB99 LDA zp_iwa_3 ; Sign bit
AB9B BPL sgn_positive ; positive: 1
AB9D .sgn_negative←2← AB68 BMI← AB86 BMI
JMP fn_true ; negative: -1
ABA0 .sgn_positive←2← AB84 BPL← AB9B BPL
LDA #1 ; Result = 1
ABA2 .sgn_return←1← AB82 BEQ
JMP int_result_a ; return it
ABA5 .sgn_zero←1← AB97 BEQ
LDA #&40 ; Result = 0 (integer)
ABA7 RTS ; Return

LOG

FWA = base-10 log of FWA. Pure routine at &ABAB.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ABA8 .fn_log
JSR fn_ln ; FWA = ln(x)
ABAB LDY #&69 ; Point at the constant log10(e): low byte
ABAD LDA #&a8 ; high byte
ABAF BNE degrad_set_ptr ; FWA = ln(x) * log10(e)
fall through ↓

RAD

FWA = FWA degrees expressed in radians. Pure routine at &ABB4.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ABB1 .fn_rad
JSR eval_real ; Evaluate the argument as a real
ABB4 LDY #&68 ; Point at the constant pi/180: low byte
ABB6 LDA #&aa ; high byte
ABB8 .degrad_set_ptr←2← ABAF BNE← ABC9 BNE
STY zp_fp_ptr ; Set the fp pointer low
ABBA STA zp_fp_ptr_1 ; ...and high
ABBC JSR fwa_mul_var ; FWA = x * pi/180
ABBF LDA #&ff ; Flag a non-zero real result
ABC1 RTS ; Return

DEG

FWA = FWA radians expressed in degrees. Pure routine at &ABC5.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ABC2 .fn_deg
JSR eval_real ; Evaluate the argument as a real
ABC5 LDY #&6d ; Point at the constant 180/pi: low byte
ABC7 LDA #&aa ; high byte
ABC9 BNE degrad_set_ptr ; Multiply FWA by it (radians -> degrees)
fall through ↓

PI

FWA = pi (3.14159265). Takes no argument.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ABCB .fn_pi
JSR asn_one ; Load the constant pi/2 into FWA
ABCE INC zp_fwa_exp ; Double it (exponent + 1) to get pi
ABD0 TAY ; Flag a non-zero real result
ABD1 RTS ; FWA = pi

USR

Call machine code and return the result registers packed into a value. USR address.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ABD2 .fn_usr
JSR eval_factor_integer ; Evaluate the address argument as an integer
ABD5 JSR usr_call ; Set up registers and call the code
ABD8 STA zp_iwa ; Returned A...
ABDA STX zp_iwa_1 ; ...and X into the low result bytes
ABDC STY zp_iwa_2 ; Returned Y
ABDE PHP ; Returned flags...
ABDF PLA ; pull them into A
ABE0 STA zp_iwa_3 ; ...into the top result byte
ABE2 CLD ; Clear decimal mode the code may have left set
ABE3 LDA #&40 ; Integer result
ABE5 RTS ; Return the packed A,X,Y,P
ABE6 .usr_type_error←2← AB8B BEQ← ABEC BNE
JMP err_type_mismatch ; Non-numeric address: Type mismatch

EVAL

Tokenise a string and evaluate it as a full BASIC expression. The argument is copied onto the stack, given a CR, tokenised in place (mid-statement, so PTR/TIME etc. tokenise as functions, not assignment targets) and run through the expression evaluator - so it accepts everything the evaluator does: "&" hex (factor_hex), "E"-exponent decimals, operators, parentheses, functions and in-scope variables. Hence EVAL("&FF") is 255 where VAL("&FF") is 0, though both read "1E3" as 1000. EVAL string.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ABE9 .fn_eval
JSR eval_factor ; Evaluate the argument string
ABEC BNE usr_type_error ; not a string: error
ABEE INC zp_strbuf_len ; Append a CR terminator
ABF0 LDY zp_strbuf_len ; index the new last byte,
ABF2 LDA #&0d ; CR,
ABF4 STA strbuf_base,y ; append it
ABF7 JSR stack_string ; Stack the string
ABFA LDA zp_text_ptr2 ; Save the parser pointer
ABFC PHA ; push low,
ABFD LDA zp_text_ptr2_1 ; high,
ABFF PHA ; push high,
AC00 LDA zp_text_ptr2_off ; offset,
AC02 PHA ; push offset
AC03 LDY zp_stack_ptr ; Point at the stacked string
AC05 LDX zp_stack_ptr_1 ; high byte in X
AC07 INY ; step over the length byte
AC08 STY zp_text_ptr2 ; parser pointer low
AC0A STY zp_general ; name pointer low
AC0C BNE eval_ptr_hi ; no carry into the high byte
AC0E INX ; carry into the high byte
AC0F .eval_ptr_hi←1← AC0C BNE
STX zp_text_ptr2_1 ; parser pointer high
AC11 STX zp_general_1 ; name pointer high
AC13 LDY #&ff ; Tokenise as mid-statement (&3B = &FF):
AC15 STY zp_fwb_sign ; so PTR etc. tokenise as functions, not assignments
AC17 INY ; Offset back to the start
AC18 STY zp_text_ptr2_off ; offset = 0 (start of string)
AC1A JSR tokenise_resume ; Tokenise the stacked string
AC1D JSR eval_or_eor ; Evaluate the expression
AC20 JSR drop_stacked_string ; Drop the stacked string
AC23 .eval_restore_ptr←1← AC75 JMP
PLA ; Restore the parser pointer
AC24 STA zp_text_ptr2_off ; the offset,
AC26 PLA ; pull the high byte,
AC27 STA zp_text_ptr2_1 ; high,
AC29 PLA ; pull the low byte,
AC2A STA zp_text_ptr2 ; low
AC2C LDA zp_var_type ; Result type
AC2E RTS ; Return

VAL

Read a number from the start of a string, through the decimal reader only (ascii_to_number then parse_number): an optional leading sign, digits, a "." and an "E" exponent. It does NOT read "&" hex, so VAL("&FF") is 0; it stops at the first non-numeric character (VAL("12AB") is 12) and yields 0 when no number leads. Returns an integer (IWA) or a real (FWA). Contrast EVAL, which evaluates the whole string. VAL string.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AC2F .fn_val
JSR eval_factor ; Evaluate the argument
AC32 BNE num_type_error ; not a string: error
fall through ↓

Convert an ASCII number to a value

Convert the ASCII number in the string work area to a value (an integer in IWA or a real in FWA) — the decimal/float reader behind VAL and numeric tokenising. It accepts an optional leading sign, then defers to parse_number for the digits, "." and "E" exponent. It is decimal only: there is no "&" hex branch here (that is factor_hex, reached from eval_factor), so a string led by "&" reads no digits and yields 0, and scanning stops at the first non-numeric character. A binary zero is appended to the SWA first.

On EntryZP_STRBUF_LEN (&36)length of the ASCII number in the SWA
STRING WORK AREA (&0600)the ASCII number
On ExitZP_IWA / ZP_FWAthe result (integer or real)
A, ZP_VAR_TYPE (&27)type: &40 = integer, &FF = real
AC34 .ascii_to_number←1← BAD3 JSR
LDY zp_strbuf_len ; NUL-terminate the SWA string
AC36 LDA #0 ; the zero byte
AC38 STA string_work,y ; store it past the digits
AC3B LDA zp_text_ptr2 ; Save PtrB (we point it at the SWA):
AC3D PHA ; push the low byte
AC3E LDA zp_text_ptr2_1 ; high byte
AC40 PHA ; push it
AC41 LDA zp_text_ptr2_off ; and the offset
AC43 PHA ; push it
AC44 LDA #0 ; Point PtrB at the SWA (&0600): offset 0
AC46 STA zp_text_ptr2_off ; set the offset
AC48 LDA #0 ; low 0
AC4A STA zp_text_ptr2 ; set the low byte
AC4C LDA #6 ; high &06
AC4E STA zp_text_ptr2_1 ; set the high byte
AC50 JSR skip_spaces_ptr2 ; skip spaces
AC53 CMP #'-' ; minus sign?
AC55 BEQ a2n_negative ; negative number
AC57 CMP #'+' ; plus sign?
AC59 BNE a2n_back ; no sign
AC5B JSR skip_spaces_ptr2 ; skip the plus
AC5E .a2n_back←1← AC59 BNE
DEC zp_text_ptr2_off ; step back
AC60 JSR parse_number ; parse the number
AC63 JMP a2n_store_type ; finish
AC66 .a2n_negative←1← AC55 BEQ
JSR skip_spaces_ptr2 ; negative: skip the minus
AC69 DEC zp_text_ptr2_off ; step back
AC6B JSR parse_number ; parse the number
AC6E BCC a2n_store_type ; integer: done
AC70 JSR fneg_zero ; real: negate it
AC73 .a2n_store_type←2← AC63 JMP← AC6E BCC
STA zp_var_type ; store the result type
AC75 JMP eval_restore_ptr ; restore PtrB and return

INT

Integer part (floor) of a number. INT numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AC78 .fn_int
JSR eval_factor ; Evaluate the argument
AC7B BEQ num_type_error ; string: error
AC7D BPL return_30 ; already integer: return it
AC7F LDA zp_fwa_sign ; Real: remember the sign
AC81 PHP ; save the sign flags
AC82 JSR fwa_to_int2 ; Take the integer part
AC85 PLP ; sign
AC86 BPL int_to_iwa ; positive: no adjustment
AC88 LDA zp_fwb_m1 ; Negative: any fractional bits?
AC8A ORA zp_fwb_m2 ; OR &3F,
AC8C ORA zp_fwb_m3 ; &40,
AC8E ORA zp_fwb_m4 ; &41 (any set => not exact)
AC90 BEQ int_to_iwa ; none: exact
AC92 JSR negate_mantissa ; round down (floor of a negative)
AC95 .int_to_iwa←2← AC86 BPL← AC90 BEQ
JSR mantissa_to_iwa ; Copy the integer to IWA
AC98 LDA #&40 ; integer result
AC9A .return_30←1← AC7D BPL
RTS ; Return
AC9B .num_type_error←5← AC32 BNE← AC7B BEQ← ACA1 BNE← ACE5 BNE← ACF3 BNE
JMP err_type_mismatch ; Type mismatch error

ASC

ASCII code of the first character of a string, or -1 if empty. ASC string.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AC9E .fn_asc
JSR eval_factor ; Evaluate the string
ACA1 BNE num_type_error ; not a string: error
ACA3 LDA zp_strbuf_len ; String length
ACA5 BEQ fn_true ; empty: -1
ACA7 LDA string_work ; First character
ACAA .asc_result←1← ACC2 BEQ
JMP int_result_a ; return it

INKEY

Read a key within a time limit, test a key, or read the machine ID. INKEY numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ACAD .fn_inkey
JSR read_key_timed ; Read a key within the time limit
ACB0 CPY #0 ; timed out?
ACB2 BNE fn_true ; no key: return -1 (TRUE)
ACB4 TXA ; X = the key code
ACB5 JMP iwa_from_ya ; Return the key code as an integer

EOF

TRUE when at the end of an open file. EOF#channel.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ACB8 .fn_eof
JSR eval_channel ; Evaluate the channel number
ACBB TAX ; Channel to X
ACBC LDA #osbyte_check_eof ; OSBYTE &7F: test for end of file
ACBE JSR osbyte ; Check for end-of-file on file handle Y
ACC1 TXA ; X=0 means EOF reached, X=&FF means data remaining
ACC2 BEQ asc_result ; Return TRUE/FALSE per the EOF flag
fall through ↓

TRUE

The constant TRUE (-1). Sets IWA = -1; this is also the ineg1 integer primitive.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ACC4 .fn_true←3← AB9D JMP← ACA5 BEQ← ACB2 BNE
LDA #&ff ; TRUE is -1: load &FF into every IWA byte
ACC6 STA zp_iwa ; byte 0
ACC8 STA zp_iwa_1 ; byte 1
ACCA STA zp_iwa_2 ; byte 2
ACCC STA zp_iwa_3 ; byte 3: IWA = -1
ACCE LDA #&40 ; Result type = integer
ACD0 RTS ; Return TRUE

NOT

Bitwise NOT (one's complement) of an integer. NOT numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ACD1 .fn_not
JSR eval_factor_integer ; Evaluate the argument as an integer
ACD4 LDX #3 ; Complement all four IWA bytes
ACD6 .not_loop←1← ACDD BPL
LDA zp_iwa,x ; byte X...
ACD8 EOR #&ff ; ...one's complement
ACDA STA zp_iwa,x ; (store)
ACDC DEX ; next byte
ACDD BPL not_loop ; loop
ACDF LDA #&40 ; Integer result
ACE1 RTS ; Return NOT value

INSTR

Position of one string within another, optionally from a start. INSTR(a$, b$ [,n]).

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
ACE2 .fn_instr
JSR eval_or_eor ; Evaluate the searched string
ACE5 BNE num_type_error ; not a string: Type mismatch
ACE7 CPX #',' ; ','?
ACE9 BNE instr_missing_comma ; no: Missing ,
ACEB INC zp_text_ptr2_off ; step past
ACED JSR stack_string ; Stack the searched string
ACF0 JSR eval_or_eor ; Evaluate the search string
ACF3 BNE num_type_error ; not a string: Type mismatch
ACF5 LDA #1 ; Default start position = 1
ACF7 STA zp_iwa ; store as the start position
ACF9 INC zp_text_ptr2_off ; step past
ACFB CPX #')' ; ')' (no start given)?
ACFD BEQ instr_dest_index ; yes: search from 1
ACFF CPX #',' ; ','?
AD01 BEQ instr_stack ; yes: a start position follows
AD03 .instr_missing_comma←1← ACE9 BNE
JMP missing_comma ; Missing , error
AD06 .instr_stack←1← AD01 BEQ
JSR stack_string ; Stack the search string
AD09 JSR eval_subexpr ; Evaluate the start, expect )
AD0C JSR coerce_to_integer ; coerce to integer
AD0F JSR unstack_string ; Restore the search string
AD12 .instr_dest_index←1← ACFD BEQ
LDY #0 ; Destination index
AD14 LDX zp_iwa ; Start position
AD16 BNE instr_match_pos ; non-zero?
AD18 LDX #1 ; force at least 1
AD1A .instr_match_pos←1← AD16 BNE
STX zp_iwa ; Current match position
AD1C TXA ; Zero-based start = start - 1
AD1D DEX ; start - 1,
AD1E STX zp_iwa_3 ; save the zero-based start
AD20 CLC ; Point at s$ + (start-1)
AD21 ADC zp_stack_ptr ; + stack pointer low,
AD23 STA zp_general ; pointer low,
AD25 TYA ; A = 0 (from Y),
AD26 ADC zp_stack_ptr_1 ; + stack pointer high,
AD28 STA zp_general_1 ; pointer high
AD2A LDA (zp_stack_ptr),y ; Length of s$
AD2C SEC ; minus the start offset...
AD2D SBC zp_iwa_3 ; len(s$) - (start-1)
AD2F BCC instr_not_found ; start beyond the end: not found
AD31 SBC zp_strbuf_len ; minus the search length
AD33 BCC instr_not_found ; won't fit: not found
AD35 ADC #0 ; Number of start positions to try
AD37 STA zp_iwa_1 ; positions to try
AD39 JSR drop_stacked_string ; Re-point at the stacked string
AD3C .instr_compare←2← AD61 BNE← AD65 BNE
LDY #0 ; Compare from index 0
AD3E LDX zp_strbuf_len ; Search length
AD40 BEQ instr_match ; empty search: matches here
AD42 .instr_cmp_loop←1← AD4B BNE
LDA (zp_general),y ; s$ character
AD44 CMP string_work,y ; vs search character
AD47 BNE instr_advance ; mismatch: advance
AD49 INY ; next
AD4A DEX ; one fewer char to match
AD4B BNE instr_cmp_loop ; all matched?
AD4D .instr_match←1← AD40 BEQ
LDA zp_iwa ; Match: result is the position
AD4F .instr_result←1← AD57 BEQ
JMP int_result_a ; return it as an integer
AD52 .instr_not_found←2← AD2F BCC← AD33 BCC
JSR drop_stacked_string ; Not found: drop the stacked string
AD55 .instr_zero←1← AD5D BEQ
LDA #0 ; result = 0
AD57 BEQ instr_result ; return it
AD59 .instr_advance←1← AD47 BNE
INC zp_iwa ; Advance the match position
AD5B DEC zp_iwa_1 ; one fewer position to try
AD5D BEQ instr_zero ; exhausted: not found
AD5F INC zp_general ; advance the s$ pointer
AD61 BNE instr_compare ; retry
AD63 INC zp_general_1 ; carry into the high byte
AD65 BNE instr_compare ; retry
AD67 .instr_type_error←2← AD6D BEQ← AD8F BEQ
JMP err_type_mismatch ; Type mismatch error

ABS

Absolute value of a number. ABS numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AD6A .fn_abs
JSR eval_factor ; Evaluate the argument
AD6D BEQ instr_type_error ; zero: return it
AD6F BMI abs_real ; real: clear the sign
fall through ↓

Make the integer accumulator positive

IWA = ABS(IWA).

On EntryZP_IWA (&2A)32-bit integer
On ExitZP_IWAmade positive
Xpreserved
AD71 .iwa_abs←4← 99C5 JSR← 99D8 JSR← 9D70 JSR← 9D80 JSR
BIT zp_iwa_3 ; Test the sign of IWA (top byte)
AD73 BMI iwa_negate ; Negative: negate it
AD75 BPL ineg_done ; Positive: leave it
AD77 .abs_real←1← AD6F BMI
JSR fwa_sign ; ABS of a real: get the sign
AD7A BPL fneg_done ; positive/zero: done
AD7C BMI fneg_toggle ; negative: clear the sign
fall through ↓

FWA = -FWA

Negate FWA by flipping the sign bit (a zero value is left unchanged).

On EntryZP_FWA (&2E-&35)the value to negate
On ExitZP_FWA_SIGN (&2E)sign bit toggled (unless zero)
Areal type marker (&FF)
AD7E .fwa_negate←5← A4D3 JMP← A4FD JSR← A933 JMP← A9A7 JMP← AD91 BMI
JSR fwa_sign ; Is FWA zero?
AD81 BEQ fneg_done ; zero: nothing to negate
AD83 .fneg_toggle←1← AD7C BMI
LDA zp_fwa_sign ; Toggle the sign bit...
AD85 EOR #&80 ; flip bit 7
AD87 STA zp_fwa_sign ; (store)
AD89 .fneg_done←2← AD7A BPL← AD81 BEQ
LDA #&ff ; real result type
AD8B RTS ; Return
AD8C .unary_minus←1← ADF8 BEQ
JSR factor_unary_plus ; Unary minus: evaluate the operand
AD8F .fneg_zero←1← AC70 JSR
BEQ instr_type_error ; zero: leave it
AD91 BMI fwa_negate ; real: negate FWA
fall through ↓

Negate the integer accumulator

IWA = -IWA (two's-complement negate the 32-bit integer).

On EntryZP_IWA (&2A)32-bit integer
On ExitZP_IWAnegated
Xpreserved (A, Y, P destroyed)
AD93 .iwa_negate←3← 9DC3 JSR← A2C8 JSR← AD73 BMI
SEC ; Negate IWA: compute 0 - IWA
AD94 LDA #0 ; from zero
AD96 TAY ; Y = 0 for the subtractions
AD97 SBC zp_iwa ; byte 0
AD99 STA zp_iwa ; (store)
AD9B TYA ; 0...
AD9C SBC zp_iwa_1 ; byte 1
AD9E STA zp_iwa_1 ; (store)
ADA0 TYA ; 0...
ADA1 SBC zp_iwa_2 ; byte 2
ADA3 STA zp_iwa_2 ; (store)
ADA5 TYA ; 0...
ADA6 SBC zp_iwa_3 ; byte 3
ADA8 STA zp_iwa_3 ; (store)
ADAA .ineg_done←1← AD75 BPL
LDA #&40 ; integer result
ADAC RTS ; Return -IWA

Read a string literal

Read a quoted ("...", with "" for a literal quote) or unquoted string at PtrB into the string buffer.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the string
On ExitSTRING_WORK (&0600)the string characters
ZP_STRBUF_LEN (&36)the string length
ZP_TEXT_PTR2_OFF (&1B)advanced past the literal
ADAD .read_string_literal←2← BABA JSR← BB38 JSR
JSR skip_spaces_ptr2 ; Read a string literal: skip spaces
ADB0 CMP #'"' ; a quote?
ADB2 BEQ rsl_quoted ; quoted string
ADB4 LDX #0 ; Unquoted: read until CR or comma
ADB6 .rsl_unquoted_loop←1← ADC3 BNE
LDA (zp_text_ptr2),y ; char
ADB8 STA string_work,x ; into the buffer
ADBB INY ; next
ADBC INX ; and the buffer index
ADBD CMP #&0d ; CR?
ADBF BEQ rsl_unquoted_end ; end
ADC1 CMP #',' ; comma?
ADC3 BNE rsl_unquoted_loop ; loop
ADC5 .rsl_unquoted_end←1← ADBF BEQ
DEY ; step back
ADC6 JMP rsl_drop_trail ; finish
ADC9 .rsl_quoted←2← ADB2 BEQ← ADFC BEQ
LDX #0 ; Quoted: read until the closing quote
ADCB .rsl_quoted_adv←1← ADDF BEQ
INY ; advance
ADCC .rsl_quoted_loop←1← ADD9 BNE
LDA (zp_text_ptr2),y ; char
ADCE CMP #&0d ; CR (unterminated)?
ADD0 BEQ rsl_missing_quote ; Missing " error
ADD2 INY ; next
ADD3 STA string_work,x ; into the buffer
ADD6 INX ; advance the buffer index
ADD7 CMP #'"' ; a quote?
ADD9 BNE rsl_quoted_loop ; no: keep copying
ADDB LDA (zp_text_ptr2),y ; doubled "" = a literal quote?
ADDD CMP #'"' ; also a quote?
ADDF BEQ rsl_quoted_adv ; yes: keep it
ADE1 .rsl_drop_trail←1← ADC6 JMP
DEX ; drop the trailing character
ADE2 STX zp_strbuf_len ; set the string length
ADE4 STY zp_text_ptr2_off ; update the text offset
ADE6 LDA #0 ; string type
ADE8 RTS ; Return the string
ADE9 .rsl_missing_quote←1← ADD0 BEQ
JMP missing_quote ; Missing " error

Evaluate a factor (evaluator level 1)

Evaluate the highest-precedence level of an expression at PtrB: unary minus, unary plus and NOT; parenthesised sub-expressions; the ?, !, $ and | indirection operators; string literals; and the built-in functions. Unlike the higher levels, it does not read the trailing operator - the caller does that.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the factor to evaluate
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the value, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the factor
ADEC .eval_factor←13← 92E3 JSR← 92FA JSR← 9E20 JSR← AB88 JSR← ABE9 JSR← AC2F JSR← AC78 JSR← AC9E JSR← AD6A JSR← ADF4 BEQ← AED1 JSR← B0A3 JSR← BF83 JSR
LDY zp_text_ptr2_off ; Next character
ADEE INC zp_text_ptr2_off ; advance the offset
ADF0 LDA (zp_text_ptr2),y ; read it
ADF2 CMP #' ' ; space?
ADF4 BEQ eval_factor ; skip it
ADF6 CMP #'-' ; '-' unary minus?
ADF8 BEQ unary_minus ; yes
ADFA CMP #'"' ; '"' string literal?
ADFC BEQ rsl_quoted ; yes
ADFE CMP #'+' ; '+' unary plus?
AE00 BNE factor_classify ; no: classify the token
AE02 .factor_unary_plus←1← AD8C JSR
JSR skip_spaces_ptr2 ; unary plus: re-read the next character
AE05 .factor_classify←1← AE00 BNE
CMP #&8e ; below the lowest function token?
AE07 BCC factor_indirect ; yes: indirection, number or variable
AE09 CMP #&c6 ; above the highest function token?
AE0B BCS no_such_variable ; yes: No such variable
AE0D JMP dispatch_token ; a function: dispatch it
AE10 .factor_indirect←1← AE07 BCC
CMP #'?' ; '?' (byte indirection) or higher?
AE12 BCS factor_name ; yes: variable or indirection
AE14 CMP #'.' ; '.' or a digit?
AE16 BCS factor_number ; yes: a number
AE18 CMP #'&' ; '&' hex number?
AE1A BEQ factor_hex ; yes
AE1C CMP #'(' ; '(' sub-expression?
AE1E BEQ eval_subexpr ; yes
AE20 .factor_name←1← AE12 BCS
DEC zp_text_ptr2_off ; Back up to the name
AE22 JSR pvr_parse ; Parse the variable reference
AE25 BEQ factor_undefined ; undefined: handle below
AE27 JMP load_var_by_type ; load the variable value
AE2A .factor_number←1← AE16 BCS
JSR parse_number ; Parse the decimal number
AE2D BCC no_such_variable ; ok Return
AE2F RTS ; Return
AE30 .factor_undefined←1← AE25 BEQ
LDA zp_opt_flag ; Undefined: OPT flag
AE32 AND #2 ; ignore-undefined set?
AE34 BNE no_such_variable ; no: No such variable
AE36 BCS no_such_variable ; bad name: No such variable
AE38 STX zp_text_ptr2_off ; accept; advance the offset
AE3A .factor_pcounter←1← 85AF JSR
LDA resint_p ; Use P% as the value
AE3D LDY resint_p_1 ; return it as an integer
AE40 JMP iwa_from_ya ; return as a 16-bit integer

Raise "No such variable" error

Raise BASIC error &1A, "No such variable", via a BRK error block. Reached when an expression or lvalue names a variable that has not been created. Does not return.

On ExitCONTROLraises BRK error &1A "No such variable"; does not return to the caller
AE43 .no_such_variable←6← 8F1B JMP← AE0B BCS← AE2D BCC← AE34 BNE← AE36 BCS← AEC7 JMP
BRK ; No such variable error
AE44 EQUB &1A
AE45 EQUS "No such variable"
AE55 EQUB &00

Evaluate a parenthesised sub-expression

Evaluate a full expression at PtrB via eval_or_eor, then require a closing ")" - raising Missing ) if the terminating operator is anything else. Used wherever a bracketed sub-expression appears in the grammar.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB, positioned just after the opening "("
ZP_TEXT_PTR2_OFF (&1B)offset into the text
On ExitAresult type (<0 float in fwa, >0 integer in iwa, 0 string)
Ycopy of the result type
ZP_IWA (&2A) / ZP_FWA (&2E) / STRING_WORK (&0600)the value, selected by the type
ZP_TEXT_PTR2_OFF (&1B)advanced past the ")"
AE56 .eval_subexpr←11← 8E2B JSR← 9747 JSR← 976C JSR← AB4A JSR← AD09 JSR← AE1E BEQ← AF0C JSR← AFDA JSR← AFFC JSR← B05B JSR← B0CB JSR
JSR eval_or_eor ; Sub-expression: evaluate it
AE59 INC zp_text_ptr2_off ; step past
AE5B CPX #')' ; ')' to close?
AE5D BNE missing_paren ; no: Missing )
AE5F TAY ; flag the result type
AE60 RTS ; Return
AE61 .missing_paren←1← AE5D BNE
BRK ; Missing ) error
AE62 EQUB &1B
AE63 EQUS "Missing )"
AE6C EQUB &00

Scan an & hex constant

Shift each hex digit's nibble into the 32-bit IWA (&2A-&2D); the bit rolled off the top is discarded, so there is no overflow check — a 9th or later digit silently drops the high nibble and only the low 32 bits survive (so &AABBCCDD is accepted as the signed integer -1430532899, and &FFFFFFFF as -1). The digit run is unbounded; the sole error is Bad HEX, raised only when & is followed by no hex digits at all. The accepted digits are 0-9 and uppercase A-F only — a lowercase a-f ends the scan, so &ff reads no digits and faults Bad HEX whereas &Ff is 15. This is the only & reader, reached from eval_factor; VAL's number reader (parse_number) is decimal-only. Returns type &40 (integer).

AE6D .factor_hex←1← AE1A BEQ
LDX #0 ; Hex number: clear IWA
AE6F STX zp_iwa ; byte 0,
AE71 STX zp_iwa_1 ; byte 1,
AE73 STX zp_iwa_2 ; byte 2,
AE75 STX zp_iwa_3 ; byte 3
AE77 LDY zp_text_ptr2_off ; scan offset
AE79 .factor_hex_loop←1← AEA0 BNE
LDA (zp_text_ptr2),y ; Next character
AE7B CMP #'0' ; below '0'?
AE7D BCC factor_hex_check ; yes: end of number
AE7F CMP #':' ; a digit 0-9?
AE81 BCC factor_hex_nibble ; yes
AE83 SBC #&37 ; fold A-F to 10-15
AE85 CMP #&0a ; below 10 (a gap char)?
AE87 BCC factor_hex_check ; yes: end of number
AE89 CMP #&10 ; above F? (also rejects lowercase a-f)
AE8B BCS factor_hex_check ; yes: end of number
AE8D .factor_hex_nibble←1← AE81 BCC
ASL ; Shift the digit into the high nibble
AE8E ASL ; (continued)
AE8F ASL ; (continued)
AE90 ASL ; (continued)
AE91 LDX #3 ; four bits to shift
AE93 .factor_hex_shift←1← AE9D BPL
ASL ; Shift one bit into IWA
AE94 ROL zp_iwa ; byte 0,
AE96 ROL zp_iwa_1 ; byte 1,
AE98 ROL zp_iwa_2 ; byte 2,
AE9A ROL zp_iwa_3 ; byte 3 (bit rolled out here is dropped: no check)
AE9C DEX ; one of four bits done
AE9D BPL factor_hex_shift ; next bit
AE9F INY ; advance
AEA0 BNE factor_hex_loop ; next digit: unbounded run, low 32 bits survive
AEA2 .factor_hex_check←3← AE7D BCC← AE87 BCC← AE8B BCS
TXA ; Any digits seen?
AEA3 BPL bad_hex ; no: Bad HEX
AEA5 STY zp_text_ptr2_off ; Save the offset
AEA7 LDA #&40 ; Type = integer
AEA9 RTS ; Return
AEAA .bad_hex←1← AEA3 BPL
BRK ; Bad HEX error
AEAB EQUB &1C
AEAC EQUS "Bad HEX"
AEB3 EQUB &00

=TIME

Read the centisecond elapsed-time clock. TIME.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AEB4 .fn_time
LDX #<(zp_iwa) ; Point OSWORD at IWA: low byte
AEB6 LDY #>(zp_iwa) ; high byte
AEB8 LDA #osword_read_clock ; OSWORD &01: read the centisecond clock into IWA
AEBA JSR osword ; Read system clock
AEBD LDA #&40 ; Integer result
AEBF RTS ; Return TIME

=PAGE

Read PAGE, the start of the BASIC program. PAGE.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AEC0 .fn_page
LDA #0 ; PAGE low byte is always 0...
AEC2 LDY zp_page ; ...high byte is the page number
AEC4 JMP iwa_from_ya ; Return PAGE as an integer
AEC7 .pseudovar_syntax_error←1← AEE2 BNE
JMP no_such_variable ; syntax error (shared)

FALSE

The constant FALSE (0). Sets IWA = 0; this is also the izero integer primitive.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AECA .fn_false
LDA #0 ; FALSE is 0
AECC BEQ int_result_a ; Return 0 as an integer
AECE .bool_type_error←1← AED4 BNE
JMP err_type_mismatch ; String operand here is a Type mismatch

LEN

Length of a string. LEN string.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AED1 .fn_len
JSR eval_factor ; Evaluate the string
AED4 BNE bool_type_error ; not a string: error
AED6 LDA zp_strbuf_len ; Length, returned as an integer
fall through ↓

Return A as an integer result

Set IWA to the unsigned byte in A (high bytes zero) and report the integer type. The common tail for functions returning a small integer.

On EntryAthe unsigned byte to return
On ExitZP_IWA (&2A-&2D)A zero-extended to 32 bits
Ainteger type marker (&40)
Xpreserved
AED8 .int_result_a←18← 8F6B JSR← 8F76 JSR← 9182 JSR← 9339 JSR← 96F8 JSR← AB6A JMP← AB73 JMP← AB7C JMP← ABA2 JMP← ACAA JMP← AD4F JMP← AECC BEQ← AEF9 JMP← AFBC JMP← B5A9 JSR← B810 JSR← BF75 JMP← BF93 JMP
LDY #0 ; High byte zero
AEDA BEQ iwa_from_ya ; return A as the integer
fall through ↓

TO

The TO keyword. "TO P" reads TOP, the end of the BASIC program, as an integer; any other TO is a syntax error.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AEDC .fn_to
LDY zp_text_ptr2_off ; Look at the character after TO
AEDE LDA (zp_text_ptr2),y ; (get it)
AEE0 CMP #'P' ; Is it "P"? TO + P spells TOP
AEE2 BNE pseudovar_syntax_error ; No: a bare TO here is a syntax error
AEE4 INC zp_text_ptr2_off ; Consume the P
AEE6 LDA zp_top ; TOP: end-of-program address, low byte
AEE8 LDY zp_top_1 ; high byte (returned as an integer)
fall through ↓

Set the integer accumulator to a small integer

IWA = 256*Y + A, zero-extended to 32 bits (unsigned 0-65535), then report the integer type.

On EntryAlow byte
Yhigh byte
On ExitZP_IWA (&2A-&2D)256*Y + A (unsigned, top two bytes zero)
Ainteger type marker (&40)
Xpreserved
AEEA .iwa_from_ya←11← 98A4 JSR← AB3E JMP← ACB5 JMP← AE40 JMP← AEC4 JMP← AEDA BEQ← AF00 JMP← AF07 JMP← AFA3 JMP← AFAA JMP← B351 JMP
STA zp_iwa ; Low byte (A) into IWA byte 0
AEEC STY zp_iwa_1 ; High byte (Y) into IWA byte 1
AEEE LDA #0 ; Clear the top 16 bits: the result is 0-65535
AEF0 STA zp_iwa_2 ; Clear IWA byte 2
AEF2 STA zp_iwa_3 ; Clear byte 3: the value is unsigned 0-65535
AEF4 LDA #&40 ; Report the value as an integer (type &40)
AEF6 RTS ; Return the integer

COUNT

Characters printed since the last newline. COUNT.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AEF7 .fn_count
LDA zp_count ; COUNT: characters printed since the last newline
AEF9 JMP int_result_a ; Return it as an integer

=LOMEM

Read LOMEM, the start of variable storage. LOMEM.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AEFC .fn_lomem
LDA zp_lomem ; LOMEM low byte
AEFE LDY zp_lomem_1 ; high byte
AF00 JMP iwa_from_ya ; Return LOMEM as an integer

=HIMEM

Read HIMEM, the top of memory for BASIC. HIMEM.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AF03 .fn_himem
LDA zp_himem ; HIMEM low
AF05 LDY zp_himem_1 ; HIMEM high
AF07 JMP iwa_from_ya ; return as an integer
AF0A .rnd_dispatch←1← AF4F BEQ
INC zp_text_ptr2_off ; Skip the "("
AF0C JSR eval_subexpr ; Evaluate the argument expression
AF0F JSR coerce_to_integer ; Coerce it to an integer
AF12 LDA zp_iwa_3 ; Examine the argument: high byte
AF14 BMI rnd_seed ; Negative: re-seed the generator
AF16 ORA zp_iwa_2 ; Any of bits 16-31 set...
AF18 ORA zp_iwa_1 ; ...(magnitude >= 256)
AF1A BNE rnd_range ; Large: RND(X) over a range
AF1C LDA zp_iwa ; Low byte
AF1E BEQ rnd_repeat ; RND(0): repeat the last fraction
AF20 CMP #1 ; RND(1)?
AF22 BEQ rnd_fraction ; RND(1): a fresh fraction (else 2..255: RND(X))
fall through ↓

RND(X), X>=2: a random integer 1 to X

Compute 1 + INT(RND(1) * X). int_to_fwa(X), push X (stack_real), take a fraction (rnd_fraction), pop X back into the operand (unstack_real), multiply (fwa_mul_var_raw), convert to an integer (fwa_to_int) and add one (iwa_inc), giving a value in 1..X.

On EntryZP_IWA (&2A-&2D)the upper bound X (>= 2)
On ExitZP_IWA (&2A-&2D)a random integer in 1..X
ZP_FWA / ZP_FWBcorrupted (scratch)
AF24 .rnd_range←1← AF1A BNE
JSR int_to_fwa ; FWA = X
AF27 JSR stack_real ; Push X onto the stack
AF2A JSR rnd_fraction ; FWA = a fraction in [0, 1)
AF2D JSR unstack_real ; Pop X back as the multiply operand
AF30 JSR fwa_mul_var_raw ; FWA = fraction * X
AF33 JSR fwa_normalise ; Normalise the product
AF36 JSR fwa_to_int ; IWA = INT(fraction * X) = 0..X-1
AF39 JSR iwa_inc ; Add one: 1..X
AF3C LDA #&40 ; Integer result
AF3E RTS ; Return RND(X)

RND(-X): seed the generator and return X

Store the integer argument into the 32-bit state (&0D-&10) and set &11 to &40. Only bit 0 of &11 is part of the LFSR, so the overflow bit becomes 0. Returns the argument as an integer.

On EntryZP_IWA (&2A-&2D)the seed value
On ExitZP_RND_SEED (&0D-&11)reseeded from IWA
ZP_IWAunchanged (the returned value)
Ainteger type marker (&40)
AF3F .rnd_seed←1← AF14 BMI
LDX #&0d ; Point at the state bytes (&0D)
AF41 JSR iwa_store_zp ; Store the argument as the 32-bit state
AF44 LDA #&40 ; Bit-32 byte =
AF46 STA zp_rnd_seed_4 ; &40: bit 32 (its bit 0) becomes 0
AF48 RTS ; Return the argument (A = &40 = integer)

RND

Random number; the form depends on the argument (see rnd_*). RND[(numeric)].

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AF49 .fn_rnd
LDY zp_text_ptr2_off ; Look at the character after RND
AF4B LDA (zp_text_ptr2),y ; (get it)
AF4D CMP #'(' ; Is it "("? then RND(expr)
AF4F BEQ rnd_dispatch ; Yes: select the RND form
fall through ↓

RND: a full-range random integer

Bare RND. Advance the generator (rnd_step) then read the 32-bit state (&0D-&10) into IWA as a signed integer, returning the integer type (&40). The result spans the full signed 32-bit range.

On EntryZP_RND_SEED (&0D-&11)the current generator state
On ExitZP_RND_SEEDadvanced one step
ZP_IWA (&2A-&2D)the random 32-bit integer
Ainteger type marker (&40)
AF51 .rnd_integer
JSR rnd_step ; Advance the generator
AF54 LDX #&0d ; Point at the state (&0D), then copy it to IWA
fall through ↓

Load a zero-page integer variable into the accumulator

Copy the 4-byte integer at &00+X (a resident integer variable A%-Z% or @%) into IWA, then report the integer type.

On EntryXzero-page offset of the source variable (from &00)
On ExitZP_IWA (&2A-&2D)the loaded integer
Ainteger type marker (&40)
Xpreserved
Ypreserved
AF56 .iwa_load_zp←2← 9DBD JSR← B326 JSR
LDA zp_lomem,x ; Copy 4-byte value at &00+X into IWA: byte 0
AF58 STA zp_iwa ; (store)
AF5A LDA zp_lomem_1,x ; byte 1
AF5C STA zp_iwa_1 ; (store)
AF5E LDA zp_vartop,x ; byte 2
AF60 STA zp_iwa_2 ; (store)
AF62 LDA zp_vartop_1,x ; byte 3
AF64 STA zp_iwa_3 ; (store)
AF66 LDA #&40 ; Integer type
AF68 RTS ; Return the integer

RND(1): a random real in [0, 1)

Advance the generator (rnd_step) then fall into rnd_repeat to build the fraction. The value is the byte-reversed 32-bit state divided by 2^32: a real in [0, 1).

On EntryZP_RND_SEED (&0D-&11)the current generator state
On ExitZP_RND_SEEDadvanced one step
ZP_FWA (&2E-&35)a normalised real in [0, 1)
Areal type marker (&FF)
AF69 .rnd_fraction←2← AF22 BEQ← AF2A JSR
JSR rnd_step ; Advance the generator, then build the fraction
fall through ↓

RND(0): repeat the last RND(1) without advancing

Build the floating-point fraction from the CURRENT generator state WITHOUT stepping it (so RND(0) repeats the last RND(1)). The mantissa bytes m1..m4 are loaded most-significant-first from the little-endian state &0D,&0E,&0F,&10, which byte-reverses the 32-bit value; with exponent &80 and after normalisation the result is state-reversed / 2^32, a real in [0, 1).

On EntryZP_RND_SEED (&0D-&10)the current 32-bit generator state
On ExitZP_FWA (&2E-&35)a normalised real in [0, 1)
Areal type marker (&FF)
Xcorrupted
AF6C .rnd_repeat←1← AF1E BEQ
LDX #0 ; Zero for the FP fields
AF6E STX zp_fwa_sign ; Sign positive
AF70 STX zp_fwa_ovf ; Clear overflow
AF72 STX zp_fwa_rnd ; Clear rounding
AF74 LDA #&80 ; Exponent &80 (= 2^0)
AF76 STA zp_fwa_exp ; (store it)
AF78 .rnd_state_loop←1← AF7F BNE
LDA zp_rnd_seed,x ; State byte X (little-endian)...
AF7A STA zp_fwa_m1,x ; ...into mantissa byte X (MSB first): byte-reverses
AF7C INX ; next byte
AF7D CPX #4 ; all four?
AF7F BNE rnd_state_loop ; loop
AF81 JSR mulf_normalise ; Normalise: value = reversed-state / 2^32
AF84 LDA #&ff ; Real result type
AF86 RTS ; Return RND(1) / RND(0)

Advance the random-number LFSR by 32 steps

Advance the 33-bit linear-feedback shift register in zp_rnd_seed by exactly 32 single-bit steps (one RND call). The register uses the primitive trinomial x^33 + x^20 + 1, i.e. taps (33, 20, 0): each step feeds (bit 19 XOR bit 32) into bit 0 and shifts the whole register left by one.

Per step: take byte 2 (bits 16-23), shift right 3 so bit 19 reaches bit 0, EOR with byte 4 to bring in bit 32, rotate to put the feedback bit into carry, then ROL the five state bytes so carry enters bit 0.

On EntryZP_RND_SEED (&0D-&11)the 33-bit generator state
On ExitZP_RND_SEEDadvanced by 32 steps
Xpreserved
AF87 .rnd_step←2← AF51 JSR← AF69 JSR
LDY #&20 ; 32 single-bit steps make one RND advance
AF89 .rnd_step_loop←1← AF9C BNE
LDA zp_rnd_seed_2 ; Byte 2 holds register bits 16-23
AF8B LSR ; Shift right so bit 19 (tap 20)...
AF8C LSR ; shifting it down
AF8D LSR ; ...reaches bit 0
AF8E EOR zp_rnd_seed_4 ; EOR byte 4 to bring in bit 32: bit19 XOR bit32
AF90 ROR ; Rotate the feedback bit into carry
AF91 ROL zp_rnd_seed ; Shift the register left, feedback into bit 0
AF93 ROL zp_rnd_seed_1 ; bits 8-15
AF95 ROL zp_rnd_seed_2 ; bits 16-23
AF97 ROL zp_rnd_seed_3 ; bits 24-31
AF99 ROL zp_rnd_seed_4 ; bit 32
AF9B DEY ; Next step
AF9C BNE rnd_step_loop ; Loop for all 32 steps
AF9E RTS ; The register has advanced

ERL

Line number where the last error occurred. ERL.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AF9F .fn_erl
LDY zp_erl_1 ; ERL high byte
AFA1 LDA zp_erl ; low byte
AFA3 JMP iwa_from_ya ; Return ERL as an integer

ERR

Error number of the last error. ERR.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AFA6 .fn_err
LDY #0 ; Read the error number...
AFA8 LDA (zp_error_ptr),y ; ...from the error block (&FD)
AFAA JMP iwa_from_ya ; Return ERR as an integer

Read a key within a time limit (INKEY)

Evaluate the time-limit argument at PtrB into IWA, then tail-call OSBYTE &81 (INKEY) to read a key with that timeout. Shared by the INKEY and INKEY$ functions.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the time-limit argument
On ExitXthe key code (or internal key number on a scan)
Y0 if a key was read, &FF on timeout, &1B on Escape
Cper OSBYTE &81
AFAD .read_key_timed←2← ACAD JSR← B026 JSR
JSR eval_factor_integer ; Evaluate the time-limit argument
AFB0 LDA #osbyte_inkey ; OSBYTE &81: INKEY (read a key with a timeout)
AFB2 LDX zp_iwa ; time limit low
AFB4 LDY zp_iwa_1 ; time limit high
AFB6 JMP osbyte ; INKEY: Scan for a key with positive X, or read internal key number EOR 128 for negative X

GET

Wait for a key and return its ASCII code. GET.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AFB9 .fn_get
JSR osrdch ; X=ASCII code typed (positive timeout) or internal key number EOR 128 (negative scan) / Y=0 if a key was pressed, Y=&FF on time-out, Y=&1B on Escape
AFBC JMP int_result_a ; Return the key code as an integer

GET$

Read a key as a one-character string, or a byte / line from a file. GET$[#channel].

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AFBF .fn_gets
JSR osrdch ; Wait for a key
AFC2 .gets_char←2← B02C BEQ← B3C2 JMP
STA string_work ; Store it as the one-character string body
AFC5 LDA #1 ; Length 1
AFC7 STA zp_strbuf_len ; (store)
AFC9 LDA #0 ; String type
AFCB RTS ; Return the one-character string

LEFT$

Leftmost n characters of a string. LEFT$(string, n).

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AFCC .fn_lefts
JSR eval_or_eor ; Evaluate the source string
AFCF BNE str_type_error ; not a string: Type mismatch
AFD1 CPX #',' ; ','?
AFD3 BNE str_missing_comma ; no: Missing ,
AFD5 INC zp_text_ptr2_off ; step past
AFD7 JSR stack_string ; Stack the string
AFDA JSR eval_subexpr ; Evaluate the count, expect )
AFDD JSR coerce_to_integer ; coerce to integer
AFE0 JSR unstack_string ; Restore the string
AFE3 LDA zp_iwa ; Count
AFE5 CMP zp_strbuf_len ; count >= length?
AFE7 BCS lefts_result ; yes: keep the whole string
AFE9 STA zp_strbuf_len ; truncate to the count
AFEB .lefts_result←1← AFE7 BCS
LDA #0 ; String result
AFED RTS ; Return

RIGHT$

Rightmost n characters of a string. RIGHT$(string, n).

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
AFEE .fn_rights
JSR eval_or_eor ; Evaluate the source string
AFF1 BNE str_type_error ; not a string: Type mismatch
AFF3 CPX #',' ; ','?
AFF5 BNE str_missing_comma ; no: Missing ,
AFF7 INC zp_text_ptr2_off ; step past
AFF9 JSR stack_string ; Stack the string
AFFC JSR eval_subexpr ; Evaluate the count, expect )
AFFF JSR coerce_to_integer ; coerce to integer
B002 JSR unstack_string ; Restore the string
B005 LDA zp_strbuf_len ; Start = length - count
B007 SEC ; ready the subtract
B008 SBC zp_iwa ; minus the count: A = start offset
B00A BCC rights_whole ; count > length: keep the whole string
B00C BEQ return_31 ; count == length: keep it
B00E TAX ; Start offset
B00F LDA zp_iwa ; New length = count
B011 STA zp_strbuf_len ; store the new (shorter) length
B013 BEQ return_31 ; zero: empty string
B015 LDY #0 ; Copy the last count chars to the front
B017 .rights_copy_down←1← B021 BNE
LDA string_work,x ; Read a kept char from the tail (at X)
B01A STA string_work,y ; Pack it down to the front (at Y)
B01D INX ; Advance the read cursor
B01E INY ; Advance the write cursor
B01F DEC zp_iwa ; One fewer char to move
B021 BNE rights_copy_down ; Until all count chars are shifted down
B023 .rights_whole←1← B00A BCC
LDA #0 ; Keep the whole string
B025 .return_31←2← B00C BEQ← B013 BEQ
RTS ; Return

INKEY$

Read a key within a time limit as a string. INKEY$ numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
B026 .fn_inkeys
JSR read_key_timed ; Read a key within the time limit
B029 TXA ; X holds the key
B02A CPY #0 ; Y=0 means a key was read
B02C BEQ gets_char ; Got one: return it as a 1-char string
B02E .inkeys_timeout←2← B06B BCS← B081 BEQ
LDA #0 ; Timeout: empty string
B030 STA zp_strbuf_len ; length 0
B032 RTS ; Return the (possibly empty) string
B033 .str_type_error←3← AFCF BNE← AFF1 BNE← B03C BNE
JMP err_type_mismatch ; Type mismatch (shared)
B036 .str_missing_comma←4← AFD3 BNE← AFF5 BNE← B040 BNE← B059 BNE
JMP missing_comma ; Missing , error (shared)

MID$

Substring from a start position. MID$(string, start [,length]).

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
B039 .fn_mids
JSR eval_or_eor ; Evaluate the source string
B03C BNE str_type_error ; not a string: Type mismatch
B03E CPX #',' ; ','?
B040 BNE str_missing_comma ; no: Missing ,
B042 JSR stack_string ; Stack the string
B045 INC zp_text_ptr2_off ; step past
B047 JSR eval_expr_integer ; Evaluate the start position
B04A LDA zp_iwa ; Save it
B04C PHA ; push the start position
B04D LDA #&ff ; Default length = 255
B04F STA zp_iwa ; as the length
B051 INC zp_text_ptr2_off ; step past
B053 CPX #')' ; ')' (no length given)?
B055 BEQ mids_restore ; yes: use the default
B057 CPX #',' ; ','?
B059 BNE str_missing_comma ; no: Missing ,
B05B JSR eval_subexpr ; Evaluate the length, expect )
B05E JSR coerce_to_integer ; coerce to integer
B061 .mids_restore←1← B055 BEQ
JSR unstack_string ; Restore the string
B064 PLA ; Start position
B065 TAY ; into Y (also tests for 0),
B066 CLC ; clear carry for the compare
B067 BEQ mids_start ; position 0: treat as 1
B069 SBC zp_strbuf_len ; past the end?
B06B BCS inkeys_timeout ; yes: empty string
B06D DEY ; Zero-based start = p - 1
B06E TYA ; A = start - 1
B06F .mids_start←1← B067 BEQ
STA zp_iwa_2 ; Save the start offset
B071 TAX ; also into X (source index)
B072 LDY #0 ; Destination index
B074 LDA zp_strbuf_len ; Available = length - start
B076 SEC ; set carry,
B077 SBC zp_iwa_2 ; length - start offset
B079 CMP zp_iwa ; more than requested?
B07B BCS mids_length ; no: use the available count
B07D STA zp_iwa ; clamp the length
B07F .mids_length←1← B07B BCS
LDA zp_iwa ; Length
B081 BEQ inkeys_timeout ; zero: empty string
B083 .mids_copy_loop←1← B08D BNE
LDA string_work,x ; Copy the substring to the front
B086 STA string_work,y ; to the front,
B089 INY ; next dest,
B08A INX ; next source,
B08B CPY zp_iwa ; copied the requested length?
B08D BNE mids_copy_loop ; loop
B08F STY zp_strbuf_len ; Set the result length
B091 LDA #0 ; String result
B093 RTS ; Return

STR$

String form of a number (STR$~ for hex). STR$[~] numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
B094 .fn_strs
JSR skip_spaces_ptr2 ; Next character
B097 LDY #&ff ; assume hex
B099 CMP #'~' ; '~' hex prefix?
B09B BEQ strs_save_flag ; yes
B09D LDY #0 ; no: decimal
B09F DEC zp_text_ptr2_off ; back up
B0A1 .strs_save_flag←1← B09B BEQ
TYA ; Save the hex/dec flag
B0A2 PHA ; push it
B0A3 JSR eval_factor ; Evaluate the number
B0A6 BEQ strs_type_error ; string: error
B0A8 TAY ; Restore the flag
B0A9 PLA ; recover the hex/dec flag
B0AA STA zp_print_flag ; @% formatting set?
B0AC LDA resint_at_3 ; yes: use it
B0AF BNE strs_formatted ; Default conversion
B0B1 STA zp_general ; clear &37 (default format)
B0B3 JSR nta_default_digits ; string result
B0B6 LDA #0 ; Return
B0B8 RTS ; Return
B0B9 .strs_formatted←1← B0AF BNE
JSR number_to_ascii ; Formatted conversion (@%)
B0BC LDA #0 ; string result
B0BE RTS ; Return
B0BF .strs_type_error←2← B0A6 BEQ← B0CE BNE
JMP err_type_mismatch ; Type mismatch error

STRING$

A string repeated n times. STRING$(n, string).

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
B0C2 .fn_strings
JSR eval_expr_integer ; Evaluate the count (n) as an integer
B0C5 JSR stack_integer ; stack it
B0C8 JSR skip_spaces_expect_comma ; require a comma
B0CB JSR eval_subexpr ; evaluate the string s$
B0CE BNE strs_type_error ; not a string: Type mismatch
B0D0 JSR unstack_integer ; recover the count
B0D3 LDY zp_strbuf_len ; source string length
B0D5 BEQ strings_type ; empty source: empty result
B0D7 LDA zp_iwa ; count = 0?
B0D9 BEQ strings_empty ; yes: empty result
B0DB DEC zp_iwa ; one copy is already there; need count-1 more
B0DD BEQ strings_type ; count = 1: done
B0DF .strings_append←1← B0F1 BNE
LDX #0 ; Append another copy:
B0E1 .strings_copy_loop←1← B0ED BCC
LDA string_work,x ; source char...
B0E4 STA string_work,y ; ...appended
B0E7 INX ; next source
B0E8 INY ; next dest
B0E9 BEQ strings_too_long ; too long: error
B0EB CPX zp_strbuf_len ; end of this copy?
B0ED BCC strings_copy_loop ; no: keep copying
B0EF DEC zp_iwa ; one fewer copy
B0F1 BNE strings_append ; loop
B0F3 STY zp_strbuf_len ; set the result length
B0F5 .strings_type←2← B0D5 BEQ← B0DD BEQ
LDA #0 ; string type
B0F7 RTS ; Return
B0F8 .strings_empty←1← B0D9 BEQ
STA zp_strbuf_len ; empty result (length 0)
B0FA RTS ; Return
B0FB .strings_too_long←1← B0E9 BEQ
JMP string_too_long ; String too long error
B0FE .no_such_fn_proc←1← B11E BMI
PLA ; No such FN/PROC: restore the text pointer
B0FF STA zp_text_ptr_1 ; pointer high
B101 PLA ; pull the low byte
B102 STA zp_text_ptr ; pointer low
B104 BRK ; "No such FN/PROC" error block
B105 EQUB &1D
B106 EQUS "No such "
B10E EQUB &A4, &2F, &F2, &00

Find a PROC/FN definition by name

Walk the program from PAGE for the DEF PROC/FN line whose name matches the reference at (zp_general), then cache its body address in the variable entry so later calls resolve quickly. Raises No such FN/PROC.

On Entry(ZP_GENERAL) (&37/&38)the PROC/FN name reference
On ExitTHE VARIABLE ENTRYupdated with the cached body address
BRKNo such FN/PROC if no matching DEF exists
B112 .find_def←1← B1E6 JMP
LDA zp_page ; Search from PAGE: high
B114 STA zp_text_ptr_1 ; (store)
B116 LDA #0 ; low 0
B118 STA zp_text_ptr ; (store)
B11A .finddef_scan←2← B136 BCC← B13A BCS
LDY #1 ; line number high byte
B11C LDA (zp_text_ptr),y ; read it
B11E BMI no_such_fn_proc ; end of program: not found
B120 LDY #3 ; skip to the line body
B122 .finddef_advance←1← B127 BEQ
INY ; advance
B123 LDA (zp_text_ptr),y ; char
B125 CMP #' ' ; skip leading spaces
B127 BEQ finddef_advance ; skip it
B129 CMP #&dd ; DEF token at the start?
B12B BEQ finddef_found ; yes: check the name
B12D .finddef_next_line←2← B15E BNE← B16A BCS
LDY #3 ; no DEF: skip to the next line
B12F LDA (zp_text_ptr),y ; line length
B131 CLC ; add the line length...
B132 ADC zp_text_ptr ; to the pointer low,
B134 STA zp_text_ptr ; (store)
B136 BCC finddef_scan ; no carry
B138 INC zp_text_ptr_1 ; carry into the pointer high
B13A BCS finddef_scan ; next line
B13C .finddef_found←1← B12B BEQ
INY ; DEF found: skip past the FN/PROC token
B13D STY zp_text_ptr_off ; (store the offset)
B13F JSR skip_spaces ; skip spaces
B142 TYA ; point at the definition name:
B143 TAX ; X = the name offset
B144 CLC ; pointer + offset:
B145 ADC zp_text_ptr ; low,
B147 LDY zp_text_ptr_1 ; high,
B149 BCC finddef_name_setup ; no carry
B14B INY ; carry into high
B14C CLC ; (borrow for the -1)
B14D .finddef_name_setup←1← B149 BCC
SBC #0 ; minus 1 (loop pre-increments Y)
B14F STA zp_fwb_ovf ; (save pointer low)
B151 TYA ; high byte...
B152 SBC #0 ; minus the borrow
B154 STA zp_fwb_exp ; (save pointer high)
B156 LDY #0 ; Compare the definition name with the called name:
B158 .finddef_match_loop←1← B162 BNE
INY ; next char
B159 INX ; advance both cursors
B15A LDA (zp_fwb_ovf),y ; definition char
B15C CMP (zp_general),y ; vs called char
B15E BNE finddef_next_line ; differ: next line
B160 CPY zp_fileblk ; end of the called name?
B162 BNE finddef_match_loop ; no: keep comparing
B164 INY ; definition name also ends?
B165 LDA (zp_fwb_ovf),y ; read the next def char
B167 JSR is_alphanumeric ; alphanumeric?
B16A BCS finddef_next_line ; definition name is longer: next line
B16C TXA ; Match: cache the definition
B16D TAY ; Y = name length
B16E JSR skip_to_statement_end ; point PtrA at the body
B171 JSR create_def_entry ; create/find the FN/PROC entry
B174 LDX #1 ; init it
B176 JSR clear_value_bytes ; initialise the value
B179 LDY #0 ; store the body pointer:
B17B LDA zp_text_ptr ; pointer low...
B17D STA (zp_vartop),y ; (store)
B17F INY ; next byte
B180 LDA zp_text_ptr_1 ; pointer high...
B182 STA (zp_vartop),y ; (store)
B184 JSR advance_vartop ; advance VARTOP
B187 JMP callpf_push_count ; continue
B18A .finddef_not_found←1← B1DA BEQ
BRK ; error block
B18B EQUB &1E
B18C EQUS "Bad call"
B194 EQUB &00

FN

Call a user-defined function and return its value. FNname[(params)].

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
B195 .fn_fn
LDA #&a4 ; FN token: enter via the PROC/FN call mechanism
fall through ↓

Enter a PROC or FN

The procedure/function call mechanism. It lets PROCs and FNs nest far beyond the 256-byte 6502 stack by moving that stack out of the way for each call:

  1. Copy the whole live 6502 stack onto the BASIC value stack (the saved stack pointer first, then every byte), then empty the hardware stack (txs with X = &FF). The frame below is therefore built from &01FF downwards.
  2. Push the call frame (the table).
  3. Locate the named definition (heap cache, else a program scan), bind any parameters, and jsr next_statement to run the body.

The 10-byte call frame, on the freshly emptied hardware stack while the body runs (deepest byte first):

Addr Byte From
&01FF PROC token &F2 / FN &A4 &27
&01FE caller PtrA offset &0A
&01FD caller PtrA low &0B
&01FC caller PtrA high &0C
&01FB LOCAL/parameter count -
&01FA PtrB offset &1B
&01F9 PtrB low &19
&01F8 PtrB high &1A
&01F7 body return high jsr
&01F6 body return low jsr

The addresses are absolute because the stack was just emptied -- which is why stmt_endproc reads the framed token at hw_stack_top (&01FF) and stmt_local bumps the count through frame_local_count (&0106,X with X = &F5, i.e. &01FB).

Unwinding is indirect: ENDPROC and =expr do not pop the frame. They verify the framed token, then fall into the end-of-statement path whose rts pops the body return (&01F6/&01F7) straight back here, to &B20E. From there this routine restores PtrB, replays the count by popping that many (variable address, saved value) pairs off the BASIC value stack to undo the LOCALs/parameters, restores the caller's PtrA, and copies the saved 6502 stack back into page 1.

What is NOT saved: only the hardware stack and the BASIC value stack are preserved. The FOR/REPEAT/GOSUB stacks (page 5, counters &26/&24/&25) are global and untouched, so a loop left early via ENDPROC or GOTO leaks its frame. See stmt_endproc and the page-5 control-flow stacks at &0500.

On EntryAPROC token &F2 or FN token &A4
B197 .call_proc_fn←1← 9312 JSR
STA zp_var_type ; Save the PROC/FN token
B199 TSX ; Copy the 6502 stack onto the BASIC stack
B19A TXA ; stack pointer to A,
B19B CLC ; add the BASIC stack pointer
B19C ADC zp_stack_ptr ; Drop the BASIC stack by the 6502 stack size
B19E JSR reserve_stack ; so procedures can nest far beyond 256 bytes
B1A1 LDY #0 ; Store the 6502 stack pointer first
B1A3 TXA ; the saved SP,
B1A4 STA (zp_stack_ptr),y ; at offset 0
B1A6 .callpf_save_stack←1← B1AF BNE
INX ; Copy each 6502 stack byte
B1A7 INY ; next dest,
B1A8 LDA hw_stack,x ; read a stack byte,
B1AB STA (zp_stack_ptr),y ; to the BASIC stack,
B1AD CPX #&ff ; whole stack copied?
B1AF BNE callpf_save_stack ; loop
B1B1 TXS ; Empty the 6502 stack
B1B2 LDA zp_var_type ; PROC/FN token
B1B4 PHA ; Push the call context and return pointers
B1B5 LDA zp_text_ptr_off ; return line offset
B1B7 PHA ; push it
B1B8 LDA zp_text_ptr ; return line pointer
B1BA PHA ; push low,
B1BB LDA zp_text_ptr_1 ; high byte,
B1BD PHA ; push high
B1BE LDA zp_text_ptr2_off ; Point &37/&38 at the PROC/FN name
B1C0 TAX ; keep the offset,
B1C1 CLC ; add the parser pointer:
B1C2 ADC zp_text_ptr2 ; low byte,
B1C4 LDY zp_text_ptr2_1 ; high byte in Y,
B1C6 BCC callpf_back ; no carry,
B1C8 INY ; carry into high,
B1C9 CLC ; clear carry,
B1CA .callpf_back←1← B1C6 BCC
SBC #1 ; back up one: low,
B1CC STA zp_general ; to &37,
B1CE TYA ; high byte,
B1CF SBC #0 ; with borrow,
B1D1 STA zp_general_1 ; to &38
B1D3 LDY #2 ; Validate the name
B1D5 JSR valname_loop ; scan it from offset 2
B1D8 CPY #2 ; no valid characters?
B1DA BEQ finddef_not_found ; yes: Bad call
B1DC STX zp_text_ptr2_off ; Offset past the name
B1DE DEY ; Name length
B1DF STY zp_fileblk ; store it (&39)
B1E1 JSR find_proc_fn ; Look it up in the heap cache
B1E4 BNE callpf_set_ptr ; found: jump to it
B1E6 JMP find_def ; not cached: scan the program
B1E9 .callpf_set_ptr←1← B1E4 BNE
LDY #0 ; Set PtrA to the definition address
B1EB LDA (zp_iwa),y ; low byte,
B1ED STA zp_text_ptr ; to PtrA low,
B1EF INY ; next,
B1F0 LDA (zp_iwa),y ; high byte,
B1F2 STA zp_text_ptr_1 ; to PtrA high
B1F4 .callpf_push_count←1← B187 JMP
LDA #0 ; Push the parameter count (0 so far)
B1F6 PHA ; push it,
B1F7 STA zp_text_ptr_off ; offset = 0
B1F9 JSR skip_spaces ; Next character
B1FC CMP #'(' ; '(' parameters?
B1FE BEQ callpf_save_parser2 ; yes: bind them
B200 DEC zp_text_ptr_off ; no: back up
B202 .callpf_save_parser←1← B30A JMP
LDA zp_text_ptr2_off ; Save the parser pointer
B204 PHA ; push offset,
B205 LDA zp_text_ptr2 ; low byte,
B207 PHA ; push low,
B208 LDA zp_text_ptr2_1 ; high byte,
B20A PHA ; push high
B20B JSR next_statement ; Execute the body
B20E PLA ; Restore the parser pointer
B20F STA zp_text_ptr2_1 ; high byte,
B211 PLA ; pull low,
B212 STA zp_text_ptr2 ; low byte,
B214 PLA ; pull offset,
B215 STA zp_text_ptr2_off ; offset
B217 PLA ; Number of LOCAL/parameter values to restore
B218 BEQ callpf_restore_ptr ; none: skip
B21A STA zp_fwb_m2 ; count
B21C .callpf_restore_locals←1← B224 BNE
JSR unstack_int_to_general ; Unstack the variable address
B21F JSR unstack_value_to_var ; restore its saved value
B222 DEC zp_fwb_m2 ; one done
B224 BNE callpf_restore_locals ; loop
B226 .callpf_restore_ptr←1← B218 BEQ
PLA ; Restore PtrA
B227 STA zp_text_ptr_1 ; high byte,
B229 PLA ; pull low,
B22A STA zp_text_ptr ; low byte,
B22C PLA ; pull offset,
B22D STA zp_text_ptr_off ; offset
B22F PLA ; Recover the saved 6502 stack pointer
B230 LDY #0 ; from offset 0,
B232 LDA (zp_stack_ptr),y ; the saved SP,
B234 TAX ; into X
B235 TXS ; restore it
B236 .callpf_restore_stack←1← B23F BNE
INY ; Copy each byte back to the 6502 stack
B237 INX ; next slot,
B238 LDA (zp_stack_ptr),y ; read from the BASIC stack,
B23A STA hw_stack,x ; to the 6502 stack,
B23D CPX #&ff ; whole stack restored?
B23F BNE callpf_restore_stack ; loop
B241 TYA ; Adjust the BASIC stack pointer back up
B242 ADC zp_stack_ptr ; free the copied bytes,
B244 STA zp_stack_ptr ; store low,
B246 BCC callpf_return ; no carry,
B248 INC zp_stack_ptr_1 ; carry into high
B24A .callpf_return←1← B246 BCC
LDA zp_var_type ; Return the PROC/FN token
B24C RTS ; Return

Bind the PROC/FN parameters

Each formal is parsed by parse_lvalue — the same lvalue parser LET, FOR and LOCAL use — so a formal need not be a name: a ?, ! or $ indirection or an array element is a legal parameter, and binding it is a scoped assignment to that location. Each formal's current value is stacked (for LOCAL-style restore on return via stack_local) and its identity recorded, then the actual arguments are evaluated and assigned.

B24D .callpf_save_parser2←2← B1FE BEQ← B27E BEQ
LDA zp_text_ptr2_off ; Save the parser pointer
B24F PHA ; push offset,
B250 LDA zp_text_ptr2 ; low byte,
B252 PHA ; push low,
B253 LDA zp_text_ptr2_1 ; high byte,
B255 PHA ; push high
B256 JSR parse_lvalue ; Parse a formal parameter (any lvalue: parse_lvalue)
B259 BEQ callpf_reset_stack ; invalid: error
B25B LDA zp_text_ptr2_off ; Update the program pointer
B25D STA zp_text_ptr_off ; from the PtrB offset
B25F PLA ; Restore the parser pointer
B260 STA zp_text_ptr2_1 ; high byte,
B262 PLA ; pull low,
B263 STA zp_text_ptr2 ; low byte,
B265 PLA ; pull offset,
B266 STA zp_text_ptr2_off ; offset
B268 PLA ; Recover the running count
B269 TAX ; into X
B26A LDA zp_iwa_2 ; Push the formal's type/address
B26C PHA ; push type,
B26D LDA zp_iwa_1 ; address high,
B26F PHA ; push it,
B270 LDA zp_iwa ; address low,
B272 PHA ; push it
B273 INX ; count this parameter
B274 TXA ; the new count,
B275 PHA ; push it
B276 JSR stack_local ; Stack the current value for LOCAL restore
B279 JSR skip_spaces ; Skip spaces
B27C CMP #',' ; ',' another parameter?
B27E BEQ callpf_save_parser2 ; yes
B280 CMP #')' ; ')' end of parameter list?
B282 BNE callpf_reset_stack ; no: error
B284 LDA #0 ; Push the end marker
B286 PHA ; push it
B287 JSR skip_spaces_ptr2 ; Expect "(" for the arguments
B28A CMP #'(' ; '(' ?
B28C BNE callpf_reset_stack ; missing: error
B28E .callpf_arg_loop←1← B2A5 BEQ
JSR eval_or_eor ; Evaluate an argument
B291 JSR stack_value ; stack its value
B294 LDA zp_var_type ; save the type
B296 STA zp_iwa_3 ; keep it
B298 JSR stack_integer ; stack the type
B29B PLA ; Bump the argument count
B29C TAX ; into X,
B29D INX ; increment,
B29E TXA ; back to A,
B29F PHA ; push it
B2A0 JSR skip_spaces_ptr2 ; Skip spaces
B2A3 CMP #',' ; ',' another argument?
B2A5 BEQ callpf_arg_loop ; yes
B2A7 CMP #')' ; ')' end of arguments?
B2A9 BNE callpf_reset_stack ; no: error
B2AB PLA ; Recover the argument count
B2AC PLA ; and the formal count
B2AD STA zp_coeff_ptr ; to &4D,
B2AF STA zp_coeff_ptr_1 ; and &4E
B2B1 CPX zp_coeff_ptr ; counts match?
B2B3 BEQ callpf_unstack_arg ; yes: bind them
B2B5 .callpf_reset_stack←6← B259 BEQ← B282 BNE← B28C BNE← B2A9 BNE← B2DA BEQ← B2FB BNE
LDX #&fb ; Reset the stack
B2B7 TXS ; set SP = &FB
B2B8 PLA ; restore PtrA
B2B9 STA zp_text_ptr_1 ; high byte,
B2BB PLA ; pull low,
B2BC STA zp_text_ptr ; low byte
B2BE BRK ; Arguments error
B2BF EQUB &1F
B2C0 EQUS "Arguments"
B2C9 EQUB &00
B2CA .callpf_unstack_arg←2← B2B3 BEQ← B305 BNE
JSR unstack_integer ; Unstack the argument type
B2CD PLA ; Unstack the formal address/type
B2CE STA zp_iwa ; address low,
B2D0 PLA ; pull high,
B2D1 STA zp_iwa_1 ; address high,
B2D3 PLA ; pull type,
B2D4 STA zp_iwa_2 ; type
B2D6 BMI callpf_string_formal ; string formal?
B2D8 LDA zp_iwa_3 ; Argument type
B2DA BEQ callpf_reset_stack ; string argument: Arguments error
B2DC STA zp_var_type ; numeric: set the formal address
B2DE LDX #&37 ; address via &37,
B2E0 JSR iwa_store_zp ; copy the address there
B2E3 LDA zp_var_type ; Argument type
B2E5 BPL callpf_assign_int ; integer?
B2E7 JSR unstack_real ; real: unstack the real value
B2EA JSR fwa_unpack_var ; into FWA
B2ED JMP callpf_assign ; assign it
B2F0 .callpf_assign_int←1← B2E5 BPL
JSR unstack_integer ; unstack the integer value
B2F3 .callpf_assign←1← B2ED JMP
JSR assign_num_by_type ; Assign to the formal
B2F6 JMP callpf_bound ; next argument
B2F9 .callpf_string_formal←1← B2D6 BMI
LDA zp_iwa_3 ; String formal: argument type
B2FB BNE callpf_reset_stack ; numeric argument: Arguments error
B2FD JSR unstack_string ; Unstack the string
B300 JSR assign_string_to ; assign it
B303 .callpf_bound←1← B2F6 JMP
DEC zp_coeff_ptr ; One parameter bound
B305 BNE callpf_unstack_arg ; loop
B307 LDA zp_coeff_ptr_1 ; Push the parameter count for restore
B309 PHA ; push it
B30A JMP callpf_save_parser ; Execute the body

Save a variable for LOCAL

Push a variable's current value and identity (address and type) onto the BASIC stack so ENDPROC can restore it, implementing LOCAL.

On EntryZP_IWA (&2A/&2B)the variable address
ZP_IWA_2 (&2C)the variable type
On ExitZP_STACK_PTR (&04/&05)lowered past the saved value and identity
B30D .stack_local←2← 932D JSR← B276 JSR
LDY zp_iwa_2 ; Variable type
B30F CPY #4 ; an integer?
B311 BNE local_load_value ; no
B313 LDX #&37 ; integer: address its 4 bytes via &37
B315 JSR iwa_store_zp ; copy them there
B318 .local_load_value←1← B311 BNE
JSR load_var_by_type ; Load the variable's current value by type
B31B PHP ; save the type flags
B31C JSR stack_value ; push the value onto the stack
B31F PLP ; restore the type
B320 BEQ local_push_id ; byte/string
B322 BMI local_push_id ; or string
B324 LDX #&37 ; integer: reload
B326 JSR iwa_load_zp ; from &37
B329 .local_push_id←2← B320 BEQ← B322 BMI
JMP stack_integer ; push the variable identity (as an integer)

Load a variable by type

Dispatch on the variable type code in zp_iwa_2 and load the variable addressed by (zp_iwa) into the appropriate accumulator: a string into the string buffer (load_string_var), a byte or 4-byte integer into IWA (iwa_load_var / load_byte_var), or a 5-byte real into FWA (load_real_var). Negative = string, 0 = byte, 5 = real, anything else = integer.

On EntryZP_IWA (&2A)pointer to the variable (&2A/&2B)
ZP_IWA_2 (&2C)the variable type code (<0 string, 0 byte, 5 real, else integer)
On ExitAvalue-type marker (&40 integer, &FF real)
ZP_IWA (&2A)the integer, if integer/byte
ZP_FWA (&2E)the unpacked real, if real (&2E-&35)
STRING_WORK (&0600)the string characters, if string; length in zp_strbuf_len (&36)
B32C .load_var_by_type←3← 9685 JSR← AE27 JMP← B318 JSR
LDY zp_iwa_2 ; Load the variable by type:
B32E BMI load_string_var ; string
B330 BEQ load_byte_var ; byte
B332 CPY #5 ; real?
B334 BEQ load_real_var ; real (else integer)
fall through ↓

Load an integer variable into the accumulator

Copy the 4-byte integer addressed by zp_iwa into IWA.

On EntryZP_IWA (&2A/&2B)a pointer to the 4-byte integer variable
On ExitZP_IWAthe loaded integer
Xpreserved
B336 .iwa_load_var
LDY #3 ; Load a 4-byte integer (MSB first): byte 3
B338 LDA (zp_iwa),y ; read it
B33A STA zp_iwa_3 ; (into IWA)
B33C DEY ; next
B33D LDA (zp_iwa),y ; byte 2
B33F STA zp_iwa_2 ; (into IWA)
B341 DEY ; next
B342 LDA (zp_iwa),y ; byte 1
B344 TAX ; (keep in X)
B345 DEY ; next
B346 LDA (zp_iwa),y ; byte 0
B348 STA zp_iwa ; (store last)
B34A STX zp_iwa_1 ; byte 1 from X
B34C LDA #&40 ; integer type
B34E RTS ; Return the integer

Load a byte variable into IWA

Read the byte addressed by zp_iwa and return it as an unsigned integer in IWA (via iwa_from_ya). Part of the typed variable loader.

On Entry(ZP_IWA) (&2A/&2B)a pointer to the byte variable
Yoffset of the byte (0)
On ExitZP_IWA (&2A-&2D)the byte, zero-extended
Ainteger type marker (&40)
B34F .load_byte_var←1← B330 BEQ
LDA (zp_iwa),y ; Read the byte
B351 JMP iwa_from_ya ; return as an integer

Load a real variable into FWA

Unpack the 5-byte packed real addressed by zp_iwa (used here as a pointer to the variable) into FWA, restoring the implied leading 1 unless the value is zero. Part of the typed variable loader.

On Entry(ZP_IWA) (&2A/&2B)a pointer to the 5-byte real
Y5 (the byte count to copy back)
On ExitZP_FWA (&2E-&35)the unpacked real
Xpreserved
B354 .load_real_var←2← B334 BEQ← B766 JSR
DEY ; Unpack a 5-byte real into FWA: byte 4...
B355 LDA (zp_iwa),y ; read it
B357 STA zp_fwa_m4 ; -> m4
B359 DEY ; next
B35A LDA (zp_iwa),y ; byte 3...
B35C STA zp_fwa_m3 ; -> m3
B35E DEY ; next
B35F LDA (zp_iwa),y ; byte 2...
B361 STA zp_fwa_m2 ; -> m2
B363 DEY ; next
B364 LDA (zp_iwa),y ; byte 1 (sign + MSB)...
B366 STA zp_fwa_sign ; -> sign byte
B368 DEY ; next
B369 LDA (zp_iwa),y ; byte 0...
B36B STA zp_fwa_exp ; -> exponent
B36D STY zp_fwa_rnd ; clear rounding
B36F STY zp_fwa_ovf ; clear overflow
B371 ORA zp_fwa_sign ; test for zero...
B373 ORA zp_fwa_m2 ; m2,
B375 ORA zp_fwa_m3 ; m3,
B377 ORA zp_fwa_m4 ; m4
B379 BEQ lrv_set_msb ; zero: leave the MSB clear
B37B LDA zp_fwa_sign ; restore the implied 1...
B37D ORA #&80 ; set the top bit
B37F .lrv_set_msb←1← B379 BEQ
STA zp_fwa_m1 ; store the mantissa MSB
B381 LDA #&ff ; real type
B383 RTS ; Return the real

Load a string variable into the buffer

Copy a string variable into the string buffer. A normal string variable carries its pointer and length in the descriptor at (zp_iwa); a $-string (Y = &80) is read from (zp_iwa) up to the terminating CR. Part of the typed variable loader.

On Entry(ZP_IWA) (&2A/&2B)a pointer to the string variable
Ythe string type byte (&80 = $-string)
On ExitSTRING_WORK (&0600)the loaded string characters
ZP_STRBUF_LEN (&36)the string length
ZP_GENERAL (&37/&38)corrupted (used as the source ptr)
B384 .load_string_var←1← B32E BMI
CPY #&80 ; $-string (absolute address)?
B386 BEQ lsv_dollar ; yes
B388 LDY #3 ; normal string: length at offset 3
B38A LDA (zp_iwa),y ; read it
B38C STA zp_strbuf_len ; string length
B38E BEQ return_32 ; empty: done
B390 LDY #1 ; string pointer high
B392 LDA (zp_iwa),y ; read it
B394 STA zp_general_1 ; into the source pointer (high)
B396 DEY ; low
B397 LDA (zp_iwa),y ; read it
B399 STA zp_general ; and the low byte
B39B LDY zp_strbuf_len ; copy into the buffer:
B39D .lsv_copy_loop←1← B3A4 BNE
DEY ; char...
B39E LDA (zp_general),y ; read it
B3A0 STA string_work,y ; -> buffer
B3A3 TYA ; count
B3A4 BNE lsv_copy_loop ; loop
B3A6 .return_32←1← B38E BEQ
RTS ; Return the string
B3A7 .lsv_dollar←1← B386 BEQ
LDA zp_iwa_1 ; $-string: copy until CR
B3A9 BEQ chrs_code ; null pointer: empty
B3AB LDY #0 ; from the start
B3AD .lsv_dollar_loop←1← B3B7 BNE
LDA (zp_iwa),y ; char...
B3AF STA string_work,y ; -> buffer
B3B2 EOR #&0d ; CR?
B3B4 BEQ lsv_set_length ; yes: end
B3B6 INY ; next
B3B7 BNE lsv_dollar_loop ; loop
B3B9 TYA ; ran 256 chars without CR: length 0
B3BA .lsv_set_length←1← B3B4 BEQ
STY zp_strbuf_len ; set the length
B3BC RTS ; Return

CHR$

One-character string for an ASCII code. CHR$ numeric.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
B3BD .fn_chrs
JSR eval_factor_integer ; Evaluate the integer argument
B3C0 .chrs_code←1← B3A9 BEQ
LDA zp_iwa ; A = the character code
B3C2 JMP gets_char ; return a one-character string

Find the line an error occurred in

Walk the program from PAGE to locate the line whose text span contains the current program pointer and record its number in ERL (0 in immediate mode).

On EntryZP_TEXT_PTR (&0B/&0C)the program pointer at the error
ZP_PAGE (&18)PAGE, the start of the program
On ExitZP_ERL (&08/&09)the line number (ERL), or 0
B3C5 .find_error_line←1← B402 JSR
LDY #0 ; Clear ERL:
B3C7 STY zp_erl ; (low)
B3C9 STY zp_erl_1 ; (high)
B3CB LDX zp_page ; Scan the program from PAGE: high
B3CD STX zp_general_1 ; (store)
B3CF STY zp_general ; low 0
B3D1 LDX zp_text_ptr_1 ; past the error position?
B3D3 CPX #7 ; page 7 (the input line)?
B3D5 BEQ return_33 ; reached it: done
B3D7 LDX zp_text_ptr ; X = error position low
B3D9 .fel_scan_loop←1← B3FF BCS
JSR read_via_ptr_general ; read a program byte
B3DC CMP #&0d ; a line end (CR)?
B3DE BNE fel_check ; no: keep scanning
B3E0 CPX zp_general ; is this line past the error?
B3E2 LDA zp_text_ptr_1 ; error high...
B3E4 SBC zp_general_1 ; minus the scan high
B3E6 BCC return_33 ; yes: keep the previous line
B3E8 JSR read_via_ptr_general ; read the line number high byte
B3EB ORA #0 ; end of program?
B3ED BMI return_33 ; yes: done
B3EF STA zp_erl_1 ; record ERL high
B3F1 JSR read_via_ptr_general ; line number low byte
B3F4 STA zp_erl ; record ERL low
B3F6 JSR read_via_ptr_general ; skip the line length byte
B3F9 .fel_check←1← B3DE BNE
CPX zp_general ; still before the error?
B3FB LDA zp_text_ptr_1 ; error high...
B3FD SBC zp_general_1 ; minus the scan high
B3FF BCS fel_scan_loop ; yes: scan the next line
B401 .return_33←3← B3D5 BEQ← B3E6 BCC← B3ED BMI
RTS ; Return
B402 .brk_handler
JSR find_error_line ; Find the line number (set ERL)
B405 STY zp_trace_flag ; TRACE off
B407 LDA (zp_error_ptr),y ; error number
B409 BNE fel_set_handler ; non-zero: an ON ERROR handler is active
B40B LDA #&33 ; no handler: reset to the default (ON ERROR OFF)
B40D STA zp_error_vec ; default handler &B433: low,
B40F LDA #&b4 ; high...
B411 STA zp_error_vec_1 ; (store)
B413 .fel_set_handler←1← B409 BNE
LDA zp_error_vec ; Point the program pointer at the handler code: low
B415 STA zp_text_ptr ; (low)
B417 LDA zp_error_vec_1 ; high
B419 STA zp_text_ptr_1 ; (store)
B41B JSR reset_data_and_stacks ; clear DATA and the stacks
B41E TAX ; offset 0
B41F STX zp_text_ptr_off ; (store)
B421 LDA #osbyte_vdu_queue_size ; OSBYTE &DA: flush the VDU queue
B423 JSR osbyte ; osbyte: vdu queue size
B426 LDA #osbyte_acknowledge_escape ; OSBYTE &7E: acknowledge any Escape
B428 JSR osbyte ; Clear escape condition and perform escape effects
B42B LDX #&ff ; reset the 6502 stack...
B42D STX zp_opt_flag ; OPT = &FF
B42F TXS ; S = &FF (empty the stack)
B430 JMP next_statement ; enter the execution loop at the handler
B433 EQUB &F6, &3A, &E7, &9E, &F1
B438 EQUS &22, " at line ", &22, ";"
B444 EQUB &9E, &3A, &E0, &8B, &F1, &3A, &E0, &0D

SOUND

Make a sound on a channel. SOUND channel, amplitude, pitch, duration.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B44C .stmt_sound
JSR eval_expr_to_integer ; Evaluate the first parameter
B44F LDX #3 ; three more
B451 .sound_stack_loop←1← B45F BNE
LDA zp_iwa ; Stack the 16-bit value
B453 PHA ; push low,
B454 LDA zp_iwa_1 ; high,
B456 PHA ; push high
B457 TXA ; save the counter
B458 PHA ; push it
B459 JSR eval_comma_integer ; step past the comma, evaluate the next
B45C PLA ; restore the counter
B45D TAX ; into X,
B45E DEX ; one fewer parameter
B45F BNE sound_stack_loop ; loop
B461 JSR sync_text_ptr ; Check the statement ends
B464 LDA zp_iwa ; Last value to the block end
B466 STA zp_fwb_exp ; low byte to &3D,
B468 LDA zp_iwa_1 ; high,
B46A STA zp_fwb_m1 ; high byte to &3E
B46C LDY #7 ; OSWORD 7, 6 bytes
B46E LDX #5 ; 6-byte block (X=5)
B470 BNE env_pop_loop ; pop into the block and call
fall through ↓

ENVELOPE

Define a pitch/amplitude envelope for SOUND. ENVELOPE n,t,... (14 parameters).

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B472 .stmt_envelope
JSR eval_expr_to_integer ; Evaluate the first parameter
B475 LDX #&0d ; 13 more
B477 .env_stack_loop←1← B482 BNE
LDA zp_iwa ; Stack the 8-bit value
B479 PHA ; push it
B47A TXA ; save the counter
B47B PHA ; push it
B47C JSR eval_comma_integer ; step past the comma, evaluate the next
B47F PLA ; restore the counter
B480 TAX ; into X,
B481 DEX ; one fewer parameter
B482 BNE env_stack_loop ; loop
B484 JSR sync_text_ptr ; Check the statement ends
B487 LDA zp_iwa ; Last value to the block end
B489 STA zp_fp_temp_1 ; into &44 (the block end)
B48B LDX #&0c ; OSWORD 8, 12 bytes
B48D LDY #osword_envelope ; OSWORD number 8
B48F .env_pop_loop←2← B470 BNE← B493 BPL
PLA ; Pop a byte into the control block
B490 STA zp_general,x ; store at offset X,
B492 DEX ; next lower offset
B493 BPL env_pop_loop ; loop
B495 TYA ; OSWORD number
B496 LDX #<(zp_general) ; Point at the control block
B498 LDY #>(zp_general) ; block address high
B49A JSR osword ; ENVELOPE command
B49D JMP statement_loop ; next statement

WIDTH

Set the output line width for PRINT. WIDTH n.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B4A0 .stmt_width
JSR eval_expr_to_integer ; Evaluate the width
B4A3 JSR sync_text_ptr ; check the statement ends
B4A6 LDY zp_iwa ; Auto-newline column = width - 1
B4A8 DEY ; width - 1
B4A9 STY zp_width ; store it
B4AB JMP statement_loop ; next statement
B4AE .width_type_error←2← B4BF BEQ← B4E2 BEQ
JMP err_type_mismatch ; Type mismatch error

Evaluate a numeric value and assign it to a variable

Evaluate the numeric expression at PtrB via eval_or_eor, then fall straight into assign_number, which pops the variable's data address off the BASIC stack and stores the value there, coercing to the variable's own type.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB at the value expression
(ZP_STACK_PTR) (&04/&05)the destination variable data address on top of the BASIC stack
B4B1 .width_eval_value←2← B7D1 JSR← BB2C JSR
JSR eval_or_eor ; Evaluate the value
fall through ↓

Store a numeric value into a variable

Assign the current numeric value (integer or real) to the variable whose data address is on the stack, coercing to the variable's own type (real variables get the value converted to FP). An integer value is written raw and wraps — there is no range check — so A%=&AABBCCDD or !p=&AABBCCDD stores the four bytes verbatim and never errors. The only numeric-overflow error (Too big) lives on the real→integer convert branch (fwa_to_int), reached only when a float value lands in an integer cell, so A%=1E10 does raise Too big.

On Entry(ZP_STACK_PTR) (&04/&05)the variable data address
ZP_IWA / ZP_FWAthe value to store
ZP_VAR_TYPE (&27)the value type
On ExitTHE NUMERIC VARIABLEupdated in its own type
B4B4 .assign_number←6← 85B4 JSR← 8C05 JSR← 911E JSR← 933E JSR← BA39 JSR← BAD6 JSR
JSR unstack_int_to_general ; Pop the variable data address from the stack
B4B7 .assign_num_by_type←1← B2F3 JSR
LDA zp_fileblk ; Variable's size/type byte
B4B9 CMP #5 ; size 5 = real variable?
B4BB BEQ store_var_type ; yes: store as a real
B4BD LDA zp_var_type ; Type of the value
B4BF BEQ width_type_error ; string: Type mismatch
B4C1 BPL iwa_store_var ; integer: store raw, 4-byte wrap (no range check)
B4C3 JSR fwa_to_int ; real value, integer variable: convert
fall through ↓

Store the accumulator into an integer variable

Copy IWA into the integer variable addressed by &37. The width comes from &39, which is the lvalue type byte rather than a separate size field: it rode in as byte 2 of the stacked destination address (parse_lvalue packs the address in &2A/&2B and the type in &2C, and unstack_int_to_general lands byte 2 in &39). Type 0 (? byte indirection) writes 1 byte; type 4 (! word indirection, an integer variable, or an array element) writes 4.

On Entry(ZP_GENERAL) (&37/&38)a pointer to the integer variable
&39the lvalue type byte: 0 stores 1 byte, nonzero stores 4
On ExitXpreserved
B4C6 .iwa_store_var←1← B4C1 BPL
LDY #0 ; Offset 0
B4C8 LDA zp_iwa ; Store byte 1
B4CA STA (zp_general),y ; write it
B4CC LDA zp_fileblk ; Size byte: 0 -> 1-byte (? indir), else 4 bytes
B4CE BEQ return_34 ; 1-byte value: done
B4D0 LDA zp_iwa_1 ; Store byte 2
B4D2 INY ; next byte slot
B4D3 STA (zp_general),y ; write it
B4D5 LDA zp_iwa_2 ; Store byte 3
B4D7 INY ; next byte slot
B4D8 STA (zp_general),y ; write it
B4DA LDA zp_iwa_3 ; Store byte 4
B4DC INY ; next byte slot
B4DD STA (zp_general),y ; write it
B4DF .return_34←1← B4CE BEQ
RTS ; Return
B4E0 .store_var_type←1← B4BB BEQ
LDA zp_var_type ; Value type
B4E2 BEQ width_type_error ; string: Type mismatch
B4E4 BMI store_real ; real: store it
B4E6 JSR int_to_fwa ; integer: convert to real
B4E9 .store_real←2← B4E4 BMI← B77F JSR
LDY #0 ; Store the 5-byte real: exponent
B4EB LDA zp_fwa_exp ; load the exponent
B4ED STA (zp_general),y ; to variable+0
B4EF INY ; next byte slot
B4F0 LDA zp_fwa_sign ; Pack the sign into the mantissa
B4F2 AND #&80 ; keep only the sign bit
B4F4 STA zp_fwa_sign ; save it
B4F6 LDA zp_fwa_m1 ; mantissa top byte
B4F8 AND #&7f ; drop its implicit top bit
B4FA ORA zp_fwa_sign ; merge the sign bit in
B4FC STA (zp_general),y ; to variable+1
B4FE INY ; mantissa 2
B4FF LDA zp_fwa_m2 ; load it
B501 STA (zp_general),y ; to variable+2
B503 INY ; mantissa 3
B504 LDA zp_fwa_m3 ; load it
B506 STA (zp_general),y ; to variable+3
B508 INY ; mantissa 4
B509 LDA zp_fwa_m4 ; load it
B50B STA (zp_general),y ; to variable+4
B50D RTS ; Return

De-tokenise and print a character or token

Print A directly if below &80, otherwise look the token up in the keyword table at &8071 and print its expanded keyword text.

On EntryAthe character or keyword token to print
On ExitTHE OUTPUTthe character or expanded keyword
ZP_COUNT (&1E)the print column, advanced
ZP_GENERAL (&37/&38)corrupted (scratch)
B50E .print_token←3← 8571 JSR← B688 JSR← BFF0 JSR
STA zp_general ; Save the character
B510 CMP #&80 ; a token?
B512 BCC print_char ; no: print it directly
B514 LDA #&71 ; Point at the token table (&8071)
B516 STA zp_general_1 ; pointer low = &71
B518 LDA #&80 ; pointer high = &80
B51A STA zp_fileblk ; set it
B51C STY zp_fileblk_1 ; save Y
B51E .ptok_entry_start←2← B530 BCC← B534 BCS
LDY #0 ; Start of an entry
B520 .ptok_scan_loop←1← B523 BPL
INY ; Scan to the token byte
B521 LDA (zp_general_1),y ; read it
B523 BPL ptok_scan_loop ; ...(skip the keyword text)
B525 CMP zp_general ; this token?
B527 BEQ ptok_print ; yes: print its keyword
B529 INY ; Advance to the next entry
B52A TYA ; offset into A
B52B SEC ; set carry: +1 past the flag byte
B52C ADC zp_general_1 ; add to the pointer low,
B52E STA zp_general_1 ; store it
B530 BCC ptok_entry_start ; no carry: scan the next entry
B532 INC zp_fileblk ; else carry into the high byte
B534 BCS ptok_entry_start ; continue
B536 .ptok_print←1← B527 BEQ
LDY #0 ; Print the keyword text: from the start
B538 .ptok_print_loop←1← B540 BNE
LDA (zp_general_1),y ; Next character
B53A BMI ptok_restore_y ; token byte: done
B53C JSR print_char ; print it
B53F INY ; next
B540 BNE ptok_print_loop ; loop
B542 .ptok_restore_y←1← B53A BMI
LDY zp_fileblk_1 ; Restore Y
B544 RTS ; Return

Print A as two hex digits

Print the byte in A as two hex digits (high nibble then low) via print_hex_digit / print_char.

On EntryAthe byte to print
On ExitZP_COUNT (&1E)the print column, advanced
Acorrupted
Xcorrupted
Ycorrupted
B545 .print_hex_byte←2← 8526 JSR← B562 JSR
PHA ; Save the byte
B546 LSR ; High nibble
B547 LSR ; shift it down,
B548 LSR ; (continued)
B549 LSR ; (continued)
B54A JSR print_hex_digit ; print it
B54D PLA ; Low nibble
B54E AND #&0f ; keep the low nibble
fall through ↓

Print the low nibble of A as a hex digit

Convert A (0-15) to the ASCII hex digit 0-9 / A-F and fall into print_char to emit it with column tracking.

On EntryAa value 0-15 (the low nibble to print)
On ExitZP_COUNT (&1E)the print column, advanced
Acorrupted
Xcorrupted
Ycorrupted
B550 .print_hex_digit←1← B54A JSR
CMP #&0a ; above 9?
B552 BCC phd_ascii ; no
B554 ADC #6 ; adjust for A-F
B556 .phd_ascii←1← B552 BCC
ADC #'0' ; to ASCII, then fall into print_char
fall through ↓

Print a character with column tracking

Output A through the print formatter, handling CR specially and maintaining the print column COUNT. Auto-newlines when the column reaches WIDTH.

On EntryAthe character to print
ZP_COUNT (&1E)the current print column
ZP_WIDTH (&23)the print WIDTH limit
On ExitZP_COUNT (&1E)updated (reset by CR, else advanced)
Xcorrupted
Ycorrupted
B558 .print_char←13← 855C JSR← 855F JSR← 8E17 JSR← 8EA4 JSR← 9911 JSR← 9919 JSR← 9964 JSR← B512 BCC← B53C JSR← B583 JSR← B64B JSR← BA9F JSR← BC02 JSR
CMP #&0d ; carriage return?
B55A BNE print_char_body ; no: format and print
B55C JSR oswrch ; print the CR
B55F JMP reset_print_column ; reset the column

Print A as two hex digits then a space

Print the byte in A as two hexadecimal digits (print_hex_byte), then fall through into print_space to emit a trailing space through the print formatter, which auto-newlines when the column reaches WIDTH.

On EntryAthe byte to print as hex
ZP_COUNT (&1E)the current print column
ZP_WIDTH (&23)the print WIDTH limit
On ExitZP_COUNT (&1E)advanced past the digits and the space
Aspace (&20)
Xcorrupted
Ycorrupted
B562 .print_hex_space←2← 852B JSR← 854E JSR
JSR print_hex_byte ; Print A as hex then a space
fall through ↓

Print a space through the print formatter

Print a space via print_char, auto-newlining when the column reaches WIDTH.

On EntryZP_COUNT (&1E)the current print column
ZP_WIDTH (&23)the print WIDTH limit
On ExitZP_COUNT (&1E)advanced past the space
Aspace (&20)
Xcorrupted
Ycorrupted
B565 .print_space←9← 8544 JSR← 8559 JSR← 8DB5 JSR← 8E08 JSR← 8E5F JSR← 991C JMP← 995A JSR← B57E BMI← B580 JSR
LDA #' ' ; Space
B567 .print_char_body←1← B55A BNE
PHA ; Save the character
B568 LDA zp_width ; WIDTH limit
B56A CMP zp_count ; vs the current column
B56C BCS print_char_emit ; within the width
B56E JSR emit_newline ; else auto-newline
B571 .print_char_emit←1← B56C BCS
PLA ; Recover the character
B572 INC zp_count ; Advance the column
B574 JMP (wrchv) ; Print it via WRCHV

Print LISTO indentation

If the LISTO flag bit in A is set against zp_listo, print 2*X spaces of indentation for a LIST line.

On EntryAthe LISTO bit mask to test
Xthe indent depth (spaces = 2*X)
ZP_LISTO (&1F)the LISTO flags
On ExitTHE OUTPUTthe indentation spaces (none if disabled)
B577 .print_listo_indent←3← B626 JSR← B62D JSR← B634 JSR
AND zp_listo ; LISTO bit set?
B579 BEQ return_35 ; no: no indent
B57B TXA ; Indent count
B57C BEQ return_35 ; zero: none
B57E BMI print_space ; single space
B580 .listo_space_loop←1← B587 BNE
JSR print_space ; Print a space...
B583 JSR print_char ; ...and a space (two per level)
B586 DEX ; next level
B587 BNE listo_space_loop ; loop
B589 .return_35←2← B579 BEQ← B57C BEQ
RTS ; Return

LISTO - set the listing options

Evaluate the option value and store it in the LISTO flag (&1F), which controls LIST indentation and spacing. LISTO n.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitZP_LISTO (&1F)the new LISTO flags
CONTROLrejoins statement_loop
B58A .stmt_listo←1← B5A1 BEQ
INC zp_text_ptr_off ; Step past LISTO
B58C JSR eval_expr ; Evaluate the option value
B58F JSR assign_check_end ; check the statement ends
B592 JSR coerce_var_to_integer ; coerce to a byte
B595 LDA zp_iwa ; Store the LISTO flag
B597 STA zp_listo ; into &1F
B599 JMP immediate_loop ; next statement

LIST

List program lines, de-tokenising them. LIST [start[,end]].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B59C .stmt_list
INY ; Peek at the next character
B59D LDA (zp_text_ptr),y ; read it
B59F CMP #'O' ; 'O' (LISTO)?
B5A1 BEQ stmt_listo ; yes: set the option
B5A3 LDA #0 ; Clear the indent levels
B5A5 STA zp_fwb_sign ; FOR/REPEAT indent = 0,
B5A7 STA zp_fwb_ovf ; second indent = 0
B5A9 JSR int_result_a ; Start line default 0
B5AC JSR check_line_number ; Embedded start line number?
B5AF PHP ; remember whether one was given
B5B0 JSR stack_integer ; Stack the start line
B5B3 LDA #&ff ; End line default 32767
B5B5 STA zp_iwa ; low byte = &FF,
B5B7 LDA #&7f ; high byte &7F,
B5B9 STA zp_iwa_1 ; high byte (= 32767)
B5BB PLP ; Was a start line given?
B5BC BCC list_skip_spaces ; no: check for a range comma
B5BE JSR skip_spaces ; Skip spaces
B5C1 CMP #',' ; ',' range separator?
B5C3 BEQ list_end_line ; yes: read the end line
B5C5 JSR unstack_integer ; Single line: end = start
B5C8 JSR stack_integer ; re-stack it
B5CB DEC zp_text_ptr_off ; back up
B5CD BPL list_save_end ; always taken
B5CF .list_skip_spaces←1← B5BC BCC
JSR skip_spaces ; Skip spaces
B5D2 CMP #',' ; ',' range separator?
B5D4 BEQ list_end_line ; yes
B5D6 DEC zp_text_ptr_off ; back up
B5D8 .list_end_line←2← B5C3 BEQ← B5D4 BEQ
JSR check_line_number ; Read the end line number
B5DB .list_save_end←1← B5CD BPL
LDA zp_iwa ; Save the end line
B5DD STA zp_fwa_m1 ; low to &31,
B5DF LDA zp_iwa_1 ; high byte,
B5E1 STA zp_fwa_m2 ; high to &32
B5E3 JSR check_end_of_statement ; Check the statement ends
B5E6 JSR check_program ; Check a program is present
B5E9 JSR unstack_integer ; Recover the start line
B5EC JSR find_program_line ; Find the first line
B5EF LDA zp_fwb_exp ; Point at it
B5F1 STA zp_text_ptr ; pointer low,
B5F3 LDA zp_fwb_m1 ; high byte,
B5F5 STA zp_text_ptr_1 ; pointer high
B5F7 BCC list_past_end ; exact line: start here
B5F9 DEY ; back up the offset,
B5FA BCS list_check_high ; inexact: start at the next line
B5FC .list_line_loop←1← B63D BEQ
JSR emit_newline ; Newline after the previous line
B5FF JSR skip_to_statement_end ; check Escape
B602 .list_check_high←1← B5FA BCS
LDA (zp_text_ptr),y ; This line's number: high byte
B604 STA zp_iwa_1 ; store high,
B606 INY ; next byte
B607 LDA (zp_text_ptr),y ; ...low byte
B609 STA zp_iwa ; store low
B60B INY ; skip the length byte
B60C INY ; (continued)
B60D STY zp_text_ptr_off ; save the body offset
B60F .list_past_end←1← B5F7 BCC
LDA zp_iwa ; Past the end line?
B611 CLC ; clear carry,
B612 SBC zp_fwa_m1 ; line - end low,
B614 LDA zp_iwa_1 ; high byte,
B616 SBC zp_fwa_m2 ; line - end high
B618 BCC list_print_num ; no: list this line
B61A JMP immediate_loop ; yes: done
B61D .list_print_num←1← B618 BCC
JSR trace_print_number ; Print the line number
B620 LDX #&ff ; Reset the quote flag
B622 STX zp_coeff_ptr ; store it (&4D)
B624 LDA #1 ; Print the LISTO leading space
B626 JSR print_listo_indent ; do it
B629 LDX zp_fwb_sign ; FOR/REPEAT indent
B62B LDA #2 ; LISTO bit 1 indent
B62D JSR print_listo_indent ; do it
B630 LDX zp_fwb_ovf ; second indent level
B632 LDA #4 ; LISTO bit 2 indent
B634 JSR print_listo_indent ; do it
B637 .list_line_offset←1← B665 JMP
LDY zp_text_ptr_off ; Line offset
B639 .list_char_loop←2← B64F BNE← B68C BNE
LDA (zp_text_ptr),y ; Next character
B63B CMP #&0d ; end of line?
B63D BEQ list_line_loop ; yes: next line
B63F CMP #'"' ; quote?
B641 BNE list_in_quote ; no: a token or literal
B643 LDA #&ff ; toggle the quote flag
B645 EOR zp_coeff_ptr ; flip the flag,
B647 STA zp_coeff_ptr ; store it
B649 LDA #'"' ; print the quote
B64B .list_emit←1← B653 BPL
JSR print_char ; do it
B64E INY ; next character
B64F BNE list_char_loop ; continue the line
B651 .list_in_quote←1← B641 BNE
BIT zp_coeff_ptr ; Inside a quoted string?
B653 BPL list_emit ; yes: print literally
B655 CMP #&8d ; line-number token?
B657 BNE list_for_token ; no
B659 JSR decode_line_number ; decode the embedded line number
B65C STY zp_text_ptr_off ; save the advanced offset
B65E LDA #0 ; no field padding
B660 STA zp_print_bytes ; clear the field width
B662 JSR print_line_number ; print it
B665 JMP list_line_offset ; continue
B668 .list_for_token←1← B657 BNE
CMP #&e3 ; FOR token?
B66A BNE list_next_token ; no
B66C INC zp_fwb_sign ; increase the indent
B66E .list_next_token←1← B66A BNE
CMP #&ed ; NEXT token?
B670 BNE list_repeat_token ; no
B672 LDX zp_fwb_sign ; indent active?
B674 BEQ list_repeat_token ; no
B676 DEC zp_fwb_sign ; decrease the indent
B678 .list_repeat_token←2← B670 BNE← B674 BEQ
CMP #&f5 ; REPEAT token?
B67A BNE list_until_token ; no
B67C INC zp_fwb_ovf ; increase the indent
B67E .list_until_token←1← B67A BNE
CMP #&fd ; UNTIL token?
B680 BNE list_print_char ; no
B682 LDX zp_fwb_ovf ; indent active?
B684 BEQ list_print_char ; no
B686 DEC zp_fwb_ovf ; decrease the indent
B688 .list_print_char←2← B680 BNE← B684 BEQ
JSR print_token ; De-tokenise and print the character
B68B INY ; next
B68C BNE list_char_loop ; loop
B68E .list_error←2← B69C BEQ← B6A7 BEQ
BRK ; No FN error
B68F EQUS " No "
B693 EQUB &E3, &00

NEXT

End a FOR loop: update the counter and loop back unless the limit is passed. NEXT [var,...].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B695 .stmt_next←1← B763 JMP
JSR parse_var_ref ; Parse the optional control variable
B698 BNE next_bad_var ; a variable named after NEXT?
B69A LDX zp_for_level ; innermost FOR frame
B69C BEQ list_error ; no FOR active: error
B69E BCS next_found ; no variable: use the innermost loop
B6A0 .next_not_var←1← B6A3 BCS
JMP syntax_error ; not a variable: error
B6A3 .next_bad_var←1← B698 BNE
BCS next_not_var ; string/array: not a loop variable
B6A5 LDX zp_for_level ; Search the FOR stack for the named variable:
B6A7 BEQ list_error ; not found: error
B6A9 .next_find_frame←1← B6C5 BNE
LDA zp_iwa ; Find the matching FOR frame on the stack
B6AB CMP for_var_lo,x ; match the variable address low?
B6AE BNE next_discard_frame ; no: next frame
B6B0 LDA zp_iwa_1 ; address high
B6B2 CMP for_var_hi,x ; match the frame address high?
B6B5 BNE next_discard_frame ; no: next frame
B6B7 LDA zp_iwa_2 ; type
B6B9 CMP for_type,x ; match the frame type?
B6BC BEQ next_found ; match: this loop
B6BE .next_discard_frame←2← B6AE BNE← B6B5 BNE
TXA ; Not this loop: discard the frame and look outward
B6BF SEC ; step out one frame (15 bytes)
B6C0 SBC #&0f ; minus 15 bytes,
B6C2 TAX ; new frame index,
B6C3 STX zp_for_level ; update the FOR level
B6C5 BNE next_find_frame ; keep searching
B6C7 BRK ; No FOR error block
B6C8 EQUS "!Can't Match "
B6D5 EQUB &E3, &00
B6D7 .next_found←2← B69E BCS← B6BC BEQ
LDA for_var_lo,x ; Found: reload the control variable to update it
B6DA STA zp_iwa ; control variable address: low
B6DC LDA for_var_hi,x ; high
B6DF STA zp_iwa_1 ; address high
B6E1 LDY for_type,x ; type
B6E4 CPY #5 ; a real (float) loop?
B6E6 BEQ next_float ; yes: float NEXT
B6E8 LDY #0 ; Integer: add STEP to the variable:
B6EA LDA (zp_iwa),y ; byte 0
B6EC ADC for_step0,x ; + step byte 0
B6EF STA (zp_iwa),y ; (store)
B6F1 STA zp_general ; (keep)
B6F3 INY ; byte 1
B6F4 LDA (zp_iwa),y ; variable byte 1
B6F6 ADC for_step1,x ; + step byte 1
B6F9 STA (zp_iwa),y ; (store)
B6FB STA zp_general_1 ; (keep)
B6FD INY ; byte 2
B6FE LDA (zp_iwa),y ; variable byte 2
B700 ADC for_step2,x ; + step byte 2
B703 STA (zp_iwa),y ; (store)
B705 STA zp_fileblk ; (keep)
B707 INY ; byte 3
B708 LDA (zp_iwa),y ; variable byte 3
B70A ADC for_step3,x ; + step byte 3
B70D STA (zp_iwa),y ; (store)
B70F TAY ; (keep)
B710 LDA zp_general ; Compare the new value with LIMIT:
B712 SEC ; set carry for the subtract
B713 SBC for_limit0,x ; value - limit: byte 0
B716 STA zp_general ; (keep)
B718 LDA zp_general_1 ; byte 1
B71A SBC for_limit1,x ; minus limit byte 1
B71D STA zp_general_1 ; (keep)
B71F LDA zp_fileblk ; byte 2
B721 SBC for_limit2,x ; minus limit byte 2
B724 STA zp_fileblk ; (keep)
B726 TYA ; byte 3
B727 SBC for_limit3,x ; minus limit byte 3
B72A ORA zp_general ; exactly equal to the limit?
B72C ORA zp_general_1 ; OR byte 1,
B72E ORA zp_fileblk ; byte 2 (all zero => equal)
B730 BEQ next_continue ; at the limit: last iteration, continue
B732 TYA ; Past the limit? (sign of step vs difference)
B733 EOR for_step3,x ; XOR the step sign,
B736 EOR for_limit3,x ; XOR the limit sign,
B739 BPL next_exit ; sign positive: use the carry test
B73B BCS next_continue ; within range: continue
B73D BCC next_pop_frame ; past: exit the loop
B73F .next_exit←1← B739 BPL
BCS next_pop_frame ; past: exit the loop
B741 .next_continue←5← B730 BEQ← B73B BCS← B792 BEQ← B799 BCS← B79D BCC
LDY for_loopback_lo,x ; Continue: reload the loop-back position:
B744 LDA for_loopback_hi,x ; loop-back pointer high
B747 STY zp_text_ptr ; (text pointer)
B749 STA zp_text_ptr_1 ; text pointer high
B74B JSR reset_offset_1 ; restore the offset
B74E JMP next_statement ; jump back to the loop body
B751 .next_pop_frame←4← B73D BCC← B73F BCS← B79B BCC← B79F BCS
LDA zp_for_level ; Exit: pop the FOR frame:
B753 SEC ; set carry,
B754 SBC #&0f ; minus 15 bytes,
B756 STA zp_for_level ; pop the frame
B758 LDY zp_text_ptr2_off ; continue after NEXT:
B75A STY zp_text_ptr_off ; sync the program offset
B75C JSR skip_spaces ; another NEXT variable (comma)?
B75F CMP #',' ; ','?
B761 BNE next_end ; no: end of statement
B763 JMP stmt_next ; yes: handle the next variable
B766 .next_float←1← B6E6 BEQ
JSR load_real_var ; Float loop: load the control variable
B769 LDA zp_for_level ; point at the STEP in the frame:
B76B CLC ; clear carry,
B76C ADC #&f4 ; level + &F4,
B76E STA zp_fp_ptr ; pointer low,
B770 LDA #5 ; page &05,
B772 STA zp_fp_ptr_1 ; pointer high (STEP address)
B774 JSR fwa_add_var ; add STEP to the variable
B777 LDA zp_iwa ; store it back:
B779 STA zp_general ; var address low,
B77B LDA zp_iwa_1 ; high byte,
B77D STA zp_general_1 ; var address high
B77F JSR store_real ; store the updated variable
B782 LDA zp_for_level ; point at the LIMIT:
B784 STA zp_var_type ; stash the level in &27,
B786 CLC ; clear carry,
B787 ADC #&f9 ; level + &F9,
B789 STA zp_fp_ptr ; pointer low,
B78B LDA #5 ; page &05,
B78D STA zp_fp_ptr_1 ; pointer high (LIMIT address)
B78F JSR fp_compare ; compare the variable with LIMIT
B792 BEQ next_continue ; at the limit: continue
B794 LDA for_step1,x ; sign of STEP...
B797 BMI next_in_range ; negative step
B799 BCS next_continue ; within range: continue
B79B BCC next_pop_frame ; past: exit
B79D .next_in_range←1← B797 BMI
BCC next_continue ; within range: continue
B79F BCS next_pop_frame ; past: exit
B7A1 .next_end←1← B761 BNE
JMP stmt_backup_end ; End of statement
B7A4 .no_for_error←2← B7C7 BEQ← B7C9 BCS
BRK ; No FOR error block
B7A5 EQUB &22, &E3
B7A7 EQUS " variable"
B7B0 .too_many_fors←1← B7D8 BCS
BRK ; error block
B7B1 EQUS "#Too many "
B7BB EQUB &E3, &73
B7BD .cant_match_for←1← B7EF BNE
BRK ; error block
B7BE EQUS "$No "
B7C2 EQUB &B8, &00

FOR

Begin a counted loop, stacking the control variable, limit and step. FOR var = start TO limit [STEP step].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B7C4 .stmt_for
JSR parse_lvalue ; Parse the control variable (numvar =)
B7C7 BEQ no_for_error ; not a variable: error
B7C9 BCS no_for_error ; indirection: error
B7CB JSR stack_integer ; Stack the variable pointer
B7CE JSR expect_eq ; Expect "="
B7D1 JSR width_eval_value ; Assign the initial value
B7D4 LDY zp_for_level ; Index the FOR stack by nesting level
B7D6 CPY #&96 ; At most 10 nested FOR loops (10 * 15)
B7D8 BCS too_many_fors ; yes: error
B7DA LDA zp_general ; Store the variable pointer in the frame (+0)
B7DC STA for_stack,y ; pointer low (+0),
B7DF LDA zp_general_1 ; high byte,
B7E1 STA for_set_ptr_hi,y ; pointer high (+1)
B7E4 LDA zp_fileblk ; ...and its type (+2)
B7E6 STA for_set_type,y ; type (+2)
B7E9 TAX ; keep the type
B7EA JSR skip_spaces_ptr2 ; Next character
B7ED CMP #&b8 ; Require the TO keyword
B7EF BNE cant_match_for ; no: No TO error
B7F1 CPX #5 ; real loop variable?
B7F3 BEQ for_eval_limit ; yes: real FOR loop
B7F5 JSR eval_expr_integer ; Integer: evaluate the limit
B7F8 LDY zp_for_level ; FOR level
B7FA LDA zp_iwa ; Store the limit in the frame (+8)
B7FC STA for_set_limit0,y ; limit byte 0 (+8),
B7FF LDA zp_iwa_1 ; byte 1,
B801 STA for_set_limit1,y ; (+9),
B804 LDA zp_iwa_2 ; byte 2,
B806 STA for_set_limit2,y ; (+A),
B809 LDA zp_iwa_3 ; byte 3,
B80B STA for_set_limit3,y ; (+B)
B80E LDA #1 ; Default STEP = 1
B810 JSR int_result_a ; IWA = 1
B813 JSR skip_spaces_ptr2 ; Next character
B816 CMP #&88 ; Optional STEP (otherwise step defaults to 1)
B818 BNE for_sync ; no: use the default
B81A JSR eval_expr_integer ; Evaluate the step
B81D LDY zp_text_ptr2_off ; update the offset
B81F .for_sync←1← B818 BNE
STY zp_text_ptr_off ; Sync the program pointer
B821 LDY zp_for_level ; FOR level
B823 LDA zp_iwa ; Store the step in the frame (+3)
B825 STA for_set_step0,y ; step byte 0 (+3),
B828 LDA zp_iwa_1 ; byte 1,
B82A STA for_set_step1,y ; (+4),
B82D LDA zp_iwa_2 ; byte 2,
B82F STA for_set_step2,y ; (+5),
B832 LDA zp_iwa_3 ; byte 3,
B834 STA for_set_step3,y ; (+6)
B837 .for_skip_body←1← B885 JMP
JSR check_statement_terminated ; Step over the loop body to find its start
B83A LDY zp_for_level ; FOR level
B83C LDA zp_text_ptr ; Store the loop-body pointer (+D)
B83E STA for_set_loop_lo,y ; low (+D),
B841 LDA zp_text_ptr_1 ; high byte,
B843 STA for_set_loop_hi,y ; (+E)
B846 CLC ; Advance the FOR level by 15
B847 TYA ; level to A,
B848 ADC #&0f ; + 15,
B84A STA zp_for_level ; store it
B84C JMP next_statement ; Continue execution
B84F .for_eval_limit←1← B7F3 BEQ
JSR eval_or_eor ; Evaluate the limit
B852 JSR ensure_real ; ensure it is real
B855 LDA zp_for_level ; Point at frame +8 (limit slot)
B857 CLC ; level + 8:
B858 ADC #8 ; offset,
B85A STA zp_fp_ptr ; pointer low,
B85C LDA #5 ; page &05,
B85E STA zp_fp_ptr_1 ; pointer high
B860 JSR fwa_pack_var ; Pack the limit there
B863 JSR fwa_set_one ; Default STEP = 1.0
B866 JSR skip_spaces_ptr2 ; Next character
B869 CMP #&88 ; STEP token?
B86B BNE for_sync2 ; no: use the default
B86D JSR eval_or_eor ; Evaluate the step
B870 JSR ensure_real ; ensure it is real
B873 LDY zp_text_ptr2_off ; update the offset
B875 .for_sync2←1← B86B BNE
STY zp_text_ptr_off ; Sync the program pointer
B877 LDA zp_for_level ; Point at frame +3 (step slot)
B879 CLC ; level + 3:
B87A ADC #3 ; offset,
B87C STA zp_fp_ptr ; pointer low,
B87E LDA #5 ; page &05,
B880 STA zp_fp_ptr_1 ; pointer high
B882 JSR fwa_pack_var ; Pack the step there
B885 JMP for_skip_body ; Join the common tail

GOSUB

Call a subroutine at a line number, stacking the return position. GOSUB line.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B888 .stmt_gosub
JSR find_line_target ; Resolve the destination line
B88B .gosub_check_end←1← B97A JMP
JSR check_end_of_statement ; Check the statement ends
B88E LDY zp_gosub_level ; Index the GOSUB return stack
B890 CPY #&1a ; At most 26 nested GOSUBs
B892 BCS err_too_many_gosubs ; too many: error
B894 LDA zp_text_ptr ; Push the return position (text pointer)
B896 STA gosub_stack,y ; low byte
B899 LDA zp_text_ptr_1 ; return position high byte
B89B STA gosub_stack_hi,y ; store it
B89E INC zp_gosub_level ; one more nesting level
B8A0 BCC goto_trace ; jump to the line
B8A2 .err_too_many_gosubs←1← B892 BCS
BRK ; Too many GOSUBs error
B8A3 EQUS "%Too many "
B8AD EQUB &E4, &73
B8AF .err_no_gosub←1← B8BB BEQ
BRK ; No GOSUB error
B8B0 EQUS "&No "
B8B4 EQUB &E4, &00

RETURN

Return from a GOSUB to the stacked return position. RETURN.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B8B6 .stmt_return
JSR check_end_of_statement ; Check the statement ends
B8B9 LDX zp_gosub_level ; RETURN with nothing on the GOSUB stack: error
B8BB BEQ err_no_gosub ; nothing stacked: error
B8BD DEC zp_gosub_level ; Pop the return position
B8BF LDY gosub_return_lo,x ; return position low byte
B8C2 LDA gosub_return_hi,x ; ...high byte
B8C5 STY zp_text_ptr ; restore the text pointer
B8C7 STA zp_text_ptr_1 ; high byte
B8C9 JMP statement_loop ; Resume execution after the GOSUB

GOTO

Jump to a line number. GOTO line.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B8CC .stmt_goto
JSR find_line_target ; Resolve the destination line
B8CF JSR check_end_of_statement ; check the statement ends
B8D2 .goto_trace←3← 98EE JMP← B8A0 BCC← B967 JMP
LDA zp_trace_flag ; TRACE: report the destination line number
B8D4 BEQ goto_dest_ptr ; TRACE off?
B8D6 JSR trace_line ; trace the line
B8D9 .goto_dest_ptr←1← B8D4 BEQ
LDY zp_fwb_exp ; Destination line pointer
B8DB LDA zp_fwb_m1 ; high byte
B8DD .goto_jump←1← BBD3 JMP
STY zp_text_ptr ; Point the interpreter at the destination line
B8DF STA zp_text_ptr_1 ; PtrA high
B8E1 JMP next_statement ; execute from there
B8E4 .goto_check_end←1← B8F7 BEQ
JSR check_end_of_statement ; Check the statement ends
B8E7 LDA #&33 ; Restore the default error handler
B8E9 STA zp_error_vec ; low byte = &33,
B8EB LDA #&b4 ; high byte = &B4,
B8ED STA zp_error_vec_1 ; handler at &B433
B8EF JMP statement_loop ; next statement
B8F2 .goto_char_loop←1← B91A BEQ
JSR skip_spaces ; Next character
B8F5 CMP #&87 ; OFF token?
B8F7 BEQ goto_check_end ; yes: ON ERROR OFF
B8F9 LDY zp_text_ptr_off ; Point at the handler statement
B8FB DEY ; back up,
B8FC JSR skip_to_statement_end ; check Escape
B8FF LDA zp_text_ptr ; Set the error handler to this line
B901 STA zp_error_vec ; low byte,
B903 LDA zp_text_ptr_1 ; high byte,
B905 STA zp_error_vec_1 ; store it
B907 JMP stmt_data ; skip the rest of the line
B90A .on_syntax_error←1← B92F BNE
BRK ; ON syntax error
B90B EQUB &27, &EE
B90D EQUS " syntax"
B914 EQUB &00

ON

ON expr GOTO/GOSUB computed jump, or ON ERROR error trapping. ON expr GOTO/GOSUB list | ON ERROR stmts.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
B915 .stmt_on
JSR skip_spaces ; Next character
B918 CMP #&85 ; ERROR token?
B91A BEQ goto_char_loop ; yes: ON ERROR
B91C DEC zp_text_ptr_off ; Back up over the character
B91E JSR eval_expr ; Evaluate the selector
B921 JSR coerce_to_integer ; coerce to integer
B924 LDY zp_text_ptr2_off ; Advance past it
B926 INY ; past the token,
B927 STY zp_text_ptr_off ; sync the offset
B929 CPX #&e5 ; GOTO token?
B92B BEQ on_save_token ; yes
B92D CPX #&e4 ; GOSUB token?
B92F BNE on_syntax_error ; no: syntax error
B931 .on_save_token←1← B92B BEQ
TXA ; Save the GOTO/GOSUB token
B932 PHA ; push it
B933 LDA zp_iwa_1 ; Selector > 255?
B935 ORA zp_iwa_2 ; OR byte 2,
B937 ORA zp_iwa_3 ; byte 3 (any => > 255)
B939 BNE on_out_of_range ; yes: out of range
B93B LDX zp_iwa ; Selector zero?
B93D BEQ on_out_of_range ; yes: out of range
B93F DEX ; Count down to the n-th destination
B940 BEQ on_read_dest ; reached it: use the first
B942 LDY zp_text_ptr_off ; Line index
B944 .on_char_loop←2← B955 BNE← B958 BNE
LDA (zp_text_ptr),y ; Next character
B946 INY ; advance
B947 CMP #&0d ; end of line?
B949 BEQ on_out_of_range ; yes: out of range
B94B CMP #':' ; ':' end of statement?
B94D BEQ on_out_of_range ; yes: out of range
B94F CMP #&8b ; ELSE?
B951 BEQ on_out_of_range ; yes: out of range
B953 CMP #',' ; ',' separator?
B955 BNE on_char_loop ; no: keep scanning
B957 DEX ; count this destination
B958 BNE on_char_loop ; not yet reached: continue
B95A STY zp_text_ptr_off ; Save the line index
B95C .on_read_dest←1← B940 BEQ
JSR find_line_target ; Read the destination line number
B95F PLA ; Recover the token
B960 CMP #&e4 ; GOSUB?
B962 BEQ on_gosub_ptr ; yes
B964 JSR reset_offset_1 ; GOTO: update the index and check Escape
B967 JMP goto_trace ; jump to the line
B96A .on_gosub_ptr←1← B962 BEQ
LDY zp_text_ptr_off ; GOSUB: line pointer
B96C .on_skip_loop←1← B975 BNE
LDA (zp_text_ptr),y ; Next character
B96E INY ; advance
B96F CMP #&0d ; end of line?
B971 BEQ on_return_index ; yes: return point here
B973 CMP #':' ; ':' separator?
B975 BNE on_skip_loop ; no: keep scanning to the return point
B977 .on_return_index←1← B971 BEQ
DEY ; Set the return index
B978 STY zp_text_ptr_off ; save it
B97A JMP gosub_check_end ; do the GOSUB
B97D .on_out_of_range←5← B939 BNE← B93D BEQ← B949 BEQ← B94D BEQ← B951 BEQ
LDY zp_text_ptr_off ; Out of range: line index
B97F PLA ; drop the token
B980 .on_scan_loop←1← B989 BNE
LDA (zp_text_ptr),y ; Next character
B982 INY ; advance
B983 CMP #&8b ; ELSE?
B985 BEQ on_else ; yes: use it
B987 CMP #&0d ; end of line?
B989 BNE on_scan_loop ; no: keep scanning
B98B BRK ; ON range error
B98C EQUB &28, &EE
B98E EQUS " range"
B994 EQUB &00
B995 .on_else←1← B985 BEQ
STY zp_text_ptr_off ; Step past ELSE
B997 JMP if_then_line ; execute what follows

Resolve a line-number operand to a program line

Read a line-number argument (an embedded tokenised line number or an evaluated integer expression) and locate that line in the program via find_program_line. Used by GOTO, GOSUB and RESTORE. Raises "No such line" if absent.

On EntryZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset at the line-number operand
On Exit(ZP_FWB_EXP) (&3D/&3E)a pointer to the target line
BRKNo such line if the line is absent
B99A .find_line_target←4← B888 JSR← B8CC JSR← B95C JSR← BAFF JSR
JSR check_line_number ; Embedded line-number token?
B99D BCS flt_find ; yes: use it
B99F JSR eval_expr ; Evaluate the line-number expression
B9A2 JSR coerce_to_integer ; ensure integer
B9A5 LDA zp_text_ptr2_off ; Update the program pointer
B9A7 STA zp_text_ptr_off ; from the PtrB offset
B9A9 LDA zp_iwa_1 ; Mask the high byte to 7 bits
B9AB AND #&7f ; (so GOTO &8000+n == GOTO n)
B9AD STA zp_iwa_1 ; store the masked high byte
B9AF .flt_find←2← 98E8 JSR← B99D BCS
JSR find_program_line ; Find the line
B9B2 BCS err_no_such_line ; not found: No such line
B9B4 RTS ; Return
B9B5 .err_no_such_line←1← B9B2 BCS
BRK ; No such line error
B9B6 EQUS ")No such line"
B9C3 EQUB &00
B9C4 .flt_type_error←2← BA00 BNE← BA1B BEQ
JMP err_type_mismatch ; Type mismatch error
B9C7 .flt_mistake←1← B9E7 BEQ
JMP syntax_error ; Mistake error
B9CA .flt_sync←1← B9DF BNE
STY zp_text_ptr_off ; Sync the pointer
B9CC JMP stmt_check_end ; check the statement ends
B9CF .inputf_skip_hash←1← BA49 BEQ
DEC zp_text_ptr_off ; Back up over "#"
B9D1 JSR sync_ptrb_from_ptra ; Get the file handle
B9D4 LDA zp_text_ptr2_off ; Sync the program pointer
B9D6 STA zp_text_ptr_off ; from the PtrB offset
B9D8 STY zp_coeff_ptr ; save the handle
B9DA .inputf_skip_spaces←2← BA16 JMP← BA3C JMP
JSR skip_spaces ; Skip spaces
B9DD CMP #',' ; ',' another variable?
B9DF BNE flt_sync ; no: done
B9E1 LDA zp_coeff_ptr ; Save the handle
B9E3 PHA ; push it
B9E4 JSR parse_lvalue ; Parse the target variable
B9E7 BEQ flt_mistake ; end: error
B9E9 LDA zp_text_ptr2_off ; Sync the program pointer
B9EB STA zp_text_ptr_off ; from the PtrB offset
B9ED PLA ; Recover the handle
B9EE STA zp_coeff_ptr ; store it (&4D),
B9F0 PHP ; save the type flags
B9F1 JSR stack_integer ; Stack the variable address
B9F4 LDY zp_coeff_ptr ; Handle
B9F6 JSR osbget ; Read the type byte
B9F9 STA zp_var_type ; save it
B9FB PLP ; restore the type flags
B9FC BCC inputf_numeric ; string?
B9FE LDA zp_var_type ; String type byte
BA00 BNE flt_type_error ; mismatch: error
BA02 JSR osbget ; Read the length
BA05 STA zp_strbuf_len ; store the length,
BA07 TAX ; into X as the count
BA08 BEQ inputf_assign_str ; empty
BA0A .inputf_read_loop←1← BA11 BNE
JSR osbget ; Read a character
BA0D STA strbuf_base,x ; into the buffer (reversed),
BA10 DEX ; next character
BA11 BNE inputf_read_loop ; loop
BA13 .inputf_assign_str←1← BA08 BEQ
JSR assign_string ; Assign the string
BA16 JMP inputf_skip_spaces ; next variable
BA19 .inputf_numeric←1← B9FC BCC
LDA zp_var_type ; Numeric type byte
BA1B BEQ flt_type_error ; mismatch: error
BA1D BMI inputf_real ; real?
BA1F LDX #3 ; Integer: 4 bytes
BA21 .inputf_int_loop←1← BA27 BPL
JSR osbget ; Read a byte
BA24 STA zp_iwa,x ; into IWA (MSB first),
BA26 DEX ; next byte
BA27 BPL inputf_int_loop ; loop
BA29 BMI inputf_assign_num ; assign
BA2B .inputf_real←1← BA1D BMI
LDX #4 ; Real: 5 bytes
BA2D .inputf_real_loop←1← BA34 BPL
JSR osbget ; Read a byte
BA30 STA fp_temp1,x ; into TEMP1 (MSB first),
BA33 DEX ; next byte
BA34 BPL inputf_real_loop ; loop
BA36 JSR fwa_unpack_temp1 ; unpack into FWA
BA39 .inputf_assign_num←1← BA29 BMI
JSR assign_number ; Assign the number
BA3C JMP inputf_skip_spaces ; next variable
BA3F .inputf_drop_loop←1← BA82 BEQ
PLA ; Drop the stacked values
BA40 PLA ; (continued)
BA41 JMP stmt_check_end ; done

INPUT

Read values from the keyboard, or a file with #, into variables. INPUT [LINE] [prompt] var,...

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BA44 .stmt_input
JSR skip_spaces ; Next non-space character
BA47 CMP #'#' ; '#': INPUT# from a file
BA49 BEQ inputf_skip_hash ; go handle INPUT#
BA4B CMP #&86 ; LINE token?
BA4D BEQ input_line_flag ; yes: LINE mode (carry set)
BA4F DEC zp_text_ptr_off ; no: step back, carry clear
BA51 CLC ; carry clear (not LINE)
BA52 .input_line_flag←1← BA4D BEQ
ROR zp_coeff_ptr ; Record the LINE flag in bit 6
BA54 LSR zp_coeff_ptr ; shift it into bit 6
BA56 LDA #&ff ; Prompt flag = -1
BA58 STA zp_coeff_ptr_1 ; store it (&4E)
BA5A .input_prompt←4← BA71 BEQ← BA75 BEQ← BAD9 JMP← BAE3 JMP
JSR print_special_skip ; Process a prompt item
BA5D BCS input_item_flag ; none found: parse a variable
BA5F .input_prompt_loop←1← BA62 BCC
JSR print_special_skip ; Process further prompt items
BA62 BCC input_prompt_loop ; more printed items: loop
BA64 LDX #&ff ; A printed item suppresses the ? prompt
BA66 STX zp_coeff_ptr_1 ; flag = -1 (printed),
BA68 CLC ; carry clear
BA69 .input_item_flag←1← BA5D BCS
PHP ; Preserve the item-seen flag
BA6A ASL zp_coeff_ptr ; shift out the old bit,
BA6C PLP ; recover the flag,
BA6D ROR zp_coeff_ptr ; rotate it into bit 7
BA6F CMP #',' ; ',' next item?
BA71 BEQ input_prompt ; yes
BA73 CMP #';' ; ';' next item?
BA75 BEQ input_prompt ; yes
BA77 DEC zp_text_ptr_off ; Back up to the variable
BA79 LDA zp_coeff_ptr ; Save the flags...
BA7B PHA ; push &4D,
BA7C LDA zp_coeff_ptr_1 ; &4E,
BA7E PHA ; push it
BA7F JSR parse_lvalue ; Parse the target variable
BA82 BEQ inputf_drop_loop ; end of statement: done
BA84 PLA ; Restore the flags
BA85 STA zp_coeff_ptr_1 ; &4E,
BA87 PLA ; pull &4D,
BA88 STA zp_coeff_ptr ; store it
BA8A LDA zp_text_ptr2_off ; Update the program pointer
BA8C STA zp_text_ptr_off ; from the PtrB offset
BA8E PHP ; Save the LINE flag
BA8F BIT zp_coeff_ptr ; Still reading the current input line?
BA91 BVS input_line_mode ; yes: no new prompt
BA93 LDA zp_coeff_ptr_1 ; Prompt flag
BA95 CMP #&ff ; item already printed?
BA97 BNE input_set_offset ; yes: read without a ? prompt
BA99 .input_line_mode←1← BA91 BVS
BIT zp_coeff_ptr ; LINE mode?
BA9B BPL input_read_line ; yes: no ? prompt
BA9D LDA #'?' ; Print '?'
BA9F JSR print_char ; do it
BAA2 .input_read_line←1← BA9B BPL
JSR point_string_buffer ; Read an input line
BAA5 STY zp_strbuf_len ; Store its length
BAA7 ASL zp_coeff_ptr ; Mark the input line as fresh
BAA9 CLC ; clear carry,
BAAA ROR zp_coeff_ptr ; rotate into bit 7
BAAC BIT zp_coeff_ptr ; LINE mode?
BAAE BVS input_recover_flag ; yes: take the whole line
BAB0 .input_set_offset←1← BA97 BNE
STA zp_text_ptr2_off ; Set the read offset
BAB2 LDA #0 ; Point at the input buffer (&0600)
BAB4 STA zp_text_ptr2 ; PtrB low = 0,
BAB6 LDA #6 ; page &06,
BAB8 STA zp_text_ptr2_1 ; PtrB high
BABA JSR read_string_literal ; Read the field literal
BABD .input_skip_spaces←1← BAC6 BNE
JSR skip_spaces_ptr2 ; Skip spaces
BAC0 CMP #',' ; ',' field delimiter?
BAC2 BEQ input_field_offset ; yes
BAC4 CMP #&0d ; end of line?
BAC6 BNE input_skip_spaces ; no: keep scanning
BAC8 LDY #&fe ; mark end of input
BACA .input_field_offset←1← BAC2 BEQ
INY ; Note the next field offset
BACB STY zp_coeff_ptr_1 ; store it (&4E)
BACD .input_recover_flag←1← BAAE BVS
PLP ; Recover the LINE flag
BACE BCS input_line_string ; LINE mode: assign the whole line
BAD0 JSR stack_integer ; Stack the variable address
BAD3 JSR ascii_to_number ; Parse the field as a number
BAD6 JSR assign_number ; assign it
BAD9 JMP input_prompt ; next variable
BADC .input_line_string←1← BACE BCS
LDA #0 ; LINE: string type
BADE STA zp_var_type ; type 0 (string)
BAE0 JSR assign_string_to ; assign the line as a string
BAE3 JMP input_prompt ; next variable

RESTORE

Reset the DATA pointer, optionally to a given line. RESTORE [line].

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BAE6 .stmt_restore
LDY #0 ; DATA pointer = PAGE
BAE8 STY zp_fwb_exp ; low byte 0 (&3D),
BAEA LDY zp_page ; PAGE,
BAEC STY zp_fwb_m1 ; high byte (&3E)
BAEE JSR skip_spaces ; Skip spaces
BAF1 DEC zp_text_ptr_off ; back up
BAF3 CMP #':' ; ':' end?
BAF5 BEQ restore_check_end ; yes: restore to PAGE
BAF7 CMP #&0d ; end of line?
BAF9 BEQ restore_check_end ; yes
BAFB CMP #&8b ; ELSE?
BAFD BEQ restore_check_end ; yes
BAFF JSR find_line_target ; Find the given line
BB02 LDY #1 ; point at it
BB04 JSR fwb_scale_clc ; set the pointer to the line
BB07 .restore_check_end←3← BAF5 BEQ← BAF9 BEQ← BAFD BEQ
JSR check_end_of_statement ; Check the statement ends
BB0A LDA zp_fwb_exp ; Set the DATA pointer
BB0C STA zp_data_ptr ; low byte (&1C),
BB0E LDA zp_fwb_m1 ; high byte,
BB10 STA zp_data_ptr_1 ; high (&1D)
BB12 JMP statement_loop ; next statement
BB15 .restore_skip_spaces←2← BB22 BEQ← BB4D JMP
JSR skip_spaces ; Skip spaces
BB18 CMP #',' ; ','?
BB1A BEQ stmt_read ; yes: READ
BB1C JMP stmt_backup_end ; next statement

READ

Read values from DATA statements into variables. READ var,...

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BB1F .stmt_read←1← BB1A BEQ
JSR parse_lvalue ; Parse the target variable
BB22 BEQ restore_skip_spaces ; end of statement: done
BB24 BCS read_string ; string variable?
BB26 JSR next_data_item ; Numeric: find the next DATA item
BB29 JSR stack_integer ; Stack the variable address
BB2C JSR width_eval_value ; read the value and assign it
BB2F JMP read_advance ; update the DATA pointer
BB32 .read_string←1← BB24 BCS
JSR next_data_item ; String: find the next DATA item
BB35 JSR stack_integer ; Stack the variable address
BB38 JSR read_string_literal ; read the DATA item as a string
BB3B STA zp_var_type ; string type
BB3D JSR assign_string ; assign it
BB40 .read_advance←1← BB2F JMP
CLC ; Advance the DATA pointer past the item
BB41 LDA zp_text_ptr2_off ; PtrB offset,
BB43 ADC zp_text_ptr2 ; + PtrB low,
BB45 STA zp_data_ptr ; DATA pointer low,
BB47 LDA zp_text_ptr2_1 ; PtrB high,
BB49 ADC #0 ; + carry,
BB4B STA zp_data_ptr_1 ; DATA pointer high
BB4D JMP restore_skip_spaces ; next variable

Advance the DATA pointer to the next item

Move the DATA pointer past the current item to the next comma- or DATA-separated value, searching forward through the program for the next DATA statement at end of line; raises Out of DATA.

On EntryZP_DATA_PTR (&1C)the current DATA read position
On ExitZP_TEXT_PTR2 (&19/&1A)PtrB at the next DATA value
ZP_DATA_PTR (&1C)advanced
BRKOut of DATA if none remain
BB50 .next_data_item←2← BB26 JSR← BB32 JSR
LDA zp_text_ptr2_off ; Save the program pointer
BB52 STA zp_text_ptr_off ; (save)
BB54 LDA zp_data_ptr ; Point at the DATA position
BB56 STA zp_text_ptr2 ; (low)
BB58 LDA zp_data_ptr_1 ; high...
BB5A STA zp_text_ptr2_1 ; (high)
BB5C LDY #0 ; From offset 0
BB5E STY zp_text_ptr2_off ; (offset 0)
BB60 JSR skip_spaces_ptr2 ; Next character
BB63 CMP #',' ; ',' item separator?
BB65 BEQ return_36 ; yes: at the next item
BB67 CMP #&dc ; DATA token?
BB69 BEQ return_36 ; yes: at the first item
BB6B CMP #&0d ; end of line?
BB6D BEQ ndi_line_marker ; yes: find the next DATA line
BB6F .ndi_scan_loop←1← BB78 BNE
JSR skip_spaces_ptr2 ; Scan to the item end: next character
BB72 CMP #',' ; ',' separator?
BB74 BEQ return_36 ; yes
BB76 CMP #&0d ; end of line?
BB78 BNE ndi_scan_loop ; no: keep scanning
BB7A .ndi_line_marker←3← BB6D BEQ← BB96 BCC← BB9A BCS
LDY zp_text_ptr2_off ; Line marker
BB7C LDA (zp_text_ptr2),y ; read it
BB7E BMI out_of_data ; end of program: Out of DATA
BB80 INY ; Skip the line number
BB81 INY ; past the low byte
BB82 LDA (zp_text_ptr2),y ; Line length
BB84 TAX ; X = length
BB85 .ndi_scan_loop2←1← BB8A BEQ
INY ; Next character
BB86 LDA (zp_text_ptr2),y ; read it
BB88 CMP #' ' ; space?
BB8A BEQ ndi_scan_loop2 ; skip leading spaces
BB8C CMP #&dc ; DATA token?
BB8E BEQ ndi_skip_token ; yes: use this line
BB90 TXA ; Advance to the next line
BB91 CLC ; add the length...
BB92 ADC zp_text_ptr2 ; to the pointer low,
BB94 STA zp_text_ptr2 ; (store)
BB96 BCC ndi_line_marker ; no carry
BB98 INC zp_text_ptr2_1 ; carry into high
BB9A BCS ndi_line_marker ; continue
BB9C .out_of_data←1← BB7E BMI
BRK ; Out of DATA error
BB9D EQUS "*Out of "
BBA5 EQUB &DC
BBA6 .err_no_repeat←1← BBBC BEQ
BRK ; No REPEAT error
BBA7 EQUS "+No "
BBAB EQUB &F5, &00
BBAD .ndi_skip_token←1← BB8E BEQ
INY ; Step past the DATA token
BBAE STY zp_text_ptr2_off ; record the offset
BBB0 .return_36←3← BB65 BEQ← BB69 BEQ← BB74 BEQ
RTS ; Return

UNTIL

End a REPEAT loop: loop back unless the condition is true. UNTIL expr.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BBB1 .stmt_until
JSR eval_expr ; Evaluate the UNTIL condition
BBB4 JSR assign_check_end ; Check for end of statement
BBB7 JSR coerce_var_to_integer ; coerce the condition to an integer
BBBA LDX zp_repeat_level ; UNTIL with no REPEAT pending: error
BBBC BEQ err_no_repeat ; UNTIL with no REPEAT: error
BBBE LDA zp_iwa ; Test the condition for true (non-zero):
BBC0 ORA zp_iwa_1 ; byte 1,
BBC2 ORA zp_iwa_2 ; byte 2,
BBC4 ORA zp_iwa_3 ; byte 3
BBC6 BEQ until_reload ; Condition false: loop back to the REPEAT
BBC8 DEC zp_repeat_level ; Condition true: pop the frame and continue
BBCA JMP statement_loop ; true: exit the loop, continue
BBCD .until_reload←1← BBC6 BEQ
LDY repeat_loop_lo,x ; Reload the saved loop-start position
BBD0 LDA repeat_loop_hi,x ; Reload the loop position: high
BBD3 JMP goto_jump ; jump back to the REPEAT
BBD6 .err_too_many_repeats←1← BBE8 BCS
BRK ; BRK error block: too many REPEATs
BBD7 EQUS ",Too many "
BBE1 EQUB &F5, &73, &00

REPEAT

Begin a REPEAT...UNTIL loop, stacking the loop position. REPEAT.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BBE4 .stmt_repeat
LDX zp_repeat_level ; Index the REPEAT stack
BBE6 CPX #&14 ; At most 20 nested REPEATs
BBE8 BCS err_too_many_repeats ; Too many nested REPEATs
BBEA JSR skip_to_statement_end ; Point PtrA at the loop start
BBED LDA zp_text_ptr ; Push the loop-start position
BBEF STA repeat_stack,x ; Store the loop position: low
BBF2 LDA zp_text_ptr_1 ; high
BBF4 STA repeat_stack_hi,x ; (store)
BBF7 INC zp_repeat_level ; One more REPEAT outstanding
BBF9 JMP next_statement ; Continue execution
BBFC .point_string_buffer←1← BAA2 JSR
LDY #0 ; Point at the string buffer (&0600): low
BBFE LDA #6 ; high
BC00 BNE ril_set_addr ; set it (shared tail)
fall through ↓

Print the prompt and read a line

Print the character in A as a prompt, then read a line into the input buffer at &0700 via OSWORD 0.

On EntryAthe prompt character to print
On ExitTHE INPUT BUFFER (&0700)the line read (CR terminated)
Cset if the read was terminated by Escape
BC02 .read_input_line←2← 8B08 JSR← 90BD JSR
JSR print_char ; Print the prompt character
BC05 LDY #0 ; Input buffer at &0700
BC07 LDA #7 ; high byte &07
BC09 .ril_set_addr←1← BC00 BNE
STY zp_general ; address low = &00
BC0B STA zp_general_1 ; address high = &07
BC0D LDA #&ee ; Max length 238
BC0F STA zp_fileblk ; store it
BC11 LDA #' ' ; Lowest accepted character
BC13 STA zp_fileblk_1 ; store it
BC15 LDY #&ff ; Highest accepted character (&FF)
BC17 STY zp_fwb_sign ; store it
BC19 INY ; Y wraps back to 0
BC1A LDX #&37 ; Point at the OSWORD block
BC1C TYA ; A = 0: OSWORD read line
BC1D JSR osword ; Read a line
BC20 BCC reset_print_column ; ok: reset the column
BC22 JMP escape_error ; Escape: raise it

Print a newline and reset the print column

Emit a newline via OSNEWL, then fall through into reset_print_column to zero the print-column counter. The standard way BASIC ends an output line so the column-tracking auto-newline logic stays in step.

On ExitZP_COUNT (&1E)reset to 0 (print column)
A0
Xpreserved
Ypreserved
BC25 .emit_newline←9← 853F JSR← 857B JSR← 8D7D JSR← 8E53 JSR← 8E67 JSR← 909A JSR← B56E JSR← B5FC JSR← BFE7 JSR
JSR osnewl ; Print a newline
BC28 .reset_print_column←4← 8EC7 JSR← 93D7 JSR← B55F JMP← BC20 BCC
LDA #0 ; Reset the column to 0
BC2A STA zp_count ; store it
BC2C RTS ; Return

Delete a program line and close the gap

Find the line (find_program_line), then shift every later line down over it and update TOP. No-op if the line is absent.

On EntryZP_IWA (&2A/&2B)the line number to delete
On ExitTHE PROGRAMthe line removed and memory compacted
ZP_TOP (&12/&13)TOP reduced by the deleted line
BC2D .delete_program_line←2← 8F53 JSR← BC8F JSR
JSR find_program_line ; Find the line
BC30 BCS return_37 ; not present: nothing to do
BC32 LDA zp_fwb_exp ; Destination = line start
BC34 SBC #2 ; line start - 2,
BC36 STA zp_general ; destination low (&37),
BC38 STA zp_fwb_exp ; also &3D,
BC3A STA zp_top ; and TOP (&12),
BC3C LDA zp_fwb_m1 ; high byte,
BC3E SBC #0 ; with borrow,
BC40 STA zp_general_1 ; destination high (&38),
BC42 STA zp_top_1 ; and TOP+1 (&13),
BC44 STA zp_fwb_m1 ; and &3E
BC46 LDY #3 ; Line length
BC48 LDA (zp_general),y ; read it (offset 3)
BC4A CLC ; Source = the next line
BC4B ADC zp_general ; destination + length,
BC4D STA zp_general ; source low,
BC4F BCC del_copy_start ; no carry into the high byte
BC51 INC zp_general_1 ; carry into the high byte
BC53 .del_copy_start←1← BC4F BCC
LDY #0 ; From offset 0
BC55 .del_copy_loop←2← BC5E BNE← BC64 BNE
LDA (zp_general),y ; Copy a byte down
BC57 STA (zp_top),y ; to the destination
BC59 CMP #&0d ; end of line?
BC5B BEQ del_skip_cr ; yes: handle the line boundary
BC5D .del_copy_next←1← BC79 JMP
INY ; next byte
BC5E BNE del_copy_loop ; no page wrap
BC60 INC zp_general_1 ; cross a page
BC62 INC zp_top_1 ; destination page too
BC64 BNE del_copy_loop ; continue
BC66 .del_skip_cr←1← BC5B BEQ
INY ; Step past the CR
BC67 BNE del_next_marker ; no page wrap,
BC69 INC zp_general_1 ; source page,
BC6B INC zp_top_1 ; destination page
BC6D .del_next_marker←1← BC67 BNE
LDA (zp_general),y ; Next line marker
BC6F STA (zp_top),y ; copy it down
BC71 BMI del_set_top ; end of program?
BC73 JSR copy_byte ; no: copy the line number
BC76 JSR copy_byte ; ...and the length byte
BC79 JMP del_copy_next ; continue with the line body
BC7C .del_set_top←1← BC71 BMI
JSR add_y_to_top ; Set the new top of program
BC7F CLC ; carry clear: line was deleted
BC80 .return_37←1← BC30 BCS
RTS ; Return

Copy one byte, advancing the offset

Increment the shared offset Y (bumping both pointer high bytes on a page crossing), then copy one byte from (zp_general) to (zp_top). The per-byte helper used by delete_program_line to copy the line-number and length bytes while compacting the program.

On EntryYthe current copy offset
(ZP_GENERAL) (&37/&38)source pointer
(ZP_TOP) (&12/&13)destination pointer
On ExitYadvanced by 1 (with page carry into &38/&13)
Athe byte copied
(ZP_TOP) (&12/&13)receives the copied byte
BC81 .copy_byte←2← BC73 JSR← BC76 JSR
INY ; Copy one byte (source -> destination)
BC82 BNE copy_byte_read ; no page wrap
BC84 INC zp_top_1 ; cross a page
BC86 INC zp_general_1 ; source page too
BC88 .copy_byte_read←1← BC82 BNE
LDA (zp_general),y ; read the byte,
BC8A STA (zp_top),y ; write it down
BC8C RTS ; Return

Insert a tokenised program line

Insert the numbered, tokenised line held in the &0700 input buffer into the program. First deletes any existing line with the same number (delete_program_line); if the buffer holds only a CR the edit was a pure deletion and it returns. Otherwise it measures the line, checks the new TOP still fits below HIMEM (raising 'LINE space' after tidying if not), shifts the program up to open a gap, writes the 4-byte line header (line number + length), copies the line body in, and sets the new TOP.

On EntryZP_IWA (&2A)the line number (&2A/&2B)
Yoffset within the &0700 input buffer of the tokenised line text
ZP_TOP (&12)current TOP, the end of the program (&12/&13)
On ExitZP_TOP (&12)updated past the new line (&12/&13)
CONTROLraises 'LINE space' (BRK) if the line will not fit below HIMEM
BC8D .insert_line←2← 8B32 JSR← 90C6 JSR
STY zp_fwb_sign ; Save the line-buffer pointer
BC8F JSR delete_program_line ; Delete any existing line with this number
BC92 LDY #7 ; Point past the line header
BC94 STY zp_fwb_ovf ; save offset 7 in &3C
BC96 LDY #0 ; Measure the new line: from offset 0
BC98 LDA #&0d ; CR
BC9A CMP (zp_fwb_sign),y ; empty line (deletion only)?
BC9C BEQ return_38 ; yes: done
BC9E .insline_scan←1← BCA1 BNE
INY ; Scan to the CR
BC9F CMP (zp_fwb_sign),y ; CR yet?
BCA1 BNE insline_scan ; keep scanning
BCA3 INY ; Add the 4-byte header
BCA4 INY ; (continued)
BCA5 INY ; (continued)
BCA6 STY zp_fwb_m2 ; Line length
BCA8 INC zp_fwb_m2 ; include the CR
BCAA LDA zp_top ; Old top of program
BCAC STA zp_fileblk ; low to &39,
BCAE LDA zp_top_1 ; high byte,
BCB0 STA zp_fileblk_1 ; to &3A
BCB2 JSR add_y_to_top ; New top = old top + line length
BCB5 STA zp_general ; new top low (&37),
BCB7 LDA zp_top_1 ; high byte,
BCB9 STA zp_general_1 ; new top high (&38),
BCBB DEY ; index the last byte (length-1)
BCBC LDA zp_himem ; New top vs HIMEM
BCBE CMP zp_top ; low byte vs TOP,
BCC0 LDA zp_himem_1 ; high byte,
BCC2 SBC zp_top_1 ; HIMEM >= new top?
BCC4 BCS insline_shift ; fits: shift the program up
BCC6 JSR check_program ; no room: tidy up
BCC9 JSR clear_vars_heap_stack ; clear variables/heap/stack
BCCC BRK ; LINE space error
BCCD EQUB &00, &86
BCCF EQUS " space"
BCD5 EQUB &00
BCD6 .insline_shift←2← BCC4 BCS← BCEF BCS
LDA (zp_fileblk),y ; Shift a byte up
BCD8 STA (zp_general),y ; to the higher address,
BCDA TYA ; at a page boundary?,
BCDB BNE insline_shift_next ; no
BCDD DEC zp_fileblk_1 ; cross a page
BCDF DEC zp_general_1 ; destination page too
BCE1 .insline_shift_next←1← BCDB BNE
DEY ; Next byte down
BCE2 TYA ; offset to A,
BCE3 ADC zp_fileblk ; + source low,
BCE5 LDX zp_fileblk_1 ; source high in X,
BCE7 BCC insline_check ; no carry,
BCE9 INX ; carry into the high byte
BCEA .insline_check←1← BCE7 BCC
CMP zp_fwb_exp ; reached the insertion point?
BCEC TXA ; high byte,
BCED SBC zp_fwb_m1 ; source vs the insertion point
BCEF BCS insline_shift ; no: keep shifting
BCF1 SEC ; Write the new line header
BCF2 LDY #1 ; from offset 1
BCF4 LDA zp_iwa_1 ; line number high
BCF6 STA (zp_fwb_exp),y ; store it,
BCF8 INY ; next
BCF9 LDA zp_iwa ; line number low
BCFB STA (zp_fwb_exp),y ; store it,
BCFD INY ; next
BCFE LDA zp_fwb_m2 ; line length
BD00 STA (zp_fwb_exp),y ; store it
BD02 JSR fwb_scale ; Set the new top of program
BD05 LDY #&ff ; Copy the line body in: offset 0
BD07 .insline_advance←1← BD0E BNE
INY ; advance the index
BD08 LDA (zp_fwb_sign),y ; buffer byte
BD0A STA (zp_fwb_exp),y ; store it
BD0C CMP #&0d ; until the CR
BD0E BNE insline_advance ; loop
BD10 .return_38←1← BC9C BEQ
RTS ; Return

RUN

Run the current program from the start. RUN.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BD11 .stmt_run
JSR check_end_of_statement ; Check the statement ends
BD14 .run_clear←1← BF2D JMP
JSR clear_vars_heap_stack ; Clear variables, heap and stack
BD17 LDA zp_page ; Point PtrA at PAGE
BD19 STA zp_text_ptr_1 ; page to the high byte,
BD1B STX zp_text_ptr ; low byte = 0 (X)
BD1D JMP execute_line ; execute from the start

Clear all variables, the heap and the stack

Reset LOMEM and VARTOP to TOP, empty the per-letter variable table, and clear the DATA pointer and the BASIC stacks (NEW/CLEAR/RUN).

On EntryZP_TOP (&12/&13)TOP, the end of the program
On ExitZP_LOMEM (&00/&01)= TOP
ZP_VARTOP (&02/&03)= TOP
THE VARIABLE TABLE AND STACKScleared
BD20 .clear_vars_heap_stack←5← 8AF3 JSR← 90C9 JSR← 9290 JSR← BCC9 JSR← BD14 JSR
LDA zp_top ; LOMEM and VARTOP = TOP: low
BD22 STA zp_lomem ; LOMEM low
BD24 STA zp_vartop ; VARTOP low
BD26 LDA zp_top_1 ; high
BD28 STA zp_lomem_1 ; LOMEM high
BD2A STA zp_vartop_1 ; VARTOP high
BD2C JSR reset_data_and_stacks ; Clear the DATA pointer and the stacks
BD2F .clear_var_table←1← 927E JSR
LDX #&80 ; Clear the variable table (&0480-&04FF):
BD31 LDA #0 ; zero byte
BD33 .clear_var_table_loop←1← BD37 BNE
STA var_table_base,x ; clear a byte
BD36 DEX ; count down
BD37 BNE clear_var_table_loop ; loop
BD39 RTS ; Return

Reset the DATA pointer and the BASIC stacks

Reset the READ/DATA pointer to PAGE and empty the FOR/REPEAT/GOSUB and value stacks: point the stack pointer back at HIMEM and zero the REPEAT, FOR and GOSUB nesting levels. Variables and the heap are left intact. Called on NEW/CLEAR/RUN as part of clear_vars_heap_stack, and on the error path.

On EntryZP_PAGE (&18)PAGE, the start of the program
ZP_HIMEM (&06)HIMEM, the top of memory (&06/&07)
On ExitZP_DATA_PTR (&1C)reset to PAGE (&1C/&1D)
ZP_STACK_PTR (&04)reset to HIMEM (stacks emptied) (&04/&05)
ZP_REPEAT_LEVEL (&24)0
ZP_FOR_LEVEL (&26)0
ZP_GOSUB_LEVEL (&25)0
BD3A .reset_data_and_stacks←3← 8B1A JSR← B41B JSR← BD2C JSR
LDA zp_page ; DATA pointer = PAGE: high
BD3C STA zp_data_ptr_1 ; (data pointer high)
BD3E LDA zp_himem ; STACK = HIMEM: low
BD40 STA zp_stack_ptr ; (store)
BD42 LDA zp_himem_1 ; HIMEM high
BD44 STA zp_stack_ptr_1 ; (store)
BD46 LDA #0 ; Clear the loop/subroutine levels:
BD48 STA zp_repeat_level ; REPEAT
BD4A STA zp_for_level ; FOR
BD4C STA zp_gosub_level ; GOSUB
BD4E STA zp_data_ptr ; DATA pointer low = 0 (= PAGE)
BD50 RTS ; Return

Push the floating-point accumulator onto the BASIC stack

Reserve five bytes on the BASIC stack and copy the packed floating-point accumulator onto it. Errors if the stack meets the heap.

On EntryZP_FWA (&2E-&35)the real to push
On ExitZP_STACK_PTR (&04/&05)lowered by 5 (real pushed)
BD51 .stack_real←12← 9A3E JSR← 9A50 JSR← 9C8B JSR← 9CAC JSR← 9CE1 JSR← 9CFF JSR← 9D14 JSR← 9D20 JSR← 9DE9 JSR← 9E39 JSR← AF27 JSR← BD92 BMI
LDA zp_stack_ptr ; From the stack top...
BD53 SEC ; prepare subtraction
BD54 SBC #5 ; Lower the stack by 5 bytes (a packed real)
BD56 JSR reserve_stack ; reserve the space
BD59 LDY #0 ; Packed byte 0 = exponent
BD5B LDA zp_fwa_exp ; read it
BD5D STA (zp_stack_ptr),y ; (store)
BD5F INY ; advance
BD60 LDA zp_fwa_sign ; Take the sign...
BD62 AND #&80 ; Pack: fold the sign into the mantissa MSB
BD64 STA zp_fwa_sign ; keep just the sign bit
BD66 LDA zp_fwa_m1 ; mantissa MSB
BD68 AND #&7f ; drop the implied 1
BD6A ORA zp_fwa_sign ; fold in the sign
BD6C STA (zp_stack_ptr),y ; byte 1
BD6E INY ; advance
BD6F LDA zp_fwa_m2 ; byte 2
BD71 STA (zp_stack_ptr),y ; (store)
BD73 INY ; advance
BD74 LDA zp_fwa_m3 ; byte 3
BD76 STA (zp_stack_ptr),y ; (store)
BD78 INY ; advance
BD79 LDA zp_fwa_m4 ; byte 4
BD7B STA (zp_stack_ptr),y ; (store)
BD7D RTS ; Real pushed

Pop a real off the BASIC stack

Point zp_fp_ptr at the 5-byte real on top of the BASIC stack and drop it (advance the stack pointer by 5), so an FP routine can use the popped value as its (zp_fp_ptr) operand.

On Entry(ZP_STACK_PTR) (&04/&05)a packed 5-byte real on top of stack
On ExitZP_FP_PTR (&4B/&4C)addresses the popped real
ZP_STACK_PTRadvanced by 5 (real dropped)
Xpreserved
Ypreserved
BD7E .unstack_real←11← 9A47 JSR← 9A5C JSR← 9C9B JSR← 9CF1 JSR← 9D05 JSR← 9D2C JSR← 9DF5 JSR← 9E4A JSR← 9E6F JSR← AF2D JSR← B2E7 JSR
LDA zp_stack_ptr ; Stack top is where the real sits...
BD80 CLC ; clear carry for the add
BD81 STA zp_fp_ptr ; ...point the fp operand there
BD83 ADC #5 ; Drop 5 bytes (a packed real)
BD85 STA zp_stack_ptr ; (store the new stack low byte)
BD87 LDA zp_stack_ptr_1 ; Stack-pointer high byte
BD89 STA zp_fp_ptr_1 ; into the fp-operand pointer too
BD8B ADC #0 ; Carry into the high byte
BD8D STA zp_stack_ptr_1 ; (store it)
BD8F RTS ; zp_fp_ptr now addresses the popped real

Push the current value by type

Push the current value onto the BASIC stack in the form matching its type: a string (stack_string), a real (stack_real) or, falling through, an integer (stack_integer).

On EntryAvalue type: 0 string, negative real, else integer
ZP_IWA / ZP_FWA / STRING_WORK (&0600)the value, selected by the type in A
On ExitZP_STACK_PTR (&04/&05)lowered past the pushed value
BD90 .stack_value←2← B291 JSR← B31C JSR
BEQ stack_string ; Type 0 (string): stack as a string
BD92 BMI stack_real ; Negative (real): stack 5 bytes; else integer below
fall through ↓

Push the integer accumulator onto the BASIC stack

Reserve four bytes on the BASIC stack (zp_stack_ptr, &04) and copy the integer accumulator (zp_iwa) onto it. Errors if the stack would collide with the heap.

On EntryZP_IWA (&2A-&2D)the integer to push
On ExitZP_STACK_PTR (&04/&05)lowered by 4 (integer pushed)
BD94 .stack_integer←29← 85AC JSR← 8BEB JSR← 8BFB JSR← 8ED8 JSR← 8F36 JSR← 8F71 JSR← 90B5 JSR← 90E8 JSR← 9185 JSR← 9334 JSR← 9400 JSR← 96FF JSR← 9744 JSR← 9AA2 JSR← 9B6F JSR← 9B7E JSR← 9DCE JSR← 9E1D JSR← AB44 JSR← B0C5 JSR← B298 JSR← B329 JMP← B5B0 JSR← B5C8 JSR← B7CB JSR← B9F1 JSR← BAD0 JSR← BB29 JSR← BB35 JSR
LDA zp_stack_ptr ; From the stack top...
BD96 SEC ; prepare subtraction
BD97 SBC #4 ; Lower the stack by 4 bytes (an integer)
BD99 JSR reserve_stack ; reserve the space (check for room)
BD9C LDY #3 ; Copy IWA, MSB first: byte 3
BD9E LDA zp_iwa_3 ; read it
BDA0 STA (zp_stack_ptr),y ; (store)
BDA2 DEY ; next
BDA3 LDA zp_iwa_2 ; byte 2
BDA5 STA (zp_stack_ptr),y ; (store)
BDA7 DEY ; next
BDA8 LDA zp_iwa_1 ; byte 1
BDAA STA (zp_stack_ptr),y ; (store)
BDAC DEY ; next
BDAD LDA zp_iwa ; byte 0 (LSB)
BDAF STA (zp_stack_ptr),y ; (store)
BDB1 RTS ; Integer pushed

Push the current string onto the BASIC stack

Copy the string from the string buffer (length zp_strbuf_len, &36; text at &0600) onto the BASIC stack, length last. Errors if the stack meets the heap.

On EntrySTRING_WORK (&0600)the string characters
ZP_STRBUF_LEN (&36)the string length
On ExitZP_STACK_PTR (&04/&05)lowered by length+1 (string pushed)
BDB2 .stack_string←9← 9AE7 JSR← 9C15 JSR← ABF7 JSR← ACED JSR← AD06 JSR← AFD7 JSR← AFF9 JSR← B042 JSR← BD90 BEQ
CLC ; From the stack top...
BDB3 LDA zp_stack_ptr ; low...
BDB5 SBC zp_strbuf_len ; Lower the stack by length+1 bytes (carry clear)
BDB7 JSR reserve_stack ; reserve the space
BDBA LDY zp_strbuf_len ; string length
BDBC BEQ ss_push_length ; zero length: just push the length
BDBE .ss_copy_loop←1← BDC4 BNE
LDA strbuf_base,y ; Copy the string from the buffer (&0600): char Y
BDC1 STA (zp_stack_ptr),y ; (onto the stack)
BDC3 DEY ; next
BDC4 BNE ss_copy_loop ; loop
BDC6 .ss_push_length←1← BDBC BEQ
LDA zp_strbuf_len ; Push the length last
BDC8 STA (zp_stack_ptr),y ; (store)
BDCA RTS ; String pushed

Pop a string off the BASIC stack

Copy the length-prefixed string on top of the stack into the string buffer and drop it (advance the stack pointer past the length byte and the characters).

On Entry(ZP_STACK_PTR) (&04/&05)a length-prefixed string on top of stack
On ExitSTRING_WORK (&0600)the popped string characters
ZP_STRBUF_LEN (&36)the string length
ZP_STACK_PTRadvanced past the string
Xpreserved
BDCB .unstack_string←6← 9C37 JSR← AD0F JSR← AFE0 JSR← B002 JSR← B061 JSR← B2FD JSR
LDY #0 ; Pop a string: read its length
BDCD LDA (zp_stack_ptr),y ; read it
BDCF STA zp_strbuf_len ; (store)
BDD1 BEQ drop_stacked_string ; zero length: just drop the length
BDD3 TAY ; Y = length
BDD4 .us_copy_loop←1← BDDA BNE
LDA (zp_stack_ptr),y ; Copy the string to the buffer: char Y
BDD6 STA strbuf_base,y ; (into the buffer)
BDD9 DEY ; next
BDDA BNE us_copy_loop ; loop
fall through ↓

Drop a string off the BASIC stack

Read the length byte of the length-prefixed string on top of the BASIC stack and advance the stack pointer past both the length byte and the string characters, discarding the string without copying it.

On EntryZP_STACK_PTR (&04)a length-prefixed string on top of the BASIC stack (&04/&05)
On ExitZP_STACK_PTR (&04)advanced past the length byte and characters (string dropped)
Xpreserved
BDDC .drop_stacked_string←6← 8CEB JMP← 9B16 JSR← AC20 JSR← AD39 JSR← AD52 JSR← BDD1 BEQ
LDY #0 ; Drop the string: get its length
BDDE LDA (zp_stack_ptr),y ; read it
BDE0 SEC ; so the +1 covers the length byte
BDE1 .drop_string_adv←1← 8D28 JMP
ADC zp_stack_ptr ; advance the stack pointer past it: low
BDE3 STA zp_stack_ptr ; (store)
BDE5 BCC return_39 ; done
BDE7 INC zp_stack_ptr_1 ; carry into high
BDE9 RTS ; Return

Pop an integer from the BASIC stack

Copy the four-byte integer on top of the BASIC stack into the integer accumulator (zp_iwa), then fall into drop_stack_integer to advance the stack pointer past it.

On Entry(ZP_STACK_PTR) (&04/&05)a 4-byte integer (MSB first) on top of stack
On ExitZP_IWA (&2A-&2D)the popped integer
ZP_STACK_PTRadvanced by 4 (integer dropped)
Xpreserved
BDEA .unstack_integer←16← 8C1E JSR← 8F11 JSR← 8F50 JSR← 90B2 JSR← 90C0 JSR← 9A3B JSR← 9CA9 JSR← 9CFC JSR← 9D11 JSR← 9D78 JSR← AB56 JSR← B0D0 JSR← B2CA JSR← B2F0 JSR← B5C5 JSR← B5E9 JSR
LDY #3 ; Index the stacked integer (4 bytes, MSB first)
BDEC LDA (zp_stack_ptr),y ; Top byte (MSB)...
BDEE STA zp_iwa_3 ; ...into IWA byte 3
BDF0 DEY ; next byte
BDF1 LDA (zp_stack_ptr),y ; byte 2...
BDF3 STA zp_iwa_2 ; ...into IWA
BDF5 DEY ; next byte
BDF6 LDA (zp_stack_ptr),y ; byte 1...
BDF8 STA zp_iwa_1 ; ...into IWA
BDFA DEY ; last byte
BDFB LDA (zp_stack_ptr),y ; byte 0 (LSB)...
BDFD STA zp_iwa ; ...into IWA, then fall into drop_stack_integer
fall through ↓

Drop an integer off the stack

Advance the BASIC stack pointer by four bytes.

On EntryZP_STACK_PTR (&04/&05)the BASIC stack pointer
On ExitZP_STACK_PTRadvanced by 4
Xpreserved
Ypreserved
BDFF .drop_stack_integer←2← 9B4E JSR← 9B95 JSR
CLC ; Prepare to add
BE00 LDA zp_stack_ptr ; Drop four bytes: stack pointer...
BE02 ADC #4 ; ...+ 4
BE04 STA zp_stack_ptr ; (store)
BE06 BCC return_39 ; No carry: done
BE08 INC zp_stack_ptr_1 ; Carry into the high byte
BE0A .return_39←3← BDE5 BCC← BE06 BCC← BE29 BCC
RTS ; Return (shared)

Pop a stacked integer into the general work area

Set the destination to zp_general (&37) and fall into unstack_int_to_zp, which copies the 4-byte integer on top of the BASIC stack to (&37..&3A) and drops it from the stack.

On EntryZP_STACK_PTR (&04)a 4-byte integer (MSB first) on top of the BASIC stack (&04/&05)
On ExitZP_GENERAL (&37)the popped integer (&37-&3A)
ZP_STACK_PTR (&04)advanced by 4 (integer dropped)
Xpreserved (= &37)
BE0B .unstack_int_to_general←3← 9412 JSR← B21C JSR← B4B4 JSR
LDX #&37 ; Default destination: zp_general (&37)
fall through ↓

Pop a stacked integer into a zero-page variable

Copy the four-byte integer on top of the BASIC stack into the zero-page bytes at (X .. X+3), then drop it from the stack. The &BE0B entry uses X = &37 (the general work area).

On EntryXthe destination zero-page offset (from &00)
(ZP_STACK_PTR) (&04/&05)a 4-byte integer on top of stack
On Exit(&00+X .. +3)the popped integer
ZP_STACK_PTRadvanced by 4 (integer dropped)
Xpreserved
BE0D .unstack_int_to_zp←5← 8FA8 JSR← 9233 JSR← 970D JSR← 9755 JSR← 99DD JSR
LDY #3 ; Copy the stacked integer to (X): top byte
BE0F LDA (zp_stack_ptr),y ; (read)
BE11 STA zp_vartop_1,x ; ...byte 3
BE13 DEY ; next
BE14 LDA (zp_stack_ptr),y ; (read)
BE16 STA zp_vartop,x ; byte 2
BE18 DEY ; next
BE19 LDA (zp_stack_ptr),y ; (read)
BE1B STA zp_lomem_1,x ; byte 1
BE1D DEY ; next
BE1E LDA (zp_stack_ptr),y ; (read)
BE20 STA zp_lomem,x ; byte 0
BE22 CLC ; Now drop the integer from the stack
BE23 LDA zp_stack_ptr ; stack pointer...
BE25 ADC #4 ; ...+ 4
BE27 STA zp_stack_ptr ; (store)
BE29 BCC return_39 ; No carry: done
BE2B INC zp_stack_ptr_1 ; Carry into the high byte
BE2D RTS ; Return

Set the stack pointer and check for room

Set the BASIC value-stack pointer to a new top (low byte in A, borrow already taken into the high byte) and check it has not run down into the top of variable storage (zp_vartop). Raises "No room" on collision; otherwise returns with the stack lowered.

On EntryAproposed new stack-pointer low byte
CARRYclear if the subtraction borrowed
BE2E .reserve_stack←4← B19E JSR← BD56 JSR← BD99 JSR← BDB7 JSR
STA zp_stack_ptr ; Set the new stack-pointer low byte
BE30 BCS rs_set_high ; No borrow: high byte unchanged
BE32 DEC zp_stack_ptr_1 ; Borrow: decrement the high byte
BE34 .rs_set_high←1← BE30 BCS
LDY zp_stack_ptr_1 ; New stack-pointer high byte
BE36 CPY zp_vartop_1 ; Compare the new top against the heap top
BE38 BCC stack_no_room ; New top below the heap page: No room
BE3A BNE return_40 ; Clearly above the heap: room available
BE3C CMP zp_vartop ; Same page: compare the low bytes
BE3E BCC stack_no_room ; Below the heap top: No room
BE40 .return_40←1← BE3A BNE
RTS ; Room available: return
BE41 .stack_no_room←2← BE38 BCC← BE3E BCC
JMP err_no_room ; Stack meets heap: No room

Store the accumulator into a zero-page integer variable

Copy IWA into the 4-byte resident integer variable at &00+X (the inverse of iwa_load_zp).

On EntryZP_IWA (&2A-&2D)the integer to store
Xzero-page offset of the destination (from &00)
On Exit(&00+X .. +3)a copy of IWA
Xpreserved
Ypreserved
BE44 .iwa_store_zp←5← 885F JSR← 9D75 JSR← AF41 JSR← B2E0 JSR← B315 JSR
LDA zp_iwa ; Copy IWA to the zp variable at X: byte 0
BE46 STA zp_lomem,x ; (&00+X)
BE48 LDA zp_iwa_1 ; byte 1
BE4A STA zp_lomem_1,x ; (&01+X)
BE4C LDA zp_iwa_2 ; byte 2
BE4E STA zp_vartop,x ; (&02+X)
BE50 LDA zp_iwa_3 ; byte 3
BE52 STA zp_vartop_1,x ; (&03+X)
BE54 RTS ; Done
BE55 .fwb_scale_clc←1← BB04 JSR
CLC ; Add without carry
BE56 .fwb_scale←1← BD02 JSR
TYA ; A = the amount (Y)
BE57 ADC zp_fwb_exp ; add it to the FWB exponent
BE59 STA zp_fwb_exp ; (store)
BE5B BCC fwb_scale_done ; no carry: done
BE5D INC zp_fwb_m1 ; carry into the mantissa MSB
BE5F .fwb_scale_done←1← BE5B BCC
LDY #1 ; Y = 1
BE61 RTS ; Return

Load a program from the filing system

Set the OSFILE load address to PAGE and call OSFILE &FF to read the named BASIC program into memory. Used by LOAD and CHAIN.

LOAD/SAVE drive OSFILE through an 18-byte control block in zero page, pointed at by XY = &0037. Each address field is 4 bytes (the BBC's 32-bit address space; the high words come from OSBYTE &82):

Addr Field
&37-&38 filename pointer
&39-&3C load address
&3D-&40 exec address
&41-&44 start address
&45-&48 end address

This routine fills only the filename pointer, the load address (PAGE), and exec = 0 (so OSFILE &FF loads to the block's load address rather than the file's own). stmt_save fills all four address fields and calls OSFILE &00.

On EntryTHE FILENAME BLOCKset up for OSFILE (control block at &37)
ZP_PAGE (&18)PAGE, where the program loads
On ExitMEMORY FROM PAGEthe loaded program
BE62 .load_program←2← BF24 JSR← BF2A JSR
JSR eval_filename ; Set the OSFILE load address to PAGE
BE65 TAY ; Y = 0 (for the exec address)
BE66 LDA #osfile_load ; OSFILE &FF: load the named file
BE68 STY zp_fwb_exp ; exec address = 0 (load to the given address)
BE6A LDX #&37 ; control block at &37
BE6C JSR osfile ; osfile: load file
fall through ↓

Validate the program and set TOP

Walk the lines from PAGE checking each is well-formed (CR-terminated, non-zero length), and set TOP past the end. Raises "Bad program" if a line is malformed.

On EntryZP_PAGE (&18)PAGE, the start of the program
On ExitZP_TOP (&12/&13)TOP, just past the last line
BRKBad program if a line is malformed
BE6F .check_program←6← 8AC3 JSR← 8ACB JSR← 8FAB JSR← B5E6 JSR← BCC6 JSR← BEF3 JSR
LDA zp_page ; Set TOP = PAGE: high
BE71 STA zp_top_1 ; (TOP high)
BE73 LDY #0 ; low 0
BE75 STY zp_top ; (store)
BE77 INY ; Y = 1
BE78 .chkprog_loop←1← BE8E BNE
DEY ; step back to the byte before the line
BE79 LDA (zp_top),y ; get it
BE7B CMP #&0d ; each line must be CR-terminated
BE7D BNE bad_program ; not a CR: Bad program
BE7F INY ; step to the line number / end marker
BE80 LDA (zp_top),y ; get it
BE82 BMI chkprog_end ; high bit set: end of program
BE84 LDY #3 ; line length is at offset 3
BE86 LDA (zp_top),y ; get it
BE88 BEQ bad_program ; zero length: Bad program
BE8A CLC ; advance to the next line
BE8B JSR add_y_to_top_lo ; TOP += line length
BE8E BNE chkprog_loop ; loop over all lines
BE90 .chkprog_end←1← BE82 BMI
INY ; End of program: skip the &FF marker
BE91 CLC ; clear carry for TOP += Y
BE92 .add_y_to_top←2← BC7C JSR← BCB2 JSR
TYA ; TOP += Y
BE93 .add_y_to_top_lo←1← BE8B JSR
ADC zp_top ; add: low
BE95 STA zp_top ; (store)
BE97 BCC chkprog_ok ; no carry
BE99 INC zp_top_1 ; carry into high
BE9B .chkprog_ok←1← BE97 BCC
LDY #1 ; return Y=1 (NE)
BE9D RTS ; Return
BE9E .bad_program←2← BE7D BNE← BE88 BEQ
JSR print_inline_string ; Bad program: print the message
BEA1 EQUS &0D, "Bad program", &0D
BEAE NOP ; (string terminator)
BEAF JMP immediate_loop ; back to the immediate loop
BEB2 .point_strbuf_lo←1← BED7 JSR
LDA #0 ; Point the general pointer at the string buffer: low
BEB4 STA zp_general ; (store)
BEB6 LDA #6 ; high (&06)
BEB8 STA zp_general_1 ; (store)
fall through ↓

Terminate the string buffer with a CR

Write a carriage return (&0D) into string_work (&0600) at the offset given by zp_strbuf_len, marking the end of the string buffer. Reached both by fall-through from point_strbuf_lo and by jsr from assign_str_addr and &BF88.

On EntryZP_STRBUF_LEN (&36)the current string length (offset of the terminator)
On ExitSTRING_WORK (&0600)the buffer, terminated with a CR at offset zp_strbuf_len
Ythe string length
A&0D
BEBA .terminate_strbuf←2← 8CA2 JSR← BF88 JSR
LDY zp_strbuf_len ; Terminate the string buffer with a CR:
BEBC LDA #&0d ; CR
BEBE STA string_work,y ; at the end of the buffer
BEC1 RTS ; Return

OSCLI

Pass a string to the OS command-line interpreter. OSCLI string.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BEC2 .stmt_oscli
JSR eval_string_arg ; Evaluate the command string, CR-terminate it
BEC5 LDX #0 ; XY -> the string (&0600): low
BEC7 LDY #6 ; high byte &06
BEC9 JSR oscli ; Pass it to OSCLI
BECC JMP statement_loop ; Back to execution
BECF .oscli_type_error←1← BED5 BNE
JMP err_type_mismatch ; not a string: Type mismatch

Evaluate a string argument and CR-terminate it

Evaluate an expression via eval_expr, require it to be a string (Type mismatch otherwise), CR-terminate it in the string buffer, and check that the statement ends. Used by OSCLI-style commands and eval_filename to fetch a single string argument.

On EntryZP_TEXT_PTR (&0B/&0C)PtrA at the string expression
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword
On ExitSTRING_WORK (&0600)the string, terminated with a carriage return
ZP_STRBUF_LEN (&36)the string length
BED2 .eval_string_arg←2← BEC2 JSR← BEDD JSR
JSR eval_expr ; Evaluate the expression
BED5 BNE oscli_type_error ; not a string: error
BED7 JSR point_strbuf_lo ; CR-terminate the string buffer
BEDA JMP assign_check_end ; Check for end of statement

Evaluate a filename and build the OSFILE load address

Evaluate a CR-terminated filename string via eval_string_arg, then set the load address to PAGE (low byte 0, high byte PAGE) and read the machine's high-order address word via OSBYTE &82. Shared setup for SAVE/LOAD/CHAIN.

On EntryZP_TEXT_PTR (&0B/&0C)PtrA at the filename expression
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword
On ExitSTRING_WORK (&0600)the CR-terminated filename
ZP_FILEBLK (&39)load address low = 0
ZP_FILEBLK_1 (&3A)load address high = PAGE
ZP_FWB_SIGN (&3B)high-order address low (X from OSBYTE &82)
ZP_FWB_OVF (&3C)high-order address high (Y from OSBYTE &82)
Azero
BEDD .eval_filename←2← BE62 JSR← BF0A JSR
JSR eval_string_arg ; Evaluate the filename, CR-terminate
BEE0 DEY ; Y = 0 for the low byte
BEE1 STY zp_fileblk ; LOAD address low = 0
BEE3 LDA zp_page ; LOAD address high = PAGE
BEE5 STA zp_fileblk_1 ; (store)
BEE7 .read_himem_byte←1← 93A3 JSR
LDA #osbyte_read_high_order_address ; OSBYTE &82: read the high-order address
BEE9 JSR osbyte ; Read high-order address (machine high word)
BEEC STX zp_fwb_sign ; set the LOAD high word
BEEE STY zp_fwb_ovf ; high word: X and Y
BEF0 LDA #0 ; A = 0
BEF2 RTS ; Return

SAVE

Save the current program to the filing system. SAVE string.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BEF3 .stmt_save
JSR check_program ; Check the program, set TOP
BEF6 LDA zp_top ; End address = TOP
BEF8 STA zp_fp_temp_2 ; low byte to &45,
BEFA LDA zp_top_1 ; high byte,
BEFC STA zp_fp_temp_3 ; to &46
BEFE LDA #&23 ; Exec address = language startup
BF00 STA zp_fwb_exp ; low byte &23 to &3D,
BF02 LDA #&80 ; high byte &80,
BF04 STA zp_fwb_m1 ; to &3E (exec &8023)
BF06 LDA zp_page ; Start address = PAGE
BF08 STA zp_fwb_rnd ; PAGE to &42
BF0A JSR eval_filename ; Read the machine high address words
BF0D STX zp_fwb_m2 ; Fill the address high words
BF0F STY zp_fwb_m3 ; &40,
BF11 STX zp_fp_temp ; &43,
BF13 STY zp_fp_temp_1 ; &44,
BF15 STX zp_fp_temp_4 ; &47,
BF17 STY zp_dp_flag ; &48
BF19 STA zp_fwb_m4 ; Load address low = PAGE
BF1B TAY ; into Y too
BF1C LDX #&37 ; Point at the OSFILE block (&37)
BF1E JSR osfile ; Save the file
BF21 JMP statement_loop ; next statement

LOAD

Load a BASIC program without running it. LOAD string.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BF24 .stmt_load
JSR load_program ; Load the named program
BF27 JMP clear_then_immediate ; Back to the immediate loop

CHAIN

Load a BASIC program and run it. CHAIN string.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BF2A .stmt_chain
JSR load_program ; Load the named program
BF2D JMP run_clear ; then RUN it

PTR#=

Set the sequential pointer of an open file. PTR#channel = position.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BF30 .stmt_ptr
JSR sync_ptrb_from_ptra ; Evaluate the #handle
BF33 PHA ; save it
BF34 JSR eval_after_eq ; Expect "=" and evaluate
BF37 JSR coerce_var_to_integer ; coerce to integer
BF3A PLA ; Recover the handle
BF3B TAY ; Y = the handle
BF3C LDX #&2a ; Point at the value
BF3E LDA #1 ; Write the file pointer
BF40 JSR osargs ; Write sequential file pointer from zero page address X (A=1)
BF43 JMP statement_loop ; next statement

EXT

Length (extent) of an open file. EXT#channel.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
BF46 .fn_ext
SEC ; Carry set selects EXT (otherwise PTR)
fall through ↓

=PTR

Read the sequential pointer of an open file. PTR#channel.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
BF47 .fn_ptr
LDA #0 ; Build the OSARGS sub-function from the carry...
BF49 ROL ; ...0 = PTR, non-zero = EXT
BF4A ROL ; (BBC: A becomes 0 or 2)
BF4B PHA ; Save the function code
BF4C JSR eval_channel ; Evaluate the #handle, point at IWA
BF4F LDX #&2a ; X -> IWA for the result
BF51 PLA ; Restore the function code
BF52 JSR osargs ; OSARGS: read PTR or EXT into IWA
BF55 LDA #&40 ; Integer result
BF57 RTS ; Return PTR/EXT

BPUT

Write a byte to an open file. BPUT#channel, value.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BF58 .stmt_bput
JSR sync_ptrb_from_ptra ; Evaluate the #handle
BF5B PHA ; save it
BF5C JSR skip_spaces_expect_comma ; require a comma
BF5F JSR eval_rhs ; evaluate the value
BF62 JSR coerce_var_to_integer ; coerce to an integer
BF65 PLA ; recover the handle
BF66 TAY ; Y = handle
BF67 LDA zp_iwa ; A = the byte to write
BF69 JSR osbput ; OSBPUT: write the byte
BF6C JMP statement_loop ; Back to execution

BGET

Read a byte from an open file. BGET#channel.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
BF6F .fn_bget
JSR eval_channel ; Evaluate the #handle
BF72 JSR osbget ; OSBGET: read a byte from the channel
BF75 JMP int_result_a ; Return the byte as an integer

OPENIN

Open a file for input, returning its channel (0 if not found). OPENIN string.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
BF78 .fn_openin
LDA #&40 ; OSFIND &40: open an existing file for input
BF7A BNE openup_action ; do the open
fall through ↓

OPENOUT

Create a file for output, returning its channel. OPENOUT string.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
BF7C .fn_openout
LDA #&80 ; OSFIND &80: create a file for output
BF7E BNE openup_action ; do the open
fall through ↓

OPENUP

Open a file for update (read and write), returning its channel. OPENUP string.

On EntryAthe function keyword token (&8E-&C5)
ZP_TEXT_PTR2 (&19/&1A)the expression text pointer (PtrB)
ZP_TEXT_PTR2_OFF (&1B)offset just past the function token
On ExitAresult type: <0 = float in fwa, >0 = integer in iwa, 0 = string
ZP_IWA (&2A-&2D) / ZP_FWA (&2E-&35) / STRING_WORK (&0600)the result, selected by the type in A
ZP_TEXT_PTR2_OFF (&1B)advanced past the consumed argument(s)
BF80 .fn_openup
LDA #&c0 ; OPENUP action &C0
BF82 .openup_action←2← BF7A BNE← BF7E BNE
PHA ; Save the action
BF83 JSR eval_factor ; Evaluate the filename
BF86 BNE openup_type_error ; not a string: error
BF88 JSR terminate_strbuf ; CR-terminate it
BF8B LDX #0 ; Point at the string buffer
BF8D LDY #6 ; high byte = &06 (&0600)
BF8F PLA ; recover the action
BF90 JSR osfind ; Open the file
BF93 JMP int_result_a ; return the handle
BF96 .openup_type_error←1← BF86 BNE
JMP err_type_mismatch ; Type mismatch error

CLOSE

Close an open file, or all files with #0. CLOSE#channel.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BF99 .stmt_close
JSR sync_ptrb_from_ptra ; Evaluate the #handle
BF9C JSR sync_text_ptr ; Check for end of statement
BF9F LDY zp_iwa ; Y = the handle
BFA1 LDA #osfind_close ; OSFIND &00: close the file
BFA3 JSR osfind ; osfind: close one or all files
BFA6 JMP statement_loop ; Back to execution

Copy PtrA to PtrB, then evaluate a #channel

Copy the three fields of the primary text pointer PtrA (offset &0A, low &0B, high &0C) into the secondary pointer PtrB (offset &1B, low &19, high &1A), then fall straight through into eval_channel. In effect: rewind PtrB to PtrA's position and parse the '#channel' file handle that follows.

On EntryZP_TEXT_PTR_OFF (&0A)PtrA offset
ZP_TEXT_PTR (&0B)PtrA pointer low/high (&0B/&0C)
On ExitZP_TEXT_PTR2 (&19)PtrB set equal to PtrA (offset &1B, low &19, high &1A)
CONTROLfalls through to eval_channel, returning with zp_iwa (&2A) = channel handle, or BRK Missing #
BFA9 .sync_ptrb_from_ptra←5← 8D2D JSR← B9D1 JSR← BF30 JSR← BF58 JSR← BF99 JSR
LDA zp_text_ptr_off ; Set PtrB = PtrA: offset
BFAB STA zp_text_ptr2_off ; (store)
BFAD LDA zp_text_ptr ; low
BFAF STA zp_text_ptr2 ; (store)
BFB1 LDA zp_text_ptr_1 ; high
BFB3 STA zp_text_ptr2_1 ; (store)
fall through ↓

Evaluate a #channel argument

Require "#" at PtrB then evaluate the file handle as an integer, leaving it in IWA. Raises Missing # if the "#" is absent.

The shared entry point for the channel-based file words. BBC BASIC's file vocabulary maps onto five MOS calls; the handle is passed in Y (except OSFILE), and OSARGS reads/writes its 4-byte value through X -> IWA (&2A):

BASIC MOS call A
OPENIN f$ OSFIND &40
OPENOUT f$ OSFIND &80
OPENUP f$ OSFIND &C0
CLOSE #h OSFIND &00
=PTR# h OSARGS &00
PTR# h = OSARGS &01
=EXT# h OSARGS &02
=BGET# h OSBGET -
BPUT# h, b OSBPUT -
LOAD / CHAIN OSFILE &FF
SAVE OSFILE &00

OSFIND returns the new handle in A (0 = open failed); CLOSE #0 closes every open file. OSFILE uses the control block at &37 (see load_program), not a handle.

On EntryZP_TEXT_PTR2 (&19/&1A)PtrB before the "#channel"
On ExitZP_IWA (&2A-&2D)the channel handle
Athe handle low byte
Ythe handle low byte
BRKMissing # if "#" is absent
BFB5 .eval_channel←3← ACB8 JSR← BF4C JSR← BF6F JSR
JSR skip_spaces_ptr2 ; Skip spaces
BFB8 CMP #'#' ; Require "#"
BFBA BNE missing_hash ; missing: error
BFBC JSR eval_factor_integer ; Evaluate the handle as an integer
BFBF LDY zp_iwa ; Y = the handle
BFC1 TYA ; A = the handle too
BFC2 RTS ; Return
BFC3 .missing_hash←1← BFBA BNE
BRK ; BRK error block ("Missing #")
BFC4 EQUS "-Missing #"
BFCE EQUB &00

Print an inline string following the call

Print the string embedded in the code immediately after the JSR to this routine. It pulls the return address into zp_general as a text pointer, then walks forward with general_next_byte printing each byte through osasci, stopping at the first byte with bit 7 set. It resumes execution at that terminating byte via jmp (zp_general) - so the terminator doubles as the opcode of the continuation.

On ExitCONTROLresumes at the string terminator (the byte after the string) as the next instruction; A, Y and zp_general (&37/&38) are corrupted
BFCF .print_inline_string←2← 9080 JSR← BE9E JSR
PLA ; Pull the return address: it points at the string
BFD0 STA zp_general ; (low)
BFD2 PLA ; (pull high)
BFD3 STA zp_general_1 ; (high)
BFD5 LDY #0 ; Start before the first character
BFD7 BEQ print_msg_next ; jump in to fetch it
BFD9 .print_msg_loop←1← BFDF BPL
JSR osasci ; Print the character
BFDC .print_msg_next←1← BFD7 BEQ
JSR general_next_byte ; Advance and fetch the next character
BFDF BPL print_msg_loop ; Loop while bit 7 is clear
BFE1 JMP (zp_general) ; Resume at the terminator (the next instruction)

REPORT

Print the message for the last error. REPORT.

On EntryAthe statement keyword token (>= &C6)
ZP_TEXT_PTR (&0B/&0C)the program text pointer (PtrA)
ZP_TEXT_PTR_OFF (&0A)offset just past the keyword token
On ExitCONTROLrejoins statement_loop; no value, registers not preserved
BFE4 .stmt_report
JSR check_end_of_statement ; Check the statement ends
BFE7 JSR emit_newline ; Newline
BFEA LDY #1 ; From offset 1
BFEC .report_loop←1← BFF4 BNE
LDA (zp_error_ptr),y ; Error message byte
BFEE BEQ report_done ; terminator: done
BFF0 JSR print_token ; print it
BFF3 INY ; next
BFF4 BNE report_loop ; loop
BFF6 .report_done←1← BFEE BEQ
JMP statement_loop ; next statement
BFF9 EQUB &00
BFFA EQUS "Roger"
BFFF EQUB &00