1
0
mirror of https://github.com/DoctorWkt/pdp7-unix.git synced 2026-04-19 09:19:49 +00:00

Merge pull request #11 from philbudne/master

update, extend asm_syntax.txt (intro to PDP-7 programming)
as7 fixes
fix some system, trysys typos
add some system comments (tab separated)
This commit is contained in:
philbudne
2016-02-29 14:31:28 -05:00
8 changed files with 479 additions and 240 deletions

View File

@@ -1,42 +1,273 @@
Basics of the Unix assembler syntax
Basics of the "Unix v0" 'as' assembler syntax and PDP-7 coding:
Lines starting with " are comment lines
ASSEMBLER SYNTAX:
=================
Anything from " to end of line is a comment.
. is the location counter, where the next code or constant will be placed
.. is the relocation counter
.. is the relocation counter (not currently handled)
t = 0 Sets a built-in variable to this value without generating any machine code.
Variables so far are . .. and t. Not sure what t's purpose is yet.
Symbol names start with a letter, and can contain letters and "."
The dump of label values in "scans/sysmap" appears to show truncation
after 8 characters (not currently enforced by the "as7" Perl script).
orig: is a label. It looks like labels can have dots in them.
Dots seem to separate structures and fields, e.g. u.base
but there are some symbols that start with dots, e.g. .seek
symbol = expression
jms copy; 10; u.rg+2; 6 Semicolons separate instructions and
word expressions that follow the instruction
Sets a built-in variable to this value without generating any
machine code.
Some expressions are 0:0, not sure about this.
Is it two 9-bit fields? No idea
as7 enters machine instructions and the indirect bit (i)
into the variable table.
jmp 1f Jump back to the closest 1: label
jmp 1b Jump forward to the closest 1: label
label: is a label.
Some lines are indented differently to others e.g.
single digit decimal numbers can be used as "local" labels,
which are referenced with Nf and Nb:
jms betwen; o10000; o17762
jms error
dac .+1
jmp 1f Jump back to the closest 1: label
jmp 1b Jump forward to the closest 1: label
I think this is to indicate the skip logic, but it plays no part in the assembly syntax.
-1 occurs instead of instructions, so it looks like the assembler allows
literal constants at any time.
multiple words can be entered on one line, separated by semi-colon.
String literals seem to be two characters decorated with < > characters, e.g.
"ab" is <a>b. I'm guessing these are placed as pairs in one word. The syntax
is confusing, because I've also seen <no> ; 040 ; <fi> ; <le>; <s 012 which
appears to mean "no files\n".
Looks like a line can contain multiple labels, e.g. o12: d10: 10
A line can contain multiple labels, e.g. o12: d10: 10
Numeric literals: 0xx are octal values, [1-9]xx are decimal values.
CONVENTIONS:
============
Because there is no immediate mode (and "as" lacks the literal syntax
found in DEC assemblers), there is a convention for literal (manifest)
constants:
dNNN indicates the location of a constant for decimal NNN.
oOOO for octal constant OOO.
dmN (decimal minus) indicates a constant for decimal -N.
High order bits of the "law" (load ac with word) instruction are all
ones, so "-1" as an instruction loads -1 into AC.
Subroutine arguments are often located in the words following the
call. When an argument is a variable, sometimes a "0:" local label is
used to tag the location:
jms namei; 0:0
Indentation is (sometimes) used to indicate an instruction
or subroutine that may skip:
jms betwen; o10000; o17762
jms error
dac .+1
NOTE! the "betwen" (between) routine (which appears in multiple
places) takes ADDRESSES of values (which can be literals, as above),
or could be symbol names for variables.
I haven't seen any cases where "skip chains" (sequences of skip
instructions) are indicated by multiple indents.
There is no hardware stack, so the only form of subroutine call (jms)
is "impure" and leaves the (updated) program counter in the first word
of the subroutine, and execution starts on the second word.
Routines which fetch arguments from after the jms instruction may do:
routine: 0
lac routine i " pick up argument after jms
dac temp " store in temp location
isz routine " increment return PC to skip argument
Routines which (optionally) skip (eg; on success) may use "isz
routine" to (conditionally) increment the return PC.
In the system code t is used as a (current) offset into a block of
temporary location(s) for a routine or group of routines , at label
9f, so you'll see:
this:
0
lac 9f+t+N
....
t=t+M
....
that:
0
lac 9f+t+I
....
t=t+J
.....
.....
.....
9f:
.=.+t
HARDWARE:
=========
References:
http://simh.trailing-edge.com/docs/architecture18b.pdf
http://www.soemtron.org/pdp7.html
http://www.soemtron.org/pdp7history.html
NOTE! All opcodes are defined in "sop.s" (and in "as7") and are
commented as below.
The PDP-4/7/9/15 family started as a simplified version of the PDP-1,
(itself an evolution of the TX-0, designed at MIT Lincoln Labs). The
PDP-4 wasn't very successful (offering 5/8 PDP-1 performance at 1/2
the price).
The PDP-7 was originally concieved of as a repackaging of the PDP-1,
but DEC had a built up more system software for the PDP-4 than for the
PDP-1 (including a FORTRAN II compiler!), so they continued with the
new architecture. See Bob's paper (first link above) for more detail.
The Living Computing Museum in Seattle has a running PDP-7, and
intends to build simulated disk hardware to enable running "Unix v0".
Words are 18 bits, words are typically represented as six octal digits.
There is one 18 bit accumulator, called "AC".
The "LINK" register is a 1-bit register that is included in shifts.
The Extended Arithmetic Element (EAE) option adds an "MQ" register which
has limited uses.
Bit numbering is "big endian": bit zero is 400000.
There are no "addressing" modes: memory referencing instructions,
(opcodes 0 thru 060) decode the low 14 bits as:
I (020000) "indirect" bit
Y (017777) 13-bit address field.
When the "I" bit is clear, the "Y" field is the address of the operand.
When the "I" bit is set, the word referenced by the contents of the
word addressed by "Y" field is used as the operand.
Unlike the PDP-1 (and PDP-6/10) indirect references are not
multi-level, and end after the first indirect fetch.
The PDP-7 found by Ken Thompson apparently did not have extended
memory or memory protection options and could directly reference all
8K words of memory (at all times).
Unix system code appears to have resided in the low 4K of memory, and
a single user program in the high 4K. The system had a "fast"
fixed-head disk, but indirect memory access locks out DMA by the disk
controller, and indirect access cannot be used while disk transfers
are active (the interrupt service routines for clock and TTY, are
coded without using indirect!). Because of this, disk access cannot
be "overlapped" with user code, but users are free to use "indirect"
freely.
When locations 010 through 017 of memory are referenced *INDIRECTLY*,
they auto-increment after access, and can be used as "index
registers".
System calls are made using the "CAL" (call) instruction (octal 00).
The Y field indicates the system call number. "CAL" behaves like "jms
020". "CAL" with the "I" bit set behaves like "jms 20 i". The system
handler appears to deal with both (although the non-indirect form
destroys the contents of location 020), and has a longer code path, so
it seems likely that "sys=cal i" became the preferred system call at
some point?
The kernel preserves the contents of the users' AC and MQ registers
and locations 8-15 in the "userdata" block (symbols u.ac, u.mq and
u.rq). Location u.rq+8 is the saved PC of the last system call.
The "save" system call (and any undefined system call) write high 4K
of memory and the "userdata" block (to fd 1?????)
The system did not have advanced interrupt processing hardware, so all
"priority interrupts" (also known in the past as "address break"
processing) dispatched as if a "jms 0" was executed, and are processed
in the "pibreak" routine.
Summary of memory instructions (from annotated sop.s):
dac = 0040000 " MEM: deposit AC
jms = 0100000 " MEM: jump to subroutine
dzm = 0140000 " MEM: deposit zero to memory
lac = 0200000 " MEM: load AC
xor = 0240000 " MEM: XOR with AC
add = 0300000 " MEM: one's complement add
tad = 0340000 " MEM: two's complement add
xct = 0400000 " MEM: execute
isz = 0440000 " MEM: increment and skip if zero
and = 0500000 " MEM: AND with memory
sad = 0540000 " MEM: skip if AC different
jmp = 0600000 " MEM: jump
Other instruction groups do not interpret the "I" bit, and all begin
with '7' in the high three bits.
I/O Transfer (or IOT) have 111000 (070) in the high six bits. the next
six bits (two octal digits) indicate the device number.
Operate (OPR) which is "microcoded" with low order bits indicating
"micro operations" to be performed have 11110 (074) in the high four
bits:
cma = 0740001 " OPR: complement AC
ral = 0740010 " OPR: rotate AC left
rar = 0740020 " OPR: rotate AC right
hlt = 0740040 " OPR: halt
sma = 0740100 " OPR: skip on minus AC
sza = 0740200 " OPR: skip on zero AC
snl = 0740400 " OPR: skip on non-zero link
skp = 0741000 " OPR: skip unconditionally
sna = 0741200 " OPR: skip on negative AC
szl = 0741400 " OPR: skip on zero link
rtl = 0742010 " OPR: rotate two left
rtr = 0742020 " OPR: rotate two right
cll = 0744000 " OPR: clear link
rcl = 0744010 " OPR: clear link, rotate left
rcr = 0744020 " OPR: clear link, rotate right
cla = 0750000 " OPR: clear AC
las = 0750004 " OPR: load AC from switches
With some limitations, OPR instructions can be OR-ed together.
(Ordering of operations is determined by the hardware, not by their
order in the source!!):
sna cla " skip on negative AC, clear AC
sna spa " skip on negative or positive AC
sna ral " skip on negative AC, rotate AC left
cla cll sza " skip on AC zero, clear AC, clear LINK
The last "operate" instruction is not microcoded:
law = 0760000 " OPR: load accumulator with (instr)
and as noted above, is often coded directly as an immediate negative constant.
Unix v0 depends on the EAE option, which uses instructions with 064 in
the top four bits, and is used for multiply, divide, and the 18-bit MQ
register:
lrs = 0640500 " EAE: long right shift
lrss = 0660500 " EAE: long right shift, signed
lls = 0640600 " EAE: long left shift
llss = 0660600 " EAE: long left shift, signed
als = 0640700 " EAE: AC left shift
alss = 0660700 " EAE: AC left shift, signed
mul = 0653323 " EAE: multiply
idiv = 0653323 " EAE: integer divide
lacq = 0641002 " EAE: load AC with MQ
clq = 0650000 " EAE: clear MQ
omq = 0650002 " EAE: OR MQ into AC
cmq = 0650004 " EAE: complement MQ
lmq = 0652000 " EAE: load MQ from AC

