The Clean Code Blog

by Robert C. Martin (Uncle Bob)

Roots

25 September 2021

When I was 15 or so, my father would drive me, and my best friend, Tim Conrad, to the Digital Equipment Corporation (DEC) sales office each Saturday. This was a 30 min drive. My father would drop us off in the morning and pick us up in the late afternoon. He spend two hours in his car each Saturday hauling us around.

Thanks Dad!

Tim and I would spend our day "playing" with the floor model of the PDP-8 they had at the office. The office staff were very accommodating and accepting of our presence, and they helped us out if we needed any fresh rolls of teleprinter paper, or paper tape.

Several years later, at the age of 20, I found myself working at Teradyne Applied Systems in Chicago. The computer we used there was called an M365; but it was really just an upgraded PDP-8. We used it to control lasers in order to trim electronic components to very precise values.

Forty four years later, in May of 2015, I started playing with a cute little Lua environment on my iPad called Codea. I wrote several fun little programs, like lunar lander, etc. But then I thought: "Wouldn't it be fun to write a PDP-8 Emulator?"

A few days/weeks later I had a nice little PDP-8 emulator running on my iPad. I found some archived binary images of ancient paper tapes and managed to load them into my emulator. This allowed me to run the suite of development tools that I had used back in those early days.

Then Apple decided it didn't want people writing code on the Ipad that was not distributed through the App store, so they blocked the means by which Codea users could share source code. Indeed, I couldn't even move Lua source code to my new iPads. So the emulator was lost.

Fortunately I had put the last working version up on GitHub.

At some point, Apple reopened the channel, perhaps due to a court case. I discovered this a few weeks back, and loaded that old source code back into my iPad. It worked like a champ.

I made a few changes to deal with the bigger screen, and the faster processor, and then announced it on twitter. I think many people have played with it since.

You can get the emulator here. You'll find a lot of good tutorial information, and several demonstration videos in that repository.

Euler 4

As you may know I have a youtube series on the cleancoders.com channel, in which I walk through the problems in the Euler project solving them in Clojure and then taking them to the max, Myth-buster style.

Euler 4 is a simple little problem of finding the factors of palindromic numbers. I quickly solved it in Clojure, and then I thought it would be fun to write a PDP-8 program to solve it.

Down the rathole I went.

