Conditionals and packs
Two questions look alike and are not. Which statements are in the program is one; which value a number has is the other. The tool answers them with different constructs and refuses to let one do the other's work.
Which statements
Advancing a pointer by one is inc. By two it is inc twice. By seven it is
an addition, and writing that out for one would be five bytes wasted.
; Advance a zero-page pointer by a constant. There is a shortest way for each
; of them, and which one is written out is decided where the macro is used.
.macro skip p, n
.if n == 1
inc p
.elsif n == 2
inc p
inc p
.else
clc
lda p
adc #n
sta p
.endif
.endm
.proc entry
at .ztemp 1
skip at, 1
skip at, 2
skip at, 7
rts
.endp
NGA memory map
phase phase0 (0)
zero page: 1 of 128 bytes
memory: 14 of 40960 bytes
$0080-$0080 skip.entry.at temporary phase0
$2000-$200D skip.entry proc phase0
Fourteen bytes: two for the first use, four for the second, seven for the
third, and the rts. The branches not taken are not in the program — not as a
jump, not as a byte — because a macro's conditionals are decided before any of
its body is instantiated, and each use is decided again.
The condition is a declared value and must be. It decides how many bytes
there are, so a condition naming a position or a size would make a size depend
on itself, which is the cycle this whole model is arranged to avoid. A label
inside a branch has to be local, @name: two branches want one name for the
same thing, and a Symbol may be defined once.
However many there are
The last parameter of a macro may be a pack, with the dots touching the name. It takes every argument the names before it do not, and it has two readings, told apart by where the name stands.
; Both readings of a pack in one statement: the name alone is how many
; elements it has, and the name with the dots spreads them where it stands.
.macro table values...
.byte values, values...
.endm
The name alone is an Integer: how many elements there are. The name with the
dots spreads them — they stand in its place, among whatever else the list
holds. So that one statement writes a count and then the elements, and
table 3, 1, 4, 1, 5 comes out as six bytes beginning with 5.
The other way to walk a pack is to take it apart.
; The other way to walk one: a case per shape, and a recursion that takes an
; element off each turn. The empty case is where it stops.
.macro poke at, values...
.match values
.case first, rest...
lda #first
sta at
poke at + 1, rest...
.case
.endmatch
.endm
.match chooses a .case by how many elements the pack has, first fit wins,
and the case's names stand for the elements — one each, with its own pack
taking the rest. The recursion spreads what is left, so each turn round is one
shorter, and the .case with no names fits the empty pack, which is where it
stops.
The pattern is not the only thing that can stop it. A count can, and then the two constructs of this chapter are doing a job each:
; The third shape, and the one the library uses: a count that comes down by one
; each turn, with `.if` on it deciding whether there is another. It writes as
; many of the list as the number says and drops the rest, so a list may be
; longer than the machine it is written for.
.macro take n, values...
.if n > 0
.match values
.case head, rest...
.byte head
take n - 1, rest...
.endmatch
.endif
.endm
.if n > 0 decides whether there is another turn and .match takes one
element off, so the recursion runs n times however long the list is. That
buys something the other shapes cannot: the list may be longer than the number,
and the rest is dropped. take 3, 3, 1, 4, 1, 5 writes three bytes.
atari/portb.asm writes its table of register values exactly this way, so that
a machine with fewer Banks takes a prefix of the list and one with more adds to
it, with nothing in the driver needing to know which machine it is.
.proc entry
poke buffer, 9, 2, 6
lda counts
lda shorter
rts
.endp
NGA memory map
phase phase0 (0)
zero page: 0 of 128 bytes
memory: 34 of 40960 bytes
$2000-$2005 pack.counts section phase0
$2006-$2008 pack.buffer section phase0
$2009-$200B pack.shorter section phase0
$200C-$2021 pack.entry proc phase0
Three stores to three consecutive addresses, a six-byte table that begins with its own length, and a three-byte one out of a list of five. Not one of those numbers is written anywhere.
Which value
A second is fifty frames on one machine and sixty on the other. That is one number with two values, and it is written as one.
; A second is fifty frames on one machine and sixty on the other. That is one
; number with two values, not two programs, so it is written as a value.
frames = NTSC ? 60 : 50
NTSC comes from the Project, where a constants block gives a name a literal
and every Module sees it.
constants {
NTSC = 1
}
?: chooses between two values, and only the condition and the answer taken
are evaluated — which is not an optimisation but the meaning, and is what lets
narrow ? 4 : buffer.runtimeSectionSize be a declared value when narrow
holds. It is a conditional value and not conditional assembly: both answers
are References, so both keep what they name from being pruned.
Not the other way
Every other assembler writes the frame count the first way anybody would think of, and here it is refused twice over.
; The way every other assembler writes it, and the one thing a branch may not
; hold: whether `frames` exists at all would depend on the condition.
.if NTSC
frames = 60
.endif
error[NGA0138]: `.if` stands in no section; what has an address is written in a `.section` or a `.proc`
--> wait.asm:4:1
|
4 | .if NTSC
| ^^^
error[NGA0169]: a constant definition cannot stand in a conditional branch, which holds instructions and data
--> wait.asm:5:1
|
5 | frames = 60
| ^^^^^^
.if chooses statements, so it stands where a statement stands — in a
.section, a .proc or a macro body, and not at the top of a file where
nothing emits. And a name is not a statement. No Symbol is ever conditional
here: if one could be, whether a name existed at all would depend on a
condition, and every Step after this one would have to ask before it could look
anything up. What a branch holds is what a macro body holds, and for the same
reason.