View File

@@ -53,7 +53,7 @@ orig:
jms halt
okexit:
dzm: u.ac
dzm u.ac
sysexit:
ion
lac .savblk
@@ -72,7 +72,7 @@ sysexit:
dac 9
lac u.rq
dac 8
lac u.rq
lac u.mq
lmq
lac u.ac
jmp u.rq+8 i

View File

@@ -32,7 +32,7 @@ error:
1: 077012
a.out:
<a.>;<ou><t 040; 040040
<a.>;<ou>;<t 040; 040040
t1: 0
t2: 0
c1: 0

View File

@@ -21,61 +21,61 @@ orig:
jmp 1f+1
1f
1: 0
iof
dac u.ac
iof " interrupts off
dac u.ac " save user AC
lacq
dac u.mq
dac u.mq " save user MQ
lac 8
dac u.rq
dac u.rq " save user auto-index location 8
lac 9
dac u.rq+1
jms copy; 10; u.rq+2; 6
lac 1b
dac u.rq+8
dac u.rq+1 " save user auto-index location 9
jms copy; 10; u.rq+2; 6 " save user auto-index locations 10-15
lac 1b " load user PC after system call
dac u.rq+8 " save user PC
-1 " load -1
dac .savblk " set "save" flag (cleared by disk I/O?)
dac .insys " set "in system" flag
lac uquant " load user quantum count
jms betwen; d0; maxquant " check if between 0 & maxquant??
jms swap " no: swap processes
ion " interrupts on
-1
dac .savblk
dac .insys
lac uquant
jms betwen; d0; maxquant
jms swap
ion
-1
tad u.rq+8
jms laci
jms betwen; o20001; swn
jmp badcal
tad swp
dac .+1
jmp .. i
tad u.rq+8 " get address of system call
jms laci " load AC indirect??
jms betwen; o20001; swn " range check
jmp badcal " bad system call
tad swp " add system call table base
dac .+1 " save as next instruction
jmp .. i " dispatch system call
. = orig+0100
jmp coldentry
jms halt
okexit:
dzm: u.ac
sysexit:
ion
lac .savblk
sza
jmp 1f
jms copy; sysdata; dskbuf; 64
dzm u.ac " 'OK' system call exit: clear user AC
sysexit: " common system call exit code
ion " enable interrupts
lac .savblk " load "save" flag
sza " is zero (cleared by disk I/O)?
jmp 1f " no: no disk I/O done?
jms copy; sysdata; dskbuf; 64 " copy system data to disk buffer
cla
jms dskio; 07000
jms dskio; 07000 " save to disk?
1:
dzm .insys
dzm .insys " clear "in system call" flag
jms chkint
skp
jmp .save
jms copy; u.rq+2; 10; 6
lac u.rq+1
jmp .save " dump core??
jms copy; u.rq+2; 10; 6 " restore auto-index locations 10-15
lac u.rq+1 " restore auto-index location 9
dac 9
lac u.rq
lac u.rq " restore auto-index location 8
dac 8
lac u.rq
lac u.mq " restore MQ register
lmq
lac u.ac
jmp u.rq+8 i
lac u.ac " restore AC register
jmp u.rq+8 i " return to user
swap: 0
ion
@@ -129,21 +129,21 @@ swap: 0
jmp swap i
t = t+1
swp:
jmp .
swp: " system call dispatch table
jmp . " base instruction
.save; .getuid; .open; .read; .write; .creat; .seek; .tell
.close; .link; .unlink; .setuid; .rename; .exit; .time; .intrp
.chdir; .chmod; .chown; badcal; .sysloc; badcal; .capt; .rele
.status; badcal; .smes; .rmes; .fork
swn:
.-swp-1 i
.-swp-1 i " count of system calls, plus indirect!
.intrp:
lac u.ac
dac u.intflg
jmp okexit
.sysloc:
.sysloc: " "sysloc": syscall to return system addresses
lac u.ac
and o17777
jms betwen; d1; locn
@@ -154,7 +154,7 @@ swn:
dac u.ac
jmp sysexit
locsw:
locsw: " table of system data structures for "sysloc" call
lac .
iget; inode; userdata; sysdata; copy; copyz; betwen; dskrd
dskwr; dskbuf; dpdata; namei; pbsflgs; alloc; free; dspdata

