Hacker News

Favorites Setup
Comment by sowbug | original | How did Woz write the Apple 1 BASIC before building the computer?
[−]sowbug · 2026-11-15 Sun 00:41 UTC · link
The Apple II had a thing called the monitor, which was a tiny CLI program in ROM that you could invoke from the BASIC command line (CALL -151). With it you got the ability to disassemble memory (300L would spit out 6502 opcodes starting at address 768), set memory regions to sequences of hex digits (300: A9 21 20 FO FD 60 would write a tiny program on an Apple II Plus that printed an exclamation point, if my memory serves), and execute memory (300G).

With these three commands, and a working memory of the hex that corresponded to the opcodes, you could be reasonably productive writing short 6502 programs.

Fun and extremely niche fact: the typo CALL -161 jumped into the monitor, but only after the weird side effect of unhooking the sniffer that watched for DOS 3.3 commands sent via the ROM's text I/O. Not only did this make text print faster -- useful when you were visually dumping memory to the screen to find something quickly, but it also protected against accidentally destroying your disk if you unwittingly printed a sequence like ^M^DINIT HELLO^M, which would trigger the disk initialization routine.

[−]tmoertel · 2026-11-15 Sun 01:46 UTC · link
> 300: A9 21 20 FO FD 60 would write a tiny program on an Apple II Plus that printed an exclamation point, if my memory serves.

Ancient assembly hacker tip: A JSR/RTS sequence can be simplified to a JMP. To continue your example, the bytes you provided decompile as follows (assuming Apple 2 monitor ROM at $F800):

            PRINTBANG:
    A9 21      LDA #$21
    20 F0 FD   JSR COUT1
    60         RTS
So, you load the A register with #$21, which is the ASCII code for `!` in hexadecimal, and then call COUT1 which will output the character in the A register, printing the exclamation point. When COUT1 is finished, it will return to you using an RTS instruction, and then you'll return to your caller using another RTS instruction.

Since the call to COUT1 is a tail call, you can borrow its RTS instruction to return to your caller and save one instruction. So the following code is equivalent to yours but a little bit smaller and faster:

            PRINTBANG:
    A9 21      LDA #$21
    4C F0 FD   JMP COUT1
Notice that the JSR instruction has become a JMP.

Another related trick: When you have a routine that callers often want to call twice—say to print `!!` for extreme exclamations—you may be tempted to create a do-it-twice call:

              PRINTBANG2:
    20 00 03    JSR PRINTBANG
    20 00 03    JSR PRINTBANG
    60          RTS
But now we know better and code it this way:

              PRINTBANG2:
    20 00 03    JSR PRINTBANG
    4C 00 03    JMP PRINTBANG
But we can do even better! We can eliminate the jump and save three more bytes by placing our do-it-twice routine immediately before the routine it is calling:

              PRINTBANG2:
    20 03 03    JSR PRINTBANG
              PRINTBANG:
    A9 21       LDA #$21
    4C F0 FD    JMP COUT1
When PRINTBANG2 is called, it calls PRINTBANG to print an exclamation point, but when PRINTBANG returns, it returns to PRINTBANG2, which promplty falls through to PRINTBANG, calling it again and printing a second exclamation point. This time, however, when PRINTBANG returns, it will return to the original caller.

Back when every byte mattered, these tricks were useful to know.

[−]flopsamjetsam · 2026-11-15 Sun 03:35 UTC · link
> CALL -151

I haven't seen that in a long time! Young me was too young to understand 2s complement arithmetic, or that addresses would wrap around. I just thought it was some kind of magic.

[−]jsrcout · 2026-11-15 Sun 07:22 UTC · link
There was also a mini-assembler [0] that let you enter assembly code directly into memory. It was dead simple to use, and since it printed an assembly listing after you entered each instruction, it was conducive to learning the hex codes by heart.

The mini-assembler didn't understand symbols (other than the opcodes themselves), but it did understand the addressing modes. This saved you from, for instance, hand calculating branch offsets.

Not all Apple II models included it, but I can't find a reference right now. Ours had it.

[0] https://www.callapple.org/vintage-apple-computers/apple-ii/a... (1978)

[−]sehugg · 2026-11-15 Sun 11:14 UTC · link
It was removed in the II+ to make room for AppleSoft BASIC. But there was a relocated version you can run out of RAM: https://savagetaylor.com/TIL/TIL00005.pdf
[−]tmoertel · 2026-11-15 Sun 13:07 UTC · link
Guess what? Apple brought back the mini-assembler when it released the "enhanced" version of the Apple //e in in 1985:

Monitor changes also included a return of the mini-assembler, absent since the days of Integer BASIC. It was activated by entering a “!” command in the Monitor, instead of a jump to a memory location as in the older Apple II. Features also added included the ability to enter ASCII characters directly into memory, and an “S” command to make it possible to search memory for a byte sequence. Interrupt handling was also improved. However, the “L” command to disassemble 6502 code still did not handle the new 65c02 opcodes as did the IIc disassembler.

Source: https://www.apple2history.org/history/ah07/#07:~:text=Monito...

[−]tonyedgecombe · 2026-11-15 Sun 09:39 UTC · link
I did a ton of assembler programming on the Apple II but for some reason I don't remember the monitor at all.