Comments are thrown away by the reader, not read one byte at a time

A comment used to walk through tokGet and srcNext and be classified,
character by character, on the way to being discarded. Comments are most
of what this assembler reads: 87 per cent of what Say.asm pulls in once
services.asm is counted, 59 per cent of CosmOS. The house style here is
dense commentary, so the assembler is penalised more than most by its own
sources.

The saving is not the classifying, it is the BOOKKEEPING. srcNext loads
and stores the walking pointer through memory for every character and
asks numCompare whether the buffer is used up. srcSkipComment keeps the
pointer in a data pointer for a whole run and the newline in B, so a
comment byte costs a load, a compare and two steps. A run is capped at
255 so one byte can count it, which is the only reason it loops.

Measured, each version with its own rebuilt images:

              without        with
  hello       791,957     586,184
  Say       9,924,401   4,506,702   2.20x
  Files    13,197,710   7,188,351   1.84x
  Keys     23,091,447  15,069,880   1.53x
  cosmos  886,498,996 789,982,899   1.12x

Which tracks the comment ratios: Say gains most and cosmos least, in
proportion to how much of each is prose.

---- And two mistakes worth keeping ----

The scratch went among the READER'S STATE, which is a block copied whole
by a count written down somewhere else - so every saved file lost the
last four bytes of itself and an include came back with its pointer
wrong. The comment above that block says not to do this, in capitals.
That is twice this week: scriptCopyState had the same shape this morning.

And a file that ends inside a comment has to put back the file that
included it, exactly as srcAtEnd does for a character. NOTHING IN THIS
REPOSITORY ENDS THAT WAY - every source here ends on a line of code with
a newline after it - so break.sh could not catch an error in that path
because nothing reached it. tail.asm is generated with no newline on its
last line for that reason, and usestail.asm names a label after the
include, which is what goes missing when the include never returns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-06 21:33:15 -04:00
co-authored by Claude Opus 5
parent e20c9bac1f
commit 2e9cb9af7e
3 changed files with 195 additions and 7 deletions
+154
View File
@@ -105,6 +105,143 @@ srcRewindNo:
ADD ; Q is not zero: it is not.
RET
; ---- Everything up to and including the next newline, thrown away ----
;
; Q is zero if a newline was found, and something else if the file ended first.
;
; THIS IS MOST OF WHAT THE ASSEMBLER READS. Measured on this repository: 87 per cent of what
; Say.asm pulls in is comment and blank line, and 59 per cent of CosmOS - the house style is
; dense commentary, so the assembler is penalised more than most by its own sources. Every one
; of those bytes used to go through tokGet and srcNext and be classified and thrown away.
;
; The saving is not the classifying, it is the BOOKKEEPING. srcNext loads and stores the
; walking pointer through memory for every single character, and asks numCompare whether the
; buffer is used up. Here the pointer lives in DP0 for a whole run and the newline lives in B,
; so a comment byte costs a load, a compare and two steps.
;
; A run is capped at 255 so that one byte can count it, which is why the outer loop exists at
; all: a full block is 256, one more than a byte holds.
srcSkipComment:
SETD.0 SrcEnded
LDA.0
BNA srcSkipEnded
srcSkipBlock:
; What is left of the buffer.
SETD.0 SrcSpan
SETD.2 SrcCount
CALL numSet
SETD.0 SrcSpan
SETD.2 SrcAt
CALL numTake
SETD.0 SrcSpan
LDA.0
INCD.0
LDB.0
OR
BRQ srcSkipRefill ; Used up, so the next block or the end.
SETD.0 SrcSpan
LDA.0
BNA srcSkipCapped ; A high half at all means 256, which is one too many to count.
INCD.0
LDA.0
BRI srcSkipCounted
srcSkipCapped:
INIA 0xFF
srcSkipCounted:
SETD.0 SrcRun
STA.0
SETD.0 SrcTaken
STA.0 ; Where the run began, to work out what it swallowed.
SETD.2 SrcPointer
LDD.0.2
SETD.1 SrcRun
INIB 0x0A ; The newline, held in B for the whole run.
srcSkipChar:
LDA.0
XOR
BRQ srcSkipNewline
INCD.0
LDA.1
DECA
STA.1
BNA srcSkipChar
; The run ended without a newline in it, so the rest of the block is comment too.
SETD.2 SrcPointer
STD.0.2
CALL srcSkipAccount
BRI srcSkipBlock
srcSkipNewline:
; Past the newline itself, which belongs to the comment.
INCD.0
SETD.1 SrcRun
LDA.1
DECA
STA.1
SETD.2 SrcPointer
STD.0.2
CALL srcSkipAccount
; A newline is what makes the next character part of the next line, counted as it goes past
; exactly as srcNext counts it.
SETD.0 SrcLine
CALL numStep
RSTA
RSTB
CCF
ADD
RET
srcSkipRefill:
CALL srcLoad
BRQ srcSkipBlock ; Another block of the same file.
; ---- A file that runs out inside a comment ----
;
; The same thing srcAtEnd does for a character, and it has to be done the same way here: an
; included file ending puts the one that included it back on, and the comment carries on
; into it exactly as it did when this was read a character at a time.
;
; NOT DOING THIS WAS THE WHOLE OF THE FIRST VERSION'S BUG. services.asm ends inside a
; comment, so an include of it never came back, and every label after the #Include line -
; the whole of the program doing the including - was quietly never defined. It surfaced as
; "no label of that name is defined anywhere" pointing at a line whose label was three
; lines from the end of the file.
INIA 0d1
SETD.0 SrcEnded
STA.0
SETD.0 SrcDepth
LDA.0
BRA srcSkipEnded ; Nothing included this, so there is no more source anywhere.
CALL srcPop
BRI srcSkipComment
srcSkipEnded:
INIA 0x01
RSTB
CCF
ADD
RET
; How much of the run went by, added to how far into the buffer we have read.
srcSkipAccount:
SETD.0 SrcTaken
LDA.0
SETD.0 SrcRun
LDB.0
CCF
SUB
MVQA
SETD.0 SrcAt
CALL numAddByte
RET
; The next character of the file, into SrcChar. Q is zero if there was one, and something
; else at the end of the file.
srcNext:
@@ -576,6 +713,23 @@ SrcStateBytes:
SrcDepthLimit:
0d6
; ---- OUTSIDE the state block above, and that is not an accident ----
;
; What is left of the buffer, how much of it one run may take, and where that run began.
; They live for the length of one call to srcSkipComment and never across an include, so they
; are not part of what a file remembers while another one is being read.
;
; PUT AMONG THE STATE FIRST, which added four bytes to a block that is copied whole by a
; count written down somewhere else - so every saved file lost the last four bytes of itself
; and an include came back with its buffer pointer wrong. The comment two paragraphs up says
; not to do it, and says it in capitals.
SrcSpan:
0x00 0x00
SrcRun:
0x00
SrcTaken:
0x00
; Where an include is looked for when it is not beside you. One fixed place rather than a
; list somebody sets, for the same reason the shell has one fixed place for programs: a
; list would need somewhere to live between one boot and the next.