View File

@@ -53,9 +53,9 @@
jms iput
jmp okexit
.getuid:
.getuid: " getuid system call
lac u.uid
dac u.ac
dac u.ac " return u.uid in user AC
jmp sysexit
.seek:
@@ -146,12 +146,12 @@
jms iput
jmp sysexit
.setuid:
lac u.uid
sma
jms error
lac u.ac
dac u.uid
.setuid: " setuid system call
lac u.uid " load current user id
sma " negative?
jms error " no: error!!
lac u.ac " load user AC
dac u.uid " save as new uid
jmp sysexit
.rename:
@@ -168,11 +168,14 @@
jms copy; 1:0; d.name; 4
jmp okexit
" time system call returns line (mains) frequency ticks since boot?
" note: returns uptime!?
" at 60Hz, 36 bits would last 36+ years!
.time:
lac s.tim
dac u.ac
lac s.tim+1
dac u.mq
lac s.tim " load high order bits
dac u.ac " return in AC
lac s.tim+1 " load low order bits
dac u.mq " return in MQ
jmp sysexit
.chdir:

View File

@@ -1,7 +1,7 @@
"** 01-s1.pdf page 41
" s7
pibreak:
pibreak: " priority interrupt processing "chain"
dac .ac "** CROSSED OUT....
dpsf
@@ -25,15 +25,15 @@ pibreak:
dac dpwrite
jmp piret "** END OF CROSSOUT
1: clsf
jmp 1f
1: clsf " clock overflow (line frequency ticks)?
jmp 1f " no
lpb
dac pbsflgs
isz s.tim+1
skp
isz s.tim
isz uquant
lpb " load display push buttons
dac pbsflgs " save
isz s.tim+1 " increment low order tick count
skp " no overflow, skip second increment
isz s.tim " low order overflowed, increment high order count
isz uquant " increment user quantum counter
"** written: ttydelay -> ttyd1
"** written: ttyrestart -> ttyres1
cnop:

