Hexas is a symbolic hex assembler written in BASIC that reads machine code expressed as hexadecimal bytes embedded within REM statements and assembles it to a target address. It supports forward-reference resolution, relative jump range checking, and two-pass label/symbol table management using parallel DIM arrays for names, addresses, and relocation records. A companion relocator module generates position-independent code by patching absolute addresses at load time, making the output relocatable to any memory location. The Basic*Olay loader uses a machine code routine in REM line 1 to install an overlay system, reporting entry points for COPY and OVERLAY functions along with the next available address.
Program Analysis
Program Structure
The listing comprises two distinct programs. The first is Hexas, a symbolic hex assembler; the second is Basic*Olay, a loader/installer for an overlay system. In Hexas, lines 1–12 form a REM expander that grows a REM statement to hold machine code output. Lines 6670–6995 are the scanner/parser that walks through REM-embedded source. Lines 7000–7450 handle token reading and symbol definition. Lines 7460–7690 parse label and equate directives. Lines 7700–7915 perform the second-pass back-patch (relocation resolution). Lines 7970–8205 print the symbol table and completion report. Lines 8995–9090 constitute the relocator data setup routine, reached by continuing after a STOP.
Machine Code in REM Statements
Both programs store executable Z80 machine code in REM line 1. In Hexas, line 1 contains the REM expander routine invoked at line 8 via RAND USR 16514. In Basic*Olay, line 1 holds the overlay manager, invoked at line 45 via RAND USR 16686. The REM bytes are accessed directly by POKEing or PEEKing absolute addresses, a standard technique for embedding machine code without a separate binary file.
REM Expander (Lines 1–12)
Before assembly can begin, Hexas must create a sufficiently large REM statement to hold the assembled output. Lines 4–11 prompt for the required byte count N, patch the REM length field at addresses 16444–16445, call the machine code expander, then fill the new REM body with 22 (the byte for the NEWLINE token) as a placeholder. This is a well-known idiom for allocating workspace inside the BASIC program itself.
Assembler Pass Structure
The assembler is effectively a two-pass design implemented in a single scan with deferred back-patching:
- Pass 1 (scan): Starting at BASIC address
A=16508, the scanner (GOSUB 6930) seeks parenthesised hex blocks. Inside each block, hex digit pairs are assembled immediately into memory at addressZ. Labels (prefixed+) are recorded inN$(NS)/N(NS). References (prefixed$or£) are deferred intoR$(NR)/R(NR). - Pass 2 (back-patch): At line 7700, every entry in the reference table is matched against the name table. Relative references get range-checked (±128 bytes) and POKEd as signed offsets. Absolute references (
$/£) are stored little-endian by the routine at line 7920.
Symbol and Reference Tables
| Array | Dimensions | Purpose |
|---|---|---|
N$(LL,8) | 48 × 8 chars | Symbol names (definitions) |
N(LL) | 48 elements | Symbol addresses |
R$(LL+LL,9) | 96 × 9 chars | Reference names with type prefix |
R(LL+LL) | 96 elements | Reference target addresses |
The first character of each R$ entry encodes the reference type: + for relative, $ or £ for absolute (two-byte little-endian). The name portion starts at character 2, allowing a single string to carry both metadata and the label name.
Key BASIC Idioms
LET I=1,LET O=0,LET T=28,LET TT=256,LET NL=118— constants stored in variables to save repeated literal parsing overhead and improve inner-loop speed.T=28is the BASIC token code for0; hex digits0–9andA–Fare detected by testingX >= TandX <= 43(token 43 =F), exploiting the ZX81 token table layout where digits are consecutive.NL=118is the NEWLINE token (0x76), used to detect end-of-line while scanning REM body bytes.- Hex nibble conversion uses
16*(X-T)+Y-T, a compact formula relying on the same token-offset trick. IF X<0 THEN LET X=X+TTat line 7880 converts a negative signed byte offset to its unsigned 8-bit equivalent for POKEing.
Relocator Setup (Lines 8995–9090)
After the main assembly, the user types CONT to fall through to the relocator setup. This routine iterates the reference table a second time, collecting only absolute ($-prefixed) reference addresses into a data table appended after the assembled code. It then patches three fields inside the relocator stub: the count of relocatable words, the base address of the assembled block, and the code length — all computed from the assembled address ranges. The result is a self-contained relocatable binary with an embedded relocation map.
Basic*Olay Loader
The second program installs the overlay manager stored in its REM line 1. Lines 35–40 write the user-supplied load address E into a two-byte pointer at 16507–16508 (little-endian), then call RAND USR 16686 to execute the installer. Line 70 computes the next available address as E+172 (0xAC bytes), consistent with the 01 AC 00 operand visible in the REM hex dump which loads BC with the copy length.
Notable Techniques
- Source code is embedded directly in BASIC REM lines as parenthesised hex strings — no separate editor or file format is needed.
- The scanner navigates raw BASIC line storage by PEEKing absolute addresses and advancing
Abyte-by-byte, bypassing the BASIC interpreter’s own line-access mechanisms for speed. - Line number recovery for error messages uses
TT*PEEK AA + PEEK (AA+I), decoding the two-byte big-endian line number stored at the start of each BASIC line in memory. - Error bytes are marked in-place with
POKE A, Y+128(setting bit 7), providing a visual inverse-video marker in the REM source for post-mortem debugging. - The
GOTO 7490inside the label-parsing loop at line 7560 uses a bareLET W=M/ loop-back pattern rather than a WHILE construct, a common ZX81 BASIC workaround for the absence of structured loops.
Potential Issues
- Line 7560 reads
GOTO 7490but the loop body starts at 7500; line 7490 only resetsLET W=M, so the first iteration after a non-=character does redundantly re-execute that assignment — functionally harmless but slightly unexpected. - The REM expander fills new bytes with
22(NEWLINE token), but the assembled output subsequently overwrites these; ifNis underestimated the assembler will silently overwrite subsequent BASIC lines without warning. - The symbol table is capped at 48 definitions and 96 references (
LL=48); exceeding these limits will cause a subscript error with no graceful message.
Content
Source Code
1 REM 2A3C401122 0ED52444D21824019C5CD9E 9C12A7F40 9227F402A2940 9222940C9
2 REM SIRIUSWARE HEXAS*
3 REM COPYRIGHT 1983 D.B.WOOD
4 PRINT "BYTES NEEDED IN REM"
5 INPUT N
6 POKE 16445,INT (N/256)
7 POKE 16444,N-256*PEEK 16445
8 RAND USR 16514
9 FOR I=0 TO N-1
10 POKE 16514+I,22
11 NEXT I
12 REM ==END OF REM EXPANDER==
6670 REM ==RELOCATING ASSEMBLER=
6675 REM PLACE YOUR MACH. LANG. CODE IN FRONT OF THIS
6680 REM (3E 00 A7 2A 7B40 11 8240 ED52 E5 21 0000
6685 REM (5E 23 56 23 E3 E5 EB 4E 23 46 EB 09 EB 72 2B 73 E1 E3 3D 20 EB
6690 REM (E1 11 8240 19 EB 01 0000 EDB0 C9)
6702 LET I=1
6704 LET T=28
6705 LET TT=256
6706 LET NL=118
6708 LET O=0
6710 LET NS=I
6720 LET NR=NS
6730 LET LL=48
6740 DIM N$(LL,8)
6750 DIM R$(LL+LL,9)
6760 DIM N(LL)
6770 DIM R(LL+LL)
6780 PRINT "START ADDR: ";
6790 INPUT Z
6795 PRINT Z
6800 LET ZZ=Z
6810 LET A=16508
6870 GOSUB 6930
6880 IF X$="(" THEN GOSUB 7050
6910 IF X$=")" THEN GOTO 7700
6920 GOTO 6870
6930 LET A=A+5
6940 IF 234=PEEK A THEN GOTO 7000
6950 LET A=A+I
6960 IF NL<>PEEK A THEN GOTO 6950
6970 IF NL<>PEEK (A+I) THEN GOTO 6930
6980 PRINT "NO CODE"
6985 IF Z=ZZ THEN STOP
6990 PRINT " END "")"""
6995 STOP
7000 LET A=A+I
7010 LET X$=CHR$ PEEK A
7020 IF X$=")" THEN RETURN
7030 IF X$<>"(" THEN GOTO 6960
7040 RETURN
7050 LET AA=A-5
7060 GOSUB 7180
7070 IF E=I THEN RETURN
7080 LET A=A+I
7090 LET Y=PEEK A
7100 IF Y<T OR Y>43 THEN GOTO 7150
7120 POKE Z,16*(X-T)+Y-T
7130 LET Z=Z+I
7140 GOTO 7060
7150 PRINT "-HEX ERROR"
7160 POKE A,Y+128
7170 PRINT "AT LINE ";TT*PEEK AA+PEEK (AA+I)
7175 STOP
7180 LET E=O
7190 LET A=A+I
7200 LET X=PEEK A
7210 IF X=O THEN GOTO 7190
7220 IF X=NL THEN LET E=I
7230 LET X$=CHR$ X
7240 IF X$=")" THEN LET E=I
7250 IF E=I THEN RETURN
7260 IF X<T OR X>43 THEN GOTO 7290
7280 RETURN
7290 IF X$="+" OR X$="$" OR X$="£" THEN GOTO 7380
7310 GOSUB 7460
7330 LET N$(NS)=S$
7340 LET N(NS)=Z
7350 IF W>O THEN LET N(NS)=M
7360 LET NS=NS+I
7370 GOTO 7200
7380 GOSUB 7460
7400 LET R$(NR)=S$
7410 LET R(NR)=Z
7420 IF X$="$" OR X$="£" THEN LET Z=Z+I
7430 LET NR=NR+I
7440 LET Z=Z+I
7450 GOTO 7200
7460 LET S$=X$
7480 LET M=O
7490 LET W=M
7500 LET A=A+I
7510 LET Q$=CHR$ PEEK A
7520 IF Q$=" " OR Q$>"Z" THEN RETURN
7540 IF Q$="=" THEN GOTO 7570
7550 LET S$=S$+Q$
7560 GOTO 7490
7570 IF X$="+" OR X$="$" OR X$="£" THEN GOTO 7680
7580 LET A=A+I
7590 LET X=PEEK A
7600 IF X=O AND W<=O THEN GOTO 7580
7610 IF X=NL THEN RETURN
7630 IF X=O THEN RETURN
7640 IF X<T OR X>43 THEN GOTO 7680
7650 LET M=16*M+X-T
7660 LET W=W+I
7670 IF M<65536 THEN GOTO 7580
7680 PRINT "-EQU ERROR"
7690 GOTO 7910
7700 LET W=O
7720 FOR J=I TO NR-I
7730 FOR K=I TO NS-I
7740 IF N$(K)=R$(J,2 TO ) THEN GOTO 7800
7750 NEXT K
7770 PRINT "UNDEFINED: ";R$(J,)
7780 LET W=W+I
7785 GOTO 7895
7800 IF R$(J,I)="$" OR R$(J,I)="£" THEN GOTO 7920
7820 LET X=N(K)-R(J)-I
7830 IF X<128 AND X>-129 THEN GOTO 7880
7850 PRINT "REL JUMP RANGE ERROR"
7860 PRINT J;"TH REFERENCE. JUMP TO ";N$(K)
7862 FOR K=I TO NS-I
7864 IF R(J)<N(K) THEN GOTO 7868
7866 NEXT K
7868 PRINT "FOLLOWING ";N$(K-I)
7870 GOTO 7910
7880 IF X<O THEN LET X=X+TT
7890 POKE R(J),X
7895 NEXT J
7900 IF W=O THEN GOTO 7970
7910 PRINT "COMPILATION ABORTED"
7915 STOP
7920 LET NN=INT (N(K)/TT)
7930 POKE R(J),N(K)-NN*TT
7940 POKE R(J)+I,NN
7950 GOTO 7895
7970 PRINT "COMPILATION COMPLETED"
7980 PRINT Z-ZZ-48;" BYTES"
7990 PRINT "RELOCATOR ENTRY: ";Z-48
8010 PRINT " SYMBOL ADDR REL"
8020 FOR Q=I TO NS-I
8030 PRINT " ";N$(Q,);TAB 12;N(Q);TAB 22;N(Q)-ZZ
8040 NEXT Q
8200 PRINT "TYPE CONT FOR RELOCATOR SETUP"
8205 STOP
8995 REM == RELOCATOR SET
9000 LET ZX=Z
9005 FOR J=I TO NR-I
9010 IF R$(J,I)<>"$" THEN GOTO 9040
9015 LET NN=INT (R(J)/TT)
9020 POKE Z,R(J)-NN*TT
9025 POKE Z+I,NN
9030 LET Z=Z+I+I
9040 NEXT J
9050 POKE ZX-47,(Z-ZX)/2
9055 LET NN=INT (ZX/TT)
9060 POKE ZX-35,ZX-NN*TT
9065 POKE ZX-34,NN
9070 LET K=ZX-ZZ-48
9075 LET NN=INT (K/TT)
9080 POKE ZX-5,K-NN*TT
9085 POKE ZX-4,NN
9090 PRINT "TOTAL BYTES INCLUDING RELOCATIONDATA: ";Z-ZZ
1 REM AF18 137F5CD23 F2A294011 5 0192216407ECDD914CD8A15ED437B402A16403E76BE2320FCCD 141223E40CD 141F12A3E4038 FEBED52444D2A7B40EBEDB02B7E12C9E5A7ED52444DD5EB2BCDAD 9E1D1D5EDB0D12A7B407EFE76C8E5D5CDF2 9CDC5 ED1D5C52A1C40E5EB2BCDAD 9ED5B1C40E1EDB8C1D1E1EDB018DAE53E76BE20 2CF F232323233EEABE28 7E1CDF2 9EB18E83E D 1 3 023EDA120EFEA1F413E76BE20E723D1C93E 3A72A7B40118240ED52E5215E415E235623E3E5EB4E2346EB 9EB722B73E1E33D20EBE111824019EB 1AC 0EDB0C9A940AF4024411B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B
2 REM COPYRIGHT 1982 D.B.WOOD
10 SAVE "BASIC*OLA%Y"
15 PRINT "ADDR:"
30 INPUT E
35 POKE 16508,INT (E/256)
40 POKE 16507,E-256*PEEK 16508
45 RAND USR 16686
50 PRINT "ENTRY POINTS"
55 PRINT " COPY ";E
60 PRINT "OVERLAY ";E+3
70 PRINT "NEXT AVAIL ADDR ";E+172
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

