PASS1

PASS1: the compiler proper

What it does

PASS1 reads the tokenized Applesoft program one byte at a time, checks its syntax, builds the symbol table and writes the object code. It makes a single pass over the source, so a reference to something not yet known (a line further on, a variable's access routine, a function defined later) cannot be written as an address. Instead it is written as a link: each reference holds the address of the previous reference to the same symbol, and the symbol holds the latest one. PASS2 later follows these backward chains and replaces every link with the real address.

Inputs: the source file (read through DOS's text-file READ, which PASS0's DOS patches make byte-exact), and the COMMON block PASS0 filled in (options, memory layout, files, the call table and the operator table, the empty symbol table). Outputs: the object file (header space, code, string literals and DATA in place, all references chained), the line number file CLINENUM (each line's number and address), and the symbol table in COMMON for PASS2. With the listing option it also prints each line as it is compiled.

Overall flow

  1. Setup (line 200). Sizes of the buffers and tables; the call table is copied from SC(150..285) into CC() and the operator table from SC(300..350) into OE() (line 320, line 350), before the symbol table space they sit in is used. The files are opened and 24 bytes are reserved at the start of the object file for the header PASS2 writes.
  2. First line (line 690). The code starts 20 bytes into the program; the first line's link and number are read. An empty program stops here.
  3. Statement loop (line 770, line 780). For each statement: an end of statement is skipped; any statement except REM ends the declarations; a name starts an assignment; a statement after an unconditional transfer is skipped with a warning; otherwise the keyword dispatches through three ON ... GOTO lists (line 860).
  4. Statement handlers (below) parse the statement and write its code, nearly always ending at the end-of-statement routine (line 6830): the line is listed when complete, Control-C is checked, and the next statement or line follows.
  5. End of program (line 6870). IF statements on the last line are patched, a final JMP to the library's END is written, the files are closed and PASS2 is run.

A fatal error (line 7720) is reported with the line and a marker, then compilation carries on so further errors are found, but no more code is written. After the last line a fatal error ends the compilation in PASS2.

Reading the source

The program file is the tokenized program as Applesoft stores it: two length bytes, then lines of [link, link][number, number][tokens...][0], ending with a zero link. Three variables form a short pipeline (line 8200): T0 is the current token, MJ the next one, and IV the byte just read from the file. When T0 becomes the zero at the end of a line, the next two bytes are the link (zero ends the program, MJ = -1) and the next line starts (line 8220):

Each token is copied into the listing buffer at $0280-$036F as it is passed; the page-3 routine at $038D prints the buffer with the keywords spelled out. A line that fills the buffer is TOO LONG.

Statement handlers

Most statements compile to their argument expressions followed by a JSR to a library or ROM routine taken from the call table. For a simple statement the call table index is the token minus 128 (PR# is 10, CALL 12, HOME 23). Most entries are the integer-argument entry point; the call emitter uses the entry 3 bytes lower when the argument is real (line 16400).

StatementHandlerCode written
ENDline 1000JMP END ($1363)
FORline 1100initial value; a loop-start operation on the loop variable (21-24 by variable and value type) plus 2 bytes for the variable's address; limit; step (or step 1)
NEXTline 1400per variable: operation 25 and its address; a bare NEXT calls the library's NEXT with a zero word
DATAline 1500JMP over the items, each as length and text, then 255 and a link; both words are chained through CC
INPUTline 1800input start; optional prompt literal; per item a read routine (first, after a prompt, next; number or string) and the store; input end
DIMline 2100no code: the dimensions go into each array's symbol entry (line 2200)
READline 2500per item: read a number or string, then the store
PLOT, HLIN, VLINline 2600each value followed by a call that saves it, the last one by the statement's call
HPLOTline 2800first point, then each TO point
DRAW, XDRAWline 3100shape number, optionally AT x, y
ONERR GOTOline 3300the library's ONERR call followed by the line's address (chained)
RESUMEline 3400a JMP to the library's RESUME only with the RESUME/debug option; otherwise a warning
LET, assignmentline 3500the target, the value by type, the store (line 20300)
GOTOline 3650JMP line (chained)
RUNline 3700JMP $03F5; RUN line: CLEAR, then JMP line
IFline 3800the condition and a flag test; THEN line: BEQ *+5 / JMP line; THEN RETURN: BEQ *+3 / RTS; THEN statements: BNE *+5 / JMP next line with the address patched when the next line starts
GOSUBline 4000JSR line (chained)
RETURNline 4100RTS
REM, REM!line 4200REM: nothing; REM! directives (below)
STOP, NEWline 5300the library's STOP, or END for NEW
ON ... GOTO/GOSUBline 5400index; the library's ON routine; a count byte (patched at the end); one chained word per line
WAITline 5600address, mask, optional second mask
DEF FNline 5800JMP over the body; the body stores the argument in the parameter, computes the expression and returns
POKEline 6100address, value
PRINTline 6200per item a print call by type; , a tab call; TAB( and SPC(; a carriage return unless the statement ends with ; , TAB( or SPC(
GETline 6400per item: get a number or a one-character string, then the store
CALL, HIMEM:line 6600argument, call (real entry in the table)
one-argument statementsline 6700argument, call (VTAB HTAB COLOR= HCOLOR= SPEED= ROT= SCALE= IN# PR#)
no-argument statementsline 6800a call (POP, CLEAR, and without the debug call GR TEXT HGR HGR2 HOME NORMAL INVERSE FLASH RESTORE)
unsupported keywordsline 6950none: WARNING / LINE NUMBER n / IGNORED

With the RESUME/debug option most handlers start with a call to the library's debug routine (line 16300), which records the statement's address and stack level so RESUME can repeat it. The statement right after THEN does not get one (RC).

Dead code. After END, STOP, NEW, GOTO, IF ... THEN line, RETURN, ONERR, RUN or (with the option) RESUME, D3 is set (line 4120). Until the next line, any further statement that starts with a keyword, except DATA and REM, is skipped with the warning; an assignment is still compiled. One consequence, kept on purpose: in IF X THEN RETURN : Y = 1 the assignment runs when X is false.

Directives (REM!)

Recognized only at the start of the program, before any other statement (line 4200). Spaces are allowed between the letters (line 5100); a remark that does not match is ignored.

Expressions

An expression is handled in two steps: the parser turns it into a table of triples, and the code generator turns the triples into code.

Parsing (line 11310)

A bottom-up parser with one stack, SZ(): operators and operand triples are pushed from the bottom, saved values (subscript counts, the function argument description) from the top; the two sides meeting means TOO COMPLEX. Operands become triples at once: a variable or constant (line 9000, kind = its type), a string literal (kind -4, written in place as JMP over [length][text], line 10500), an array element (the array's triple plus pairs of subscripts with operation 22, line 9600), a function call (its operation is token-181, arguments as described in F(), line 10100), FN (operation 18, line 12540). Before each operator the reduce step (line 13500) compares priorities from the operator table and pops operators into new triples while the one on the stack has the higher priority; a closing parenthesis removes its pair. Two-character relations are combined first (line 13200). Numeric and string parts are parsed by separate paths, because a string can only be followed by + (concatenation) or a relation; a string comparison gives a numeric result.

Operation numbers in the triples:

NumbersOperations
1-7+ - * / ^ AND OR
8-13> = < >= <= <>
14, 15, 16NOT, negation, concatenation
17MID$'s third argument
18FN call
19, 20, 21real, string, integer array element
22a pair of further subscripts
23-28the string relations
29-53functions, token-181 (SGN 29 ... ASC 49, CHR$ 50, LEFT$ 51, RIGHT$ 52, MID$ 53)

Operand kinds (negative): -1 integer, -2 string, -3 real (variables and constants), -4 string literal, and, once code exists for a triple, -5 result in the accumulator (real or string), -6 real temporary, -7 string temporary, -8 integer temporary, -9 result in the integer accumulator.

Constants (line 12210): with the integer constants option a whole number up to 32767 is an integer constant; anything else is packed into the 5-byte floating point form by the USR routine at $0377 (the ROM's own conversion and rounding). Each is entered once in its constant chain (value or exponent mod 32).

Code generation (line 17200)

The triples are taken in order. For each one the generator decides whether its operands are used as integers or reals (RN) and whether the result is real (RV): + - * and negation are integer operations only with the integer arithmetic option and integer operands; relations and AND/OR/NOT always work on integers as integers; division and the functions are real, except for the few that take or give integers. An integer left in the integer accumulator that is needed as a real is floated first. Then by operation (line 17550):

Where the result goes (line 19810): if the next operation uses it from the accumulator it stays there; otherwise it is stored in a temporary of its type (line 16800), which is freed again once it has been used.

The store (line 20300) writes operation 1 on the target (or 7 to store a real value into an integer variable, which rounds and range-checks), or the library's array store. A load of the variable just stored is left out when the store was the last code written (line 20500). For IF, unless the last operation left the flags set, LDA $9D (real) or LDA $A0 / ORA $A1 (integer) sets them (line 20800).

Writing the object file

All output goes through a few emitters:

LineWrites
line 15300an operation on a symbol: [operation byte][link], marking the operation in the symbol's mask
line 15400an opcode (JMP, JSR) and a link to a line or function symbol
line 15450a word that is an address (kept as address-32767)
line 15600a byte
line 15700a patch of an earlier word or byte
line 16400JSR (or JMP, line 16700) to call table entry CI

The object file is open for WRITE through DOS. DOS's output state byte at $AA52 is set to 4 before each byte, so every character printed goes to the file, and the page-3 routine at $0387 prints the byte. Before a DOS command the state is set to 5 (the start of a write line, where Control-D starts a command). A patch positions the file with WRITE name,B at the byte, writes, and positions it back at the end. With the files on the boot drive, the drive motor is touched after each byte to keep it running.

Data structures

Symbol table, SC()

5351 words in COMMON. Chain heads at the bottom (set by PASS0), then entries allocated upward from K0; SYMBOL TABLE FULL past 5350. Every entry starts with a link to the next in its chain (0 ends it) and a reference chain head (the latest reference in the object file; -32767 for none). New entries go at the end of their chain, so a chain keeps the order of first use.

KindChainWords after the link and reference chain
variable(name hash + type code) mod 32, from VOoperation mask, name hash (99 × first character + second), type code
arraylikewisethe same, then the number of dimensions and each dimension (-1 if never dimensioned)
real constantexponent byte mod 32, from KXoperation mask, the 5 packed bytes
integer constantvalue mod 32operation mask, -10, the value
line number(number-32767) mod 32, from Lnumber-32767 (the address is in the line number file)
function1 + hash mod 4name hash, address of the body (-32767 until defined)
temporary5 integer, 6 string, 7 real, 8 inside DEF FNoperation mask, type (type-3 while in use)
COMMON declarationlist from SC(0)the symbol

Type codes: -3 real, -2 string, -1 integer; arrays add 22. The operation mask has bit n set for every operation n used on the symbol; PASS2 writes an entry point only for those.

Call table, CC()

136 addresses of library and ROM routines (from PASS0), by index. For an operation the index is its number+64 (- is 66, FSUBT; > is 72; SGN is 93).

Operator table, OE()

17 rows of token, stack priority, arriving priority (see PASS0).

Parse stack, SZ(), and triple table, TP()

SZ(0..100) holds operators and operand triples from the bottom (SU) and saved values from the top (V3). Between statements it also holds the addresses of IF jumps waiting for the next line. TP(1..100, 0..2) holds (operation or operand kind, left, right) for one expression; U2 is the next free triple.

Variable access operations

The operation numbers written before a variable reference, which PASS2 turns into calls of the variable's routine:

NumberReal variableInteger variable
0load into the accumulatorload into the integer accumulator
1storestore
2add to the accumulatoradd
3memory minus accumulatormemory minus accumulator
4multiplymultiply
5comparecompare
6load into the second accumulator(6-13: operations 0-7 with the variable used as a real)
7memory divided by accumulator
20test for AND/OR (becomes LDA)test (LDA, ORA)
21-25loop start (by variable and value type), NEXT

String variables have two operations (0 load, 1 store) and arrays two (0 load an element, 1 its address).

Errors

The handler names the kind (line 7530) and reports it (line 7720): two bells, *****FATAL ERROR, the line number, the kind, the line up to the error with !ERR! and the rest of the statement. With the pause option it waits for a key (Control-C ends the compilation, line 20900). The parse may have been deep inside subroutines, so the library routine at $0947 resets the stack before the next statement. The kinds are SYNTAX, DECLARATION, SUBSCRIPT, TOO COMPLEX, TOO LONG, SYMBOL TABLE FULL, INCOMPLETE, OPERAND, REDEFINED and TYPE MISMATCH.