View File

@@ -1,97 +1,95 @@
"** 01-s1.pdf page 62
" sop
dac = 0040000
jms = 0100000
dzm = 0140000
lac = 0200000
xor = 0240000
add = 0300000
tad = 0340000
xct = 0400000
isz = 0440000
and = 0500000
sad = 0540000
jmp = 0600000
nop = 0740000
i = 020000
law = 0760000
cma = 0740001
las = 0750004
ral = 0740010
rar = 0740020
hlt = 0740040
sma = 0740100
sza = 0740200
snl = 0740400
skp = 0741000
sna = 0741200
szl = 0741400
rtl = 0742010
rtr = 0742020
cil = 0744000
rcl = 0744010
rcr = 0744020
cia = 0750000
lrs = 0640500
lrss = 0660500
lls = 0640600
llss = 0660600
als = 0640700
alss = 0660700
mul = 0653323
idiv = 0653323
lacq = 0641002
clq = 0650000
omq = 0650002
cmq = 0650004
lmq = 0652000
dac = 0040000 " MEM: deposit AC
jms = 0100000 " MEM: jump to subroutine
dzm = 0140000 " MEM: deposit zero to memory
lac = 0200000 " MEM: load AC
xor = 0240000 " MEM: XOR with AC
add = 0300000 " MEM: one's complement add
tad = 0340000 " MEM: two's complement add
xct = 0400000 " MEM: execute
isz = 0440000 " MEM: increment and skip if zero
and = 0500000 " MEM: AND
sad = 0540000 " MEM: skip if AC different
jmp = 0600000 " MEM: jump
nop = 0740000 " OPR: no-op
i = 020000 " indirect
law = 0760000 " OPR: load accumulator with (instr)
cma = 0740001 " OPR: complement AC
las = 0750004 " OPR: load AC from switches
ral = 0740010 " OPR: rotate AC left
rar = 0740020 " OPR: rotate AC right
hlt = 0740040 " OPR: halt
sma = 0740100 " OPR: skip on minus AC
sza = 0740200 " OPR: skip on zero AC
snl = 0740400 " OPR: skip on non-zero link
skp = 0741000 " OPR: skip unconditionally
sna = 0741200 " OPR: skip on negative AC
szl = 0741400 " OPR: skip on zero link
rtl = 0742010 " OPR: rotate two left
rtr = 0742020 " OPR: rotate two right
cll = 0744000 " OPR: clear link
rcl = 0744010 " OPR: clear link, rotate left
rcr = 0744020 " OPR: clear link, rotate right
cla = 0750000 " OPR: clear AC
lrs = 0640500 " EAE: long right shift
lrss = 0660500 " EAE: long right shift, signed
lls = 0640600 " EAE: long left shift
llss = 0660600 " EAE: long left shift, signed
als = 0640700 " EAE: AC left shift
alss = 0660700 " EAE: AC left shift, signed
mul = 0653323 " EAE: multiply
idiv = 0653323 " EAE: integer divide
lacq = 0641002 " EAE: load AC with MQ
clq = 0650000 " EAE: clear MQ
omq = 0650002 " EAE: OR MQ into AC
cmq = 0650004 " EAE: complement MQ
lmq = 0652000 " EAE: load MQ from AC
dscs = 0707141
dslw = 0707124
dslm = 0707142
dsld = 0707104
dsls = 0707144
dssf = 0707121
dsrs = 0707132
iof = 0700002
ion = 0700042
caf = 0703302
clon = 0700044
clsf = 0700001
dscs = 0707141 " DSK: clear status register
dslw = 0707124 " DSK: clear and load WC from AC
dslm = 0707142 " DSK: clear and load MAC from AC
dsld = 0707104 " DSK: clear and load TA and SA from AC
dsls = 0707144 " DSK: load status
dssf = 0707121 " DSK: skip on flags
dsrs = 0707132 " DSK: read status register
iof = 0700002 " PIC: interrupts off
ion = 0700042 " PIC: interrupts on
caf = 0703302 " CPU: clear all flags
clon = 0700044 " CLK: clear flag, enable
clsf = 0700001 " CLK: skip if overflow
"** 01-s1.pdf page 63
clof = 0700004
ksf = 0700301
krb = 0700312
tsf = 0700401
tcf = 0700402
tls = 0700406
sck = 0704301
cck = 0704304
lck = 0704312
rsf = 0700101
rsa = 0700104
rrb = 0700112
psf = 0700201
pcf = 0700202
psa = 0700204
cdf = 0700501
lds = 0701052
lda = 0701012
wcga = 0704206
raef = 0700742
rlpd = 0700723
beg = 0700547
spb = 0704401
cpb = 0704404
lpb = 0704412
wbl = 0704424
dprs = 0704752
dpsf = 0704741
dpcf = 0704761
dprc = 0704712
crsf = 0706701
crrb = 0706712
clof = 0700004 " CLK: clear flag, disable
ksf = 0700301 " KBD: skip if flag set
krb = 0700312 " KBD: read buffer
tsf = 0700401 " TTY: skip if flag set
tcf = 0700402 " TTY: clear flag
tls = 0700406 " TTY: load buffer, select
sck = 0704301 " S-2: skip on console keyboard
cck = 0704304 " S-2: clear console keyboard
lck = 0704312 " S-2: load console keyboard
rsf = 0700101 " PTR: skip if flag set
rsa = 0700104 " PTR: select alphanumeric mode
rrb = 0700112 " PTR: clear flag, or read buffer
psf = 0700201 " PTP: skip if flag set
pcf = 0700202 " PTP: clear flag
psa = 0700204 " PTP: alphanumeric mode
cdf = 0700501 " ???
lds = 0701052 " S-2: load display status
lda = 0701012 " S-2: load display address
wcga = 0704206 " S-2: ???
raef = 0700742 " S-2: resume after edges flag
rlpd = 0700723 " S-2: resume after light pen stop, disabled
beg = 0700547 " S-2: begin
spb = 0704401 " S-2: skip on push button flag
cpb = 0704404 " S-2: clear push button flag
lpb = 0704412 " S-2: load push buttons
wbl = 0704424 " S-2: write button lights
dprs = 0704752 " dataphone: read status
dpsf = 0704741 " dataphone: skip on flag
dpcf = 0704761 " dataphone: clear flag
dprc = 0704712 " dataphone: read character
crsf = 0706701 " CDR: skip if ready
crrb = 0706712 " CDR: read buffer

