Hexas, Basic*Olay

Products: BASIC*OLAY, HEXAS*
Date: 1983
Type: Cassette
Platform(s): TS 1000

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:

  1. 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 address Z. Labels (prefixed +) are recorded in N$(NS)/N(NS). References (prefixed $ or £) are deferred into R$(NR)/R(NR).
  2. 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

ArrayDimensionsPurpose
N$(LL,8)48 × 8 charsSymbol names (definitions)
N(LL)48 elementsSymbol addresses
R$(LL+LL,9)96 × 9 charsReference names with type prefix
R(LL+LL)96 elementsReference 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=28 is the BASIC token code for 0; hex digits 0–9 and A–F are detected by testing X >= T and X <= 43 (token 43 = F), exploiting the ZX81 token table layout where digits are consecutive.
  • NL=118 is 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+TT at 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 A byte-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 7490 inside the label-parsing loop at line 7560 uses a bare LET 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 7490 but the loop body starts at 7500; line 7490 only resets LET 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; if N is 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

Appears On

Related Products

Permits the full use of 64K RAM. Overlay/copy BASIC program segments.
Symbolic hex assembler written in BASIC. Generates relocatable code.

Related Articles

Related Content

Image Gallery

Hexas, Basic*Olay

Source Code

   1 REM 2A3C4011220ED52444D21824019C5CD9E9C12A7F409227F402A29409222940C9
   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 AF18137F5CD23F2A29401150192216407ECDD914CD8A15ED437B402A16403E76BE2320FCCD141223E40CD141F12A3E4038FEBED52444D2A7B40EBEDB02B7E12C9E5A7ED52444DD5EB2BCDAD9E1D1D5EDB0D12A7B407EFE76C8E5D5CDF29CDC5ED1D5C52A1C40E5EB2BCDAD9ED5B1C40E1EDB8C1D1E1EDB018DAE53E76BE202CFF232323233EEABE287E1CDF29EB18E83ED13023EDA120EFEA1F413E76BE20E723D1C93E3A72A7B40118240ED52E5215E415E235623E3E5EB4E2346EB9EB722B73E1E33D20EBE111824019EB1AC0EDB0C9A940AF4024411B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B1B
   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.

People

No people associated with this content.

Scroll to Top