I used TDD to get the individual subroutines working. Among the subroutines I wrote were single and double precision multiply and divide routines. (We didn't use the word "functions" back then.) The poor PDP-8 could only add. It couldn't even subtract. Subtraction was accomplished by using twos-complement addition (let the reader understand;-)

Was this fun? Yes, at first it was kinda cool to reminisce, and to feel all the old knowledge and instincts come flooding back into my brain. But once the "novelty" wore off, it stopped being fun, and just turned into work -- grinding, tedious, work.

It took me several hours, over a period of a few days, but I got the blasted thing working. It's not an experience I'd like to repeat. Working on a PDP-8 is a PITA, even when with all the cheats I supply in my Emulator.

Here, for your edification, is my solution to Euler 4 on a PDP-8. This code solves the problem; but I'm quite sure it has some really nasty bugs anyway. I am in no way proud of this code. I'm just not willing to improve it. If you study it you'll see just how awful it is. I mean, among other sins I used truly naive algorithms for multiplying and dividing numbers.

Anyway, be careful. The lure of the rathole is very compelling.

/EULER 4 SOLUTION
 PZERO=20
 *200
 
MAIN, CLA
 TLS
 TAD SEED
 ISZ SEED
 CIA
 JMS CALL
 MKPAL
 
 JMS CALL
 PRDOT
 CLA
 TAD MAXFAC
 DCA FAC
FACLUP, 
 CLA
 TAD FAC
 TAD K100
 SMA CLA
 JMP MAIN
 
 JMS CALL
 DLOAD
 DPAL
 TAD FAC
 CIA
 JMS CALL
 ISFAC
 SKP
 JMP GOTFAC
 
 CLA
 TAD I OFP /OTHER FAC > 999 TRY NEXT PAL.
 TAD MAXFAC
 SMA CLA
 JMP MAIN
 ISZ FAC
 JMP FACLUP
 
GOTFAC,
 CLA
 TAD I OFP
 TAD MAXFAC
 SMA CLA
 JMP MAIN
 JMS CRLF
 CLA
 TAD FAC
 CIA
 JMS CALL
 PRAC
 JMS CALL
 PRDOT
 CLA
 TAD I OFP
 JMS CALL
 PRAC
 JMS CRLF
 JMS CALL
 DLOAD
 DPAL
 JMS CALL
 PRDACC
 JMS CRLF
 HLT 
 DECIMAL
SEED, -999
MAXFAC, -999
 OCTAL
FAC, 0
OFP, OTHFAC+1
 *400
/MAKE A PALINDROMIC NUMBER FROM A SEED.
/ABC->ABCCBA IN DECIMAL IN DACC AND STORED IN DPAL
MKPAL, 0
 DCA DPAL+1
 DCA DPAL
 TAD DPAL+1
 JMS CALL
 DIV
 K10
 DCA WRK
 TAD REM
 DCA DIGS
 TAD WRK
 JMS CALL
 DIV
 K10
 DCA DIGS+2
 TAD REM
 DCA DIGS+1
 JMS CALL
 DLOAD
 DPAL
 TAD K1000
 JMS CALL
 DMUL
 JMS CALL
 DSTORE
 DPAL
 CLA
 TAD DIGS
 JMS CALL
 MUL
 K10
 TAD DIGS+1
 JMS CALL
 MUL
 K10
 TAD DIGS+2
 DCA DWRK+1
 DCA DWRK
 JMS CALL
 DLOAD
 DPAL
 JMS CALL
 DADD
 DWRK
 
 JMS CALL
 DSTORE
 DPAL
 JMP I MKPAL
 
/SKIP IF AC IS A FACTOR OF DACC. AC=0
ISFAC, 0
 DCA DFAC+1
 DCA DFAC
 
 JMS CALL
 DDIV
 DFAC
 
 JMS CALL
 DSTORE
 OTHFAC
 
 JMS CALL
 DLOAD
 DREM
 JMS CALL
 DSKEQ
 D0
 SKP
 ISZ ISFAC
 JMP I ISFAC
DFAC, 0
 0
OTHFAC, 0
 0
 
 OCTAL
DPAL, 0
 0
 
DIGS, 0
 0
 0
 
WRK, 0
DWRK, 0
 0
 
// PZERO FOR EULER
 *PZERO
 DECIMAL
K100, 100
K1000, 1000
K10, 10
 OCTAL
PZERO = .
~ 
*1000
/DMATHLIB 
/DLOAD - LOAD ARG INTO DACC, AC=0
DLOAD, 0
 CLA
 TAD I DLOAD
 ISZ DLOAD
 DCA DARGP
 TAD I DARGP
 DCA DACC
 ISZ DARGP
 TAD I DARGP
 DCA DACC+1
 JMP I DLOAD
/DOUBLE PRECISION STORE ACCUMULATOR POINTED TO BY ARG
DSTORE, 0
 CLA
 TAD I DSTORE
 DCA DARGP
 ISZ DSTORE
 
 TAD DACC
 DCA I DARGP
 ISZ DARGP
 TAD DACC+1
 DCA I DARGP
 JMP I DSTORE
/SKIP IF DOUBLE PRECISION ARGUMENT IS EQUAL TO DACC. AC=0
DSKEQ, 0
 CLA
 TAD I DSKEQ
 DCA DARGP
 ISZ DSKEQ
 
 TAD DACC
 CIA
 TAD I DARGP
 SZA CLA
 JMP I DSKEQ
 
 ISZ DARGP
 TAD DACC+1
 CIA
 TAD I DARGP
 SNA CLA 
 ISZ DSKEQ
 JMP I DSKEQ
 
/DOUBLE PRECISION ADD ARGUMENT TO DACC. AC=0
DADD, 0
 CLA CLL
 TAD I DADD
 ISZ DADD
 DCA DARGP
 TAD DARGP
 IAC
 DCA DARGP2
 
 TAD I DARGP2
 TAD DACC+1
 DCA DACC+1
 RAL
 TAD I DARGP
 TAD DACC
 DCA DACC
 
 JMP I DADD
/COMPLEMENT AND INCREMENT DACC 
DCIA, 0
 CLA CLL
 TAD DACC+1
 CMA IAC
 DCA DACC+1
 TAD DACC
 CMA
 SZL
 IAC
 DCA DACC
 JMP I DCIA
 
/MULTIPY DACC BY AC
DMUL, 0
 CIA
 DCA PLIERD
 JMS DSTORE
 DCAND
 JMS DLOAD
 D0
 TAD PLIERD
 SNA CLA
 JMP I DMUL
DMUL1, JMS DADD
 DCAND
 ISZ PLIERD
 JMP DMUL1
 JMP I DMUL
 
PLIERD, 0
DCAND, 0
 0
 
/DIV DACC BY DARG (AWFUL) R IN DREM AC=0
DDIV, 0
 CLA
 TAD I DDIV
 ISZ DDIV
 DCA .+4
 JMS DSTORE
 DVDEND
 JMS DLOAD
 0
 JMS DCIA /NEGATE DIVISOR
 JMS DSTORE
 DVSOR
 JMS DLOAD
 DVDEND
 
 DCA DQUOT 
 DCA DQUOT+1
 JMP DDIV1
 
DDIV2, ISZ DQUOT+1 // INCREMENT DQUOT
 SKP
 ISZ DQUOT
 
DDIV1, JMS DSTORE
 DREM
 JMS DADD
 DVSOR
 TAD DACC
 SMA CLA
 JMP DDIV2
 
 JMS DLOAD
 DQUOT
 JMP I DDIV
 
 
DARGP, 0
DARGP2, 0 
 
DVSOR, 0
 0
DVDEND, 0
 0
DQUOT, 0
 0
 
/PAGE ZERO DATA FOR DMATHLIB
*PZERO
DACC, 0
 0
D0, 0
 0
DREM, 0
 0
PZERO=.
~
/SINGLE PRECISION MATH LIBRARY
 *2000
/DIVIDE AC BY ARGP (SLOW AND NAIVE)
/Q IN AC, R IN REM
DIV, 0
 DCA REM
 TAD I DIV
 ISZ DIV
 DCA ARGP
 TAD I ARGP
 CIA
 DCA MDVSOR
 DCA QUOTNT
 TAD REM
DIVLUP, TAD MDVSOR
 SPA
 JMP DIVDUN
 ISZ QUOTNT
 JMP DIVLUP
DIVDUN, CIA
 TAD MDVSOR
 CIA
 DCA REM
 TAD QUOTNT
 JMP I DIV
MDVSOR, 0
QUOTNT, 0
ARGP, 0
/MULTIPLY AC BY ARGP (SLOW AND NAIVE)
/GIVING SINGLE PRECISION PRODUCT IN AC
MUL, 0
 DCA CAND
 TAD I MUL
 ISZ MUL
 DCA ARGP
 TAD I ARGP
 SNA
 JMP I MUL
 CIA
 DCA PLIER
 TAD CAND
 ISZ PLIER
 JMP .-2
 JMP I MUL
CAND, 0
PLIER, 0
/PZERO FOR SMATHLIB
 *PZERO
REM, 0
PZERO=.
~
/TTY UTILS
 *3000
/PRINT ONE CHAR IN AC. IF CR THEN PRINT LF. 
PRTCHAR,0
 TSF
 JMP .-1
 TLS
 DCA CH
 TAD CH
 TAD MCR
 SZA
 JMP RETCHR
 TAD KLF
 TSF
 JMP .-1
 TLS
RETCHR, CLA
 TAD CH
 JMP I PRTCHAR
CH, 0
MCR, -215
/PRINT AC AS ONE DECIMAL DIGIT AC=0
PRDIG, 0
 TAD K260
 TSF
 JMP .-1
 TLS
 CLA
 JMP I PRDIG
 
K260, 260
/PRINT THE DACC IN DECIMAL
PRDACC, 0
 JMS CALL
 DSTORE
 DACSV
 JMS CALL
 DDIV
 D1E6
 TAD DACC+1
 JMS PRDIG
 JMS CALL
 DLOAD
 DREM
 JMS CALL
 DDIV
 D1E5
 TAD DACC+1
 JMS PRDIG
 JMS CALL
 DLOAD
 DREM
 JMS CALL
 DDIV
 D1E4
 TAD DACC+1
 JMS PRDIG
 JMS CALL
 DLOAD
 DREM
 JMS CALL
 DDIV
 D1E3
 TAD DACC+1
 JMS PRDIG
 JMS CALL
 DLOAD
 DREM
 JMS CALL
 DDIV
 D1E2
 TAD DACC+1
 JMS PRDIG
 JMS CALL
 DLOAD
 DREM
 JMS CALL
 DDIV
 D1E1
 TAD DACC+1
 JMS PRDIG
 JMS CALL
 DLOAD
 DREM
 TAD DACC+1
 JMS PRDIG
 JMS CALL
 DLOAD
 DACSV
 JMP I PRDACC
 
 
DACSV, 0
 0
D1E6, 0364
 1100
D1E5, 0030
 3240
D1E4, 2
 3420
D1E3, 0
 1750
D1E2, 0
 144
D1E1, 0
 12
 
/PRINT AC, AC=AC
PRAC, 0
 DCA SAC
 TAD SAC
 JMS CALL
 DIV
 D1E3+1
 JMS PRDIG
 TAD REM
 JMS CALL
 DIV
 D1E2+1
 JMS PRDIG
 TAD REM
 JMS CALL
 DIV
 D1E1+1
 JMS PRDIG
 TAD REM
 JMS PRDIG
 TAD SAC
 JMP I PRAC
SAC, 0
/PRINT DOT AC=AC
PRDOT, 0
 DCA SAC
 TAD KDOT
 JMS TYPE
 TAD SAC
 JMP I PRDOT
 
/----------------------
/PZERO TEST LIBRARY
 *PZERO 
TYPE, 0 / AC=0
 TSF
 JMP .-1
 TLS
 CLA
 JMP I TYPE
 
CRLF, 0 / AC=0
 CLA
 TAD KCR
 JMS TYPE
 TAD KLF
 JMS TYPE
 JMP I CRLF
/SOUND BELL AND HALT WITH ADDR OF BAD TEST IN AC 
ERROR, 0
 CLA
 TAD KBELL
 JMS TYPE
 CLA CMA
 TAD ERROR
 HLT
/PRINT DOT, COUNT ERROR 
PASS, 0
 CLA
 TAD KDOT
 JMS TYPE
 ISZ TESTS
 JMP I PASS
/TESTS COMPLETE, PRINT ZERO AND HALT WITH # OF TESTS IN AC. 
TSTDUN,
 JMS CRLF
 TAD KZERO
 JMS TYPE
 JMS CRLF
 TAD TESTS
 HLT
 
/CALL SUBROUTINE
CALL, 0
 DCA AC
 TAD I CALL
 DCA CALLEE
 TAD CALL
 IAC
 DCA I CALLEE
 ISZ CALLEE
 TAD AC
 JMP I CALLEE
AC, 0
CALLEE, 0
TESTS, 0 
KZERO, 260
KBELL, 207
KCR, 215
KLF, 212
KDOT, 256
PZERO=.
~
$

AltStyle によって変換されたページ (->オリジナル) /