125
tools/as7
View File

@@ -4,7 +4,7 @@
# and convert them into PDP-7 machine code
#
# (c) 2016 Warren Toomey, GPL3
# Tweaked by Phil Budne (expression parsing, "list" format)
# Tweaked by Phil Budne (line, expression parsing, "list" format)
#
use strict;
use warnings;
@@ -25,7 +25,6 @@ my $origline; # The original current input line of code
my $line; # line being parsed
my $stage = 1; # Pass one or pass two
my $errors = 0; # set to non-zero on error
my %Undef; # undefined symbols: only complain once
my $line_error = ' ';
my $file; # current file name
my $lineno; # current line number
@@ -149,7 +148,7 @@ usage() if ( @ARGV < 1 );
rsb => 0700144, # select PTR in binary mode
psf => 0700201, # skip if PTP flag set
pcf => 0700202, # clear PTP clag
pcf => 0700202, # clear PTP flag
psa => 0700204, # punch PTP in alphanumeric mode
psb => 0700244, # punch PTP in binary mode
@@ -157,7 +156,7 @@ usage() if ( @ARGV < 1 );
krb => 0700312, # read KBD buffer
iors => 0700314, # input/output read status
tsf => 0700401, # if if TTY output flag set
tsf => 0700401, # skip if if TTY output flag set
tcf => 0700402, # clear TTY output flag
tls => 0700406, # load TTY output buffer and select
@@ -272,8 +271,9 @@ sub parse_file {
close($IN);
}
# process a label and set its value to the location counter (only called on pass 1)
# (if called on pass 2, should check if values are identical)
# process a label and set its value to the location counter
# only called on pass 1;
# if called on pass 2, should check if values are identical
sub process_label {
my $label = shift;
@@ -294,25 +294,21 @@ sub process_label {
}
# Blame Phil for this....
# parses global $line based on prefixes
# (nibbling of a bit at a time)
# parses global $line based on prefixes, nibbling of a bit at a time
# (: and ; can appear in char literals)
# handles multiple ';' separated words per line
sub parse_line {
$line_error = ' ';
# Lose any leading whitespace
$line =~ s{^\s*}{};
while (1) {
# Lose any leading whitespace
$line =~ s{^\s*}{};
$line_error = ' '; # clear listing error indicator
return if ($line eq '' || $line =~ m{^"}); # empty or comment: quit
print "parse_line: '$line'\n" if ($debug);
return if ($line eq '');
if ($line =~ m{^"}) { # remainder of line is comment
return;
}
if ($line =~ s{^([a-z0-9\.]+):}{}) { # label
while ($line =~ s{^([a-z0-9\.]+):\s*}{}) { # labels
my $label = $1;
# First pass: parse the labels
@@ -321,44 +317,40 @@ sub parse_line {
process_label($1);
}
}
else {
my $lhs = undef;
if ( $line =~ s{^(\S+)\s*=}{}) { # assignment
$lhs = $1;
}
if ( $line =~ s{^(\S+)\s*=}{}) { # assignment
my $lhs = $1;
my $word = parse_expression();
if ($lhs) {
printf( "Setting variable %s to 0%o\n", $lhs, $word ) if ($debug);
$Var{$lhs} = $word;
printf("\t%06o %s\n", $word, $line_error) if ($stage == 2 && $format eq 'list');
}
else { # bare expression
# Get its value on pass two and save to memory
# Also save the input line that altered memory
if ( $stage == 2 ) {
my $location = $Var{'.'};
$Mem[$location] = $word;
$Mline[$location] = $origline;
$origline = '';
if ($format eq 'list') {
printf( "%06o: %06o %s\n", $location, $word, $line_error);
}
printf( "Setting variable %s to 0%o\n", $lhs, $word ) if ($debug);
$Var{$lhs} = $word;
printf("\t%06o %s\n", $word, $line_error) if ($stage == 2 && $format eq 'list');
}
else { # bare expression
# Get its value on pass two and save to memory
# Also save the input line that altered memory
my $word = parse_expression();
if ( $stage == 2 ) {
my $location = $Var{'.'};
$Mem[$location] = $word;
$Mline[$location] = $origline;
$origline = '';
if ($format eq 'list') {
printf( "%06o: %06o %s\n", $location, $word, $line_error);
}
# Move up to the next location in both passes
$Var{'.'}++;
} # expr
} # assignment or expression
}
# Move up to the next location in both passes
$Var{'.'}++;
} # expr
# eat trailing whitespace and ";", if any
$line =~ s{^\s*}{};
$line =~ s{^;}{};
$line =~ s{^\s*;?}{};
} # while
}
# Blame Phil for this bit too...
# Parse an expression off $line and return a PDP-7 word
# as a series of whitespace separated "syllables"
# and adds them together.
# ORed, added, or subtracted
sub parse_expression {
my $word = 0;
@@ -366,23 +358,22 @@ sub parse_expression {
while (1) {
my $syllable = 0;
my $sign = 1;
my $op = '|';
$line =~ s{^\s+}{};
print " '$line'\n" if ($debug);
if ($line eq '' || $line =~ m{^[";]}) { # EOL ; and " terminate expr
printf("\tparse_expression => %#o\n", $word) if ($debug);
return $word;
}
print " '$line'\n" if ($debug);
if ($line =~ s{^-}{}) {
# leading '-' negates upcomming syllable.
$sign = -$sign;
$op = '-';
}
else {
# ignore leading '+'
$line =~ s{^\+}{};
elsif ($line =~ s{^\+}{}) {
$op = '+';
}
if ($line =~ s{^<(.)}{}) { # <char
@@ -406,8 +397,7 @@ sub parse_expression {
printf("\tlbl: %s: %#o\n", $sym, $syllable) if ($debug);
}
elsif ($stage == 2) {
err('U', "$sym not defined") unless (defined $Undef{$sym});
$Undef{$sym} = 1; # only complain once
err('U', "$sym not defined")
} # pass 2
} # symbol
elsif ( $line =~ s{^(\d+)([fb])}{} ) { # relative label
@@ -421,14 +411,31 @@ sub parse_expression {
else {
$syllable = $value + 0;
}
$syllable &= 0777777;
}
else {
# From the BSD fortune file:
# Ken Thompson has an automobile which he helped design.
# Unlike most automobiles, it has neither speedometer,
# nor gas gauge, nor any of the numerous idiot lights
# which plague the modern driver. Rather, if the driver
# makes any mistake, a giant "?" lights up in the center
# of the dashboard. "The experienced driver",
# he says, "will usually know what's wrong.
err('?', "huh? '$line'");
$line = '';
$line = ''; # abort processing
return $word;
}
$syllable = ($syllable * $sign) & 0777777;
$word = ($word + $syllable) & 0777777;
if ($op eq '+') {
$word += $syllable;
}
elsif ($op eq '-') {
$word -= $syllable;
}
else {
$word |= $syllable;
}
$word &= 0777777;
printf("\tsyllable: %#o word: %#o\n", $syllable, $word) if ($debug);
}
}