!   Intel 8086 IMP77 compiler second pass

! Copyright 2002 NB Information Limited.  From an original
! version probably Copyright The University of Edinburgh and
! various contributions Copyright many other individuals, but
! most particularly Copyright 1977-1980 Peter Robertson

%begin

!SIZE CONSTANTS
%constinteger  max vars = 600
%constinteger  max stack = 16
%constinteger	max labs = 50
%constinteger  max level = 16
%constinteger  Max GP = 100

! SOME WEE ENVIRONMENTAL THINGS
%conststring(8)  program ep = "__impmain";     ! Main program external name
%conststring(5)  system prefix = "_imp_";     ! prefixed to %system routine idents

! I/O file handles
%constinteger icode   = 1
%constinteger source  = 2
%constinteger report  = 0
%constinteger objout  = 1
%constinteger listout = 2

!CONTROL BITS
%constinteger  check capacity = 1
%constinteger  check unass    = 2
%constinteger  check array    = 4
%constinteger  check bits     = check array;  ! The only one that does anything so far

!REGISTERS - basic register number = actual value + 1
%constinteger  AX = 1
%constinteger  CX = 2
%constinteger  DX = 3
%constinteger  BX = 4
%constinteger  SP = 5
%constinteger  BP = 6
%constinteger  SI = 7
%constinteger  DI = 8

! Floating point coprocessor stack registers
%constinteger  FR0	= 9
!%constinteger  FR1	= 10
!%constinteger  FR2	= 11
!%constinteger  FR3	= 12
!%constinteger  FR4	= 13
!%constinteger  FR5	= 14
!%constinteger  FR6	= 15
%constinteger  FR7	= 16

! 8 bit registers - actual value + 17
%constinteger	AL	= 17
%constinteger	CL	= 18
%constinteger	DL	= 19
%constinteger	BL	= 20
%constinteger	AH	= 21
%constinteger	CH	= 22
%constinteger	DH	= 23
%constinteger	BH	= 24


! Pseudo Registers
%constinteger  any   = 25  ; ! Truly any 16 bit register
%constinteger  anyg  = 26  ; ! A "General Purpose" byte accessible register (AX, BX, CX, DX)
%constinteger  anyp  = 27  ; ! A 16 bit pointing register (BX, SI, DI)
%constinteger  anyf  = 28  ; ! Generally means the top of the 8087 stack

!DATA FORMS

! EXTERNAL
%constinteger  recordformat   = 4
%constinteger  switch         = 6
%constinteger  array          = 11
%constinteger  arrayname      = 12
%constinteger  namearray      = 13
%constinteger  namearrayname  = 14

! INTERNAL
%constinteger  constant    = 0
%constinteger  v in r      = 1
%constinteger  av in r     = 2
%constinteger  a in r      = 3
%constinteger  v in s      = 4
%constinteger  av in s     = 5
%constinteger  a in s      = 6
%constinteger  v in rec    = 7
%constinteger  av in rec   = 8
%constinteger  a in rec    = 9
%constinteger  pgm label   = 10

!DATA TYPES
%constinteger  general  = 0
%constinteger  integer  = 1
%constinteger  real     = 2
%constinteger  string   = 3
%constinteger  record   = 4
! Private internal derived types
%constinteger  byte     = 5
%constinteger  lreal    = 6

! SIZE OF EACH OF THOSE TYPES IN BYTES
%constbyteintegerarray  vsize(0:6) = 0,2,4,0,0,1,8

! GENERIC STORE ALIGNMENT - ASSUME 8086
%constinteger  align = 1
%constinteger  wordsize = 2;  ! in bytes

!OWN INFO
%constinteger  own = 1
%constinteger  con = 2
%constinteger  external = 3
%constinteger  system = 4
%constinteger  dynamic = 5
%constinteger  primrt = 6
%constinteger  permrt = 7

! Define type codes known externally (to pass 3 and user):
%constbyteintegerarray  gen map(general:lreal) =
      0,      1,     2,      3,     4,       6,     8

! PERM ROUTINE INDEXES
%constinteger  iexp=1;     ! Integer Exponent
%constinteger  fexp=2;     ! floating exponent
%constinteger  smove=3;    ! string copy (length checked)
%constinteger  sjam=4;     ! string copy (whatever fits)
%constinteger  sconc=5;    ! string concatenate (length checked)
%constinteger	sjconc=6;	! concatenate whatever fits
%constinteger  sresln=7;   ! string resolution
%constinteger  scomp=8;    ! string compare
%constinteger  aref=9;     ! array access
%constinteger  adef=10;    ! array definition
%constinteger  signal=11;  ! %signal
%constinteger	lastperm=signal

! and the corresponding linkage names for the perms
%const %string(12)%array permname(1:lastperm)=
"__impiexp",
"__impfexp",
"__impstrcpy",
"__impstrjam",
"__impstrcat",
"__impstrjcat",
"__impstrres",
"__impstrcmp",
"__imparef",
"__impadef",
"__impsignal"

! COMPILER OPERATIONS (not to be confused with OpCodes)
%constinteger  op add = 1
%constinteger  op sub = 2
%constinteger  op mul = 3
%constinteger  op div = 4
%constinteger  op conc = 5
%constinteger  op and = 6
%constinteger  op or  = 7
%constinteger  op xor = 8
%constinteger  op lsh = 9
%constinteger  op rsh = 10
%constinteger  op rem = 11
%constinteger  op exp = 12
%constinteger  op rexp = 13
%constinteger  op rdiv = 14
%constinteger  op not = 15
%constinteger  op neg = 16
%constinteger  op abs = 17
%constinteger  unaries = 15

! opcode indexes...

! simple (no operand) ones first
%constinteger NOP	= 0
%constinteger CWD = 1
%constinteger RET = 2
%constinteger SAHF = 3

! simple unary math functions
%constinteger DEC = 4
%constinteger INC = 5
%constinteger NEG = 6
%constinteger NOT = 7

! simple unary moves
%constinteger POP = 8
%constinteger PUSH = 9

! two operand moves
%constinteger LEA = 10
%constinteger MOV = 11
%constinteger XCHG = 12

! simple two operand math functions
%constinteger ADC = 13
%constinteger ADD = 14
%constinteger AND = 15
%constinteger CMP = 16
%constinteger OR = 17
%constinteger SUB = 18
%constinteger XOR = 19
! slightly more complicated two operand math
%constinteger SHL = 20
%constinteger SHR = 21
%constinteger IDIV = 22
%constinteger IMUL = 23

! calls and jumps
%constinteger CALL = 24
%constinteger JE  = 25
%constinteger JNE = 26
%constinteger JG  = 27
%constinteger JGE = 28
%constinteger JL  = 29
%constinteger JLE = 30
%constinteger JA  = 31
%constinteger JAE = 32
%constinteger JB  = 33
%constinteger JBE = 34
%constinteger JMP = 35

! Floating point instructions - note that these map directly onto
! 8087 sequences, unlike the generic MOV, ADD style of the base
! operations for the 8086
%constinteger	FILD	= 36
%constinteger	FLDD	= 37
%constinteger	FLDQ	= 38
%constinteger	FSTI	= 39
%constinteger	FSTD	= 40
%constinteger	FSTQ	= 41
%constinteger	FADD	= 42
%constinteger	FSUB	= 43
%constinteger	FSUBR	= 44
%constinteger	FMUL	= 45
%constinteger	FDIV	= 46
%constinteger	FDIVR	= 47
%constinteger	FCMP	= 48
%constinteger	FCHS	= 49
%constinteger	FABS	= 50
! Special floating point things
%constinteger  FSTSW = 51
%constinteger  FLDZ  = 52
%constinteger  FLDPI = 53

! modifiers to memory base for accessing global memory
%constinteger DATA   = 16_10
%constinteger COT		= 16_20
%constinteger BSS		= 16_30
%constinteger DISPLAY = 16_40
%constinteger EXT		= 16_50
%constinteger SWT    = 16_60
%constinteger CODE	= 16_70

! Standard IMPish data structures

! Variables are declared here
%recordformat  varfm(%byteinteger type, form, level, scope, dim, %c
                     %integer  disp, format, size, pbase, extra)
%record(varfm)%array  var(0:max vars)
%record(varfm)%name   decvar
%record(varfm)     begin

! The compiler is stack based
%recordformat  stackfm(%byteinteger type, form, aform, base, scope, dim, %c
                     %integer  disp, format, size, pbase, extra, %c
                     %integer  var no)
%record(stackfm)%array  stack(1:max stack)
%record(stackfm)   null
%record(stackfm)%name top

! Pass 1 uses a lame label redefinition that forces us to map
! label ID's into unique labels for pass 3, using this database
%recordformat LabelFm(%integer id, tag)
%record(LabelFm)%array Labels(1:Max Labs)

! Status of registers
%ownintegerarray  activity(0:fr7) = 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
%owninteger  claimed = 0

! Pointer registers may be pointing to non-local display - we remember
! them for future use
%ownintegerarray displayhint(AX:DI) = 0, 0, 0, 0, 0, 0, 0, 0

! Math Co-processor uses a stack - we remember where it should be
! with this pointer
%owninteger fpustack = 0

! A general purpose workspace resource
%recordformat gp tag(%integer info, addr, flags, link)
%record(gptag)%array gptags(0:Max GP)
%integer gp asl;

%owninteger  control = check bits;  ! Current compiler flags (set by %control statement)
%owninteger  diagnose = 0;          ! Current diagnostic flags (set by %diagnose statement)

%owninteger nextcad = 0;            ! notional code address (not real - pass3 shuffles stuff)
%owninteger level = 0;              ! current contextual level
%integer    sym, next sym;          ! CODE SYMBOL, NEXT SYMBOL
%integer    vlb,vub;                ! VECTOR LOWER/UPPER BOUND
%owninteger current line = 0;       ! SOURCE LINE NUMBER
%owninteger stp = 0;                ! STACK POINTER
%integer    data size;              ! CURRENT DATA ITEM SIZE
%owninteger frame = 0;              ! LOCAL STACK FRAME EXTENT
%integer    parms;                  ! START OF PARAMETER STACK
%owninteger invert = 0;             ! CONDITION INVERSION FLAG
%owninteger compare unsign = 0;     ! CONDITION WAS NON-STANDARD (GENERALLY FPU COMPARE)
%owninteger uncond jump = 0;        ! ADDRESS OF CODE HOLE
%owninteger block type = 1;         ! -1 = RECORDS, 1 = PROCEDURE, 2 = SPEC
%owninteger in params = 0;          ! NON-ZERO INSIDE PARAMETER LISTS
%integer    decl;                   ! LAST-DEFINED DESCRIPTOR
%integer    otype, owntype, ownform;  ! Information about OWNs currently being declared
%integer    spec, frozen, potype;   ! More about current declaration
%integer    dim;                    ! Dimensions
%integer		i, j;							! used in the initialisation loops only
%owninteger Fp Result Loc = -1;     ! Place to store Real and LReal function results

%constinteger max dataseg = 12000;  ! Size in bytes of data segment table
%byteintegerarray datat(0:max dataseg)
%owninteger datatp = 0;             ! pointer to next data segment byte

%constinteger max switch = 1000;    ! Size in WORDS of switch segment table
%integerarray swtab(0:max switch)
%owninteger swtp = 0;               ! pointer to next switch segment entry

%ownstring(255)  external id = "", alias = ""
%ownstring(255)  internal id = ""

%byteintegername   cslen
%byteintegerarray  current string(0:255);   ! current string literal

%integer  xlen
%byteintegerarray xsymbuff(0:255);     ! current external string name

! WORK List - used to optimise use of temporary storage
! There is a head of list for each contextual level
%ownintegerarray worklist(1:max level) = 
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

%longreal	rvalue;		! floating point value for constants and initialisers

%owninteger ownval = 0; ! value to use when initialising OWNs

!-----------------------------------------------------------
! Start with machine independent utility functions and stack
! manipulation and debug
!-----------------------------------------------------------

!                                                             >> SHOW <<
%routine  show(%record(stackfm)%name  v)
   write(v_varno,3);
   print string(" : Typ="); write(v_type,1)
   print string(" Frm="); write(v_form,1)
   print string(" Bse="); write(v_base,1);
   print string(" Dsp="); write(v_disp,4)
   print string(" Siz="); write(v_size,1)
   print string(" Xtr="); write(v_extra,1)
   print string(" Fmt="); write(v_format,1)
   print string(" Dim="); write(v_dim,1)
   print string(" Pba="); write(v_pbase,1)
   newline
%end

! Simple ABORT routine
%routine abort(%string(255) message)
   %integer j

   select output(report)
   printstring("Pass 2 abandoned at line ");
   write(current line, 1);
   printstring(" : ");
   printstring(message)
   newline
   %if stp # 0 %start
      print string("STACK:");  newline
      show(stack(j)) %for j = 1,1,stp
   %finish
   %stop
%end

!                                                             >> WARN <<
%routine  warn(%integer  n)
%switch  w(1:8)
   select output(report)
   print string("*WARNING: line")
   write(current line, 1);  print string(": ")
   -> w(n)
w(1): print string("division by zero");  -> at
w(2): print string("Illegal FOR");  -> at
w(3): print string("Non-local control variable?");  -> at
w(4): print string("Invalid parameter for READ SYMBOL");  -> at
w(5): print string("String constant too long");  -> at
w(6): print string("No. of shifts outwith 0..31");  -> at
w(7): print string("Illegal constant exponent");  -> at
w(8): print string("Numerical constant too big");  -> at
at: newline
   select output(objout)
%end

!                                                          >> MONITOR <<
%routine  monitor(%record(stackfm)%name  v, %string(15) text)
   select output(report)
   print string(text);  print symbol(':')
   spaces(10-length(text))
   show(v)
   select output(objout)
%end

!                                                             >> GET GP TAG <<
%integerfn get gp tag
   %integer l

   %if gp asl = 0 %then abort("GP Tags")
   l = gp asl
   gp asl = gp tags(l)_link
   %result = l
%end

!                                                             >> RET GP TAG <<
%routine ret gp tag(%integer index)

   gp tags(index)_link = gp asl
   gp asl = index
%end
   
!------------------------------------------------------
! Machine dependent utility routines
!------------------------------------------------------

! Routines to write the intermediate file
! Record format is:
! <type><length><data>
! For debug purposes, the elements are all written as ascii
! characters, where <type> is a single letter, <length> is a single
! hex digit, length refers to the number of bytes (2 chars) of data.

! Intermediate file types:
%constinteger IF OBJ		      = 0;	! A - plain object code
%constinteger IF DATA	      = 1;	! B - dataseg offset code word
%constinteger IF CONST	      = 2;	! C - const seg offset code word
%constinteger IF DISPLAY	   = 3;	! D - display seg offset code word
%constinteger IF JUMP		   = 4;	! E - unconditional jump to label
%constinteger IF JCOND	      = 5;	! F - cond jump to label JE, JNE, JLE, JL, JGE, JG
%constinteger IF CALL		   = 6;	! G - call a label
%constinteger IF LABEL	      = 7;	! H - define a label
%constinteger IF FIXUP	      = 8;	! I - define location for stack fixup instruction
%constinteger IF SETFIX	      = 9;	! J - stack fixup <location> <amount>
%constinteger IF REQEXT	      = 10;	! K - external name spec
%constinteger IF REFEXT	      = 12;	! M - external name relative offset code word
%constinteger IF BSS		      = 13;	! N - BSS segment offset code word
%constinteger IF COTWORD	   = 14;	! O - Constant table word
%constinteger IF DATWORD	   = 15;	! P - Data segment word
%constinteger IF SWTWORD	   = 16;	! Q - switch table entry - actually a label ID
%constinteger IF SOURCE	      = 17;	! R - name of the source file
%constinteger IF DEFEXTCODE	= 18;	! S - define a code label that is external
%constinteger IF DEFEXTDATA	= 19;	! T - define a data label that is external
%constinteger IF SWT		      = 20;	! U - switch table offset code word
%constinteger IF LINE		   = 21;	! V - line number info for debugger

%routine  writenibble(%integer n)
	n = n & 16_f;
	%if 0 <= n %and n <= 9 %start
		printsymbol(n + '0');
	%else
		printsymbol(n + ('A' - 10));
   %finish
%end

! print a number in hexadecimal, to "places" size
%routine write hex(%integer n, places)
   %integer p, shift

   shift = (places - 1) * 4
   %while shift > 0 %cycle
      p = n >> shift
      writenibble(p)
      shift = shift - 4
   %repeat
   writenibble(n)
%end

%routine  writeifrecord(%integer type, %integer length, %byteintegerarrayname buffer)
	%integer c1, c2, i;

   select output(objout)
	printsymbol('A'+type);
	%if (length > 255) %then abort("Intermediate file record too long");
	writenibble(length>>4);
	writenibble(length&15);
	
	i = 0;
   %while (length > 0) %cycle
		c1 = buffer(i) >> 4;
		c2 = buffer(i) & 15;
		writenibble(c1);
		writenibble(c2);
		i = i + 1;
		length = length - 1;
   %repeat
	newline
%end

! Simple buffered output of code bytes...
%own %integer objectptr = 0;
%own %byte %integer %array objectbytes(0:7);
! And corresponding bytes for the listing (not always the same for fudged opcodes)
%own %integer listptr = 0;
%own %byte %integer %array listbytes(0:7);

! Routine to provide the address and hex opcode listing in the
! diagnostic output
%routine  listpreamble
	%integer i;

	select output(listout)
   space; writehex(nextcad, 4); space;
	%for i = 0, 1, 7 %cycle
		%if i < listptr %start
         writehex(listbytes(i), 2)
         space
		%else
         spaces(3)
      %finish
	%repeat
   spaces(8)
	nextcad = nextcad + listptr;
	listptr = 0;
%end

! flush the code buffer
%routine flushcode
	%if objectptr # 0 %start
		writeifrecord(IF OBJ, objectptr, objectbytes);
		objectptr = 0;	! reset the output pipe
   %finish
%end

! puts a normal code byte into the listing and code pipes
%routine  putbyte(%integer b)
	listbytes(listptr) = b;
	listptr = listptr + 1;
	objectbytes(objectptr) = b;
	objectptr = objectptr + 1;
%end

! A very handy little boolean function, used for instructions
! with variable size immediate operands
%integerfn issmall(%integer i)
	%result = 1 %if i < 128 %and i > -128
	%result = 0;
%end

! And aide-memoire of intel 8086 address modes...
!		DS:[BX+SI]
!		DS:[BX+DI]
!		SS:[BP+SI]
!		SS:[BP+DI]
!		DS:[SI];
!		DS:[DI];
!		DS: [offset16]
!		DS: [BX]
!		DS:[BX + SI + offset8]
!		DS:[BX + DI + offset8]
!		SS:[BP + SI + offset8]
!		SS:[BP + DI + offset8]
!		DS:[SI + offset8]
!		DS:[DI + offset8]
!		SS:[BP + offset8]
!		DS:[BX + offset8]
!		DS:[BX + SI + offset16]
!		DS:[BX + DI + offset16]
!		SS:[BP + SI + offset16]
!		SS:[BP + DI + offset16]
!		DS:[SI + offset16]
!		DS:[DI + offset16]
!		SS:[BP + offset16]
!		DS:[BX + offset16]

! mod r/m format is:
! mod LHREG R/M
! where mod = 11 for rh registers

! plant a modrm reference where the rh operand is a register
! Both parameters are actual register numbers, not internal ID's
%routine  modrmreg(%integer reg1, %integer reg2)
	putbyte(16_C0 ! (reg1 << 3) ! (reg2));
%end

! tags corresponding to linker directives...
%const %integer %array reltag(0:6) =
	0,			   ;! no relocation
	1,	         ;! dataseg offset code word
	2,    	   ;! const seg offset code word
	13,   		;! BSS relative code word
	3,       	;! display seg offset code word
	12,      	;! external name relative offset code word
	20    		;! switch table offset code word

! plant code for a relocated (DATA/BSS/DISPLAY/EXTERNAL) code word
%routine relocateoffset(%integer reloc, %integer offset)
	%integer tag;

	flushcode %if reloc # 0;	! so that only the offset is going into the queue

	putbyte(offset & 255);
	putbyte(offset >> 8);

	%if reloc # 0 %start
		tag = reltag(reloc);
		writeifrecord(tag, 2, objectbytes);
		objectptr = 0;	! clear the queue
	%finish
%end

! plant a modrm reference where the rh operand is in memory
! Parameter REG1 is an actual register number, but BASE is an internal ID
%routine  modrmmem(%integer reg1, %integer base, %integer disp)
	%integer mod, rm, reloc;

	reloc = base>>4;
	base = base & 15;

	%if base = 0 %start; 	! no register, just a displacement
		! mod = 000, rm = 110
		putbyte((reg1 << 3) ! 6);
		relocateoffset(reloc, disp);
	%else
		%if disp = 0 %start
			mod = 0
		%else
         %if issmall(disp) # 0 %start;		! fits in one byte
			   mod = 1
		   %else
			   mod = 2
         %finish
      %finish

		! unfortunately displacement (even zero) must be output in full if
		! the offset is relocatable
		%if reloc # 0 %then mod = 2;

		%if base = BP %start
			rm = 6;
		%else
         %if base = BX %start
			   rm = 7;
		   %else
            %if base = DI %start
			      rm = 5;
		      %else
               %if base = SI %start
			         rm = 4;
		         %else
			         abort("Internal address mode error");
               %finish
            %finish
         %finish
      %finish

		putbyte((mod << 6)!(reg1 << 3)!rm);

		%if mod = 1 %start
			putbyte(disp);
		%else
         %if mod = 2 %then relocateoffset(reloc, disp)
      %finish
   %finish
%end

%const %string(2) %array regname(AX:DI) =
	"AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI"

%const %string(2) %array reg8name(AL:BH) =
	"AL", "CL", "DL", "BL", "AH", "CH", "DH", "BH"

%const %string(7) %array relocname(0:6) =
	"", "DATA", "COT", "BSS", "DISPLAY", "EXTERN", "SWTAB"

! Print the corresponding memory access string
! BASE is an internal ID, not an actual register number
%routine  printmemref(%integer base, %integer disp)
	%integer reloc;

	reloc = base >> 4;
	base = base & 15;
   selectoutput(listout)
	printsymbol('[');
	%if base # 0 %start
		printstring(regname(base));
		%if reloc # 0 %start
         printsymbol('+'); printstring(relocname(reloc));
      %finish
		%if disp # 0 %then %start
         printsymbol('+') %if disp > 0
         write(disp,1)
      %finish
	%else
		%if reloc # 0 %start
         printstring(relocname(reloc)); printsymbol('+')
      %finish
      writehex(disp, 4)
   %finish
   printsymbol(']');
%end

! opcodes
%const %string(4) %array opname(NOP:JMP) =
	"NOP", "CWD", "RET", "SAHF",
	"DEC", "INC", "NEG", "NOT",
	"POP", "PUSH",
	"LEA", "MOV", "XCHG",
	"ADC", "ADD", "AND", "CMP", "OR", "SUB", "XOR",
	"SHL", "SHR", "IDIV", "IMUL",
	"CALL", "JE", "JNE",
   "JG", "JGE", "JL", "JLE",
   "JA", "JAE", "JB", "JBE", "JMP"


%const %byte %integer %array opvalue(NOP:JMP) =
	16_90, 16_99, 16_C3, 16_9E,
	16_FF, 16_FF, 16_F7, 16_F7,
	16_8F, 16_FF,
	16_8B, 16_89, 16_87,		;! LEA is fudged as if it were m <- r, to allow the flip
	16_11, 16_01, 16_21, 16_39, 16_09, 16_29, 16_31,
	16_D1, 16_D1, 16_F7, 16_F7,
	16_E8, 16_74, 16_75,
   16_7F, 16_7D, 16_7C, 16_7E,
   16_77, 16_73, 16_72, 16_76, 16_EB

! 8 bit equivalent opcodes
%const %byte %integer %array op8value(NOP:JMP) =
	16_90, 16_99, 16_C3, 16_9E,	;! not 8 bit, included for completeness
	16_FE, 16_FE, 16_F6, 16_F6,
	16_8F, 16_FF,				      ;! not 8 bit, included for completeness
	16_8B, 16_88, 16_86,		      ;! LEA is not applicable for 8 bit
	16_10, 16_00, 16_20, 16_38, 16_08, 16_28, 16_30,
	16_D0, 16_D0, 16_F6, 16_F6,
	16_E8, 16_74, 16_75,
   16_7F, 16_7D, 16_7C, 16_7E,
   16_77, 16_73, 16_72, 16_76, 16_EB	;! not 8 bit, included for completeness

! An opcode with no operands (eg RET)
%routine dumpsimple(%integer opn)

	putbyte(opvalue(opn));

	listpreamble;
   printstring(opname(opn));
   newline
	flushcode;
%end

! A special bit of magic, used in record assignment
%routine dumprepmovsb
	putbyte(16_f3);	! rep
	putbyte(16_a4);	! movsb

	listpreamble;
	printstring("REP MOVSB");
   newline
	flushcode;
%end

! Used in record = 0 assignment
%routine dumprepstosb
	putbyte(16_f3);	! rep
	putbyte(16_aa);	! stosb

	listpreamble;
	printstring("REP STOSB");
   newline
	flushcode;
%end

! unary register operation - DEC, INC, NEG, NOT, POP, PUSH, IDIV, IMUL
! REG is an internal ID, not an actual register number
%routine dumpur(%integer opn, %integer reg)
   %switch ops(DEC:IMUL)

	displayhint(reg) = 0;

	->ops(opn)

	ops(DEC):
		putbyte(16_48 + reg - AX);
		->break;

	ops(INC):
		putbyte(16_40 + reg - AX);
		->break;

	ops(NEG):
		putbyte(16_F7);
		modrmreg(3, reg - AX);
		->break;

	ops(NOT):
		putbyte(16_F7);
		modrmreg(2, reg - AX);
		->break;

	ops(POP):
		putbyte(16_58 + reg - AX);
		->break;

	ops(PUSH):
		putbyte(16_50 + reg - AX);
		->break;

	ops(IDIV):
		putbyte(16_F7);
		modrmreg(7, reg - AX);
		->break;

	ops(IMUL):
		putbyte(16_F7);
		modrmreg(5, reg - AX);

break:
	listpreamble;
   printstring(opname(opn))
   space
   printstring(regname(reg));
   newline
	flushcode

%end

! Plant code for a unary operation on memory
! BASE is an internal ID, not the actual register number
%routine dumpum(%integer opn, %integer base, %integer disp)
   %switch ops(DEC:JMP)

	->ops(opn)

	ops(DEC):
		putbyte(16_FF);
		modrmmem(1, base, disp);
		->break;

	ops(INC):
		putbyte(16_FF);
		modrmmem(0, base, disp);
		->break;

	ops(NEG):
		putbyte(16_F7);
		modrmmem(3, base, disp);
		->break;

	ops(NOT):
		putbyte(16_F7);
		modrmmem(2, base, disp);
		->break;

	ops(POP):
		putbyte(16_8F);
		modrmmem(0, base, disp);
		->break;

	ops(PUSH):
		putbyte(16_FF);
		modrmmem(6, base, disp);
		->break;

	ops(IDIV):
		putbyte(16_F7);
		modrmmem(7, base, disp);
		->break;

	ops(IMUL):
		putbyte(16_F7);
		modrmmem(5, base, disp);
		->break;

	ops(JMP):
		putbyte(16_FF);
		modrmmem(4, base, disp);

	ops(CALL):
		putbyte(16_FF);
		modrmmem(2, base, disp);

break:
	listpreamble;
   printstring(opname(opn))
   printstring(" WORD ");	! otherwise it's ambiguous for the reader
	printmemref(base, disp);
   newline
	flushcode;
%end

! Plant code for a unary operation on an 8 bit memory location
! Not all of the possible unary ops make sense as 8 bit destinations
! BASE is an internal ID, not the actual register number
%routine dumpum8(%integer opn, %integer base, %integer disp)
	%integer base op, index

	%if opn = DEC %or opn = INC %start
		base op = 16_FE
		%if opn = DEC %then index = 1 %else index = 0
	%else
		%if opn = NOT %or opn = NEG %start
			base op = 16_F6
			%if opn = NOT %then index = 2 %else index = 3
		%else
			Abort("Invalid UM8")
		%finish
	%finish

	putbyte(base op)
	modrmmem(index, base, disp)

	listpreamble;
   printstring(opname(opn))
   printstring(" BYTE ");	! otherwise it's ambiguous for the reader
	printmemref(base, disp);
   newline
	flushcode;
%end

! Plant a Memory <- Reg operation
! Both BASE and REG are internal ID's, not actual register numbers
%routine dumpmr(%integer opn, %integer base, %integer disp, %integer reg)

	%if opn = SHL %start;	! special "shift by CL"
		putbyte(16_D3);
		modrmmem(4, base, disp);
	%else
      %if opn = SHR %start
         putbyte(16_D3);
		   modrmmem(5, base, disp);
      %else;               ! normal stuff
			putbyte(opvalue(opn));
			modrmmem(reg - AX, base, disp);
      %finish
   %finish

	listpreamble;
   printstring(opname(opn))
   space
	printmemref(base, disp);
   printsymbol(',')
   printstring(regname(reg));
   newline
	flushcode;
%end

! Plant an 8 bit Memory <- Reg operation
! Both BASE and REG are internal ID's, not actual register numbers
%routine dumpmr8(%integer opn, %integer base, %integer disp, %integer reg)

	%if opn = SHL %start;	! special "shift by CL"
		putbyte(16_D2);
		modrmmem(4, base, disp);
	%else
      %if opn = SHR %start
         putbyte(16_D2);
		   modrmmem(5, base, disp);
      %else;               ! normal stuff
			putbyte(op8value(opn));
			modrmmem(reg - AL, base, disp);
      %finish
   %finish

	listpreamble;
   printstring(opname(opn))
   space
	printmemref(base, disp);
   printsymbol(',')
   printstring(reg8name(reg));
   newline
	flushcode;
%end

! Plant a 16 bit Reg <- Memory operation
! Both BASE and REG are internal ID's, not actual register numbers
%routine dumprm(%integer opn, %integer reg, %integer base, %integer disp)

! We optimise the fairly common instruction MOV AX,[disp] with
! the special short-form quirk of the 8086...

	%if reg = AX %and opn = MOV %and base & 15 = 0 %start
		putbyte(16_A1)
   	relocateoffset(base>>4, disp)
	%else
		displayhint(reg) = 0;

		putbyte(opvalue(opn)+2);
		modrmmem(reg - AX, base, disp);
	%finish

	listpreamble;
   printstring(opname(opn))
   space
   printstring(regname(reg));
   printsymbol(',')
	printmemref(base, disp);
   newline
	flushcode;
%end

! Plant an 8 bit Reg <- Memory operation
! Both BASE and REG are internal ID's, not actual register numbers
%routine dumprm8(%integer opn, %integer reg, %integer base, %integer disp)

	putbyte(op8value(opn)+2);
	modrmmem(reg - AL, base, disp);

	listpreamble;
   printstring(opname(opn))
   space
   printstring(reg8name(reg));
   printsymbol(',')
	printmemref(base, disp);
   newline
	flushcode;
%end

! Plant a 16 bit Reg <- Reg operation
! Both register parameters are internal ID's
%routine dumprr(%integer opn, %integer reg1, %integer reg2)

	displayhint(reg1) = 0;

	%if opn = SHL %start;	! special "shift by CL"
		putbyte(16_D3);
		modrmreg(4, reg1 - AX);
	%else
      %if opn = SHR %start
         putbyte(16_D3);
		   modrmreg(5, reg1 - AX);
      %else;               ! normal stuff
   		putbyte(opvalue(opn));
	   	modrmreg(reg2 - AX, reg1 - AX);
      %finish
   %finish

	listpreamble;
   printstring(opname(opn))
   space
   printstring(regname(reg1));
   printsymbol(',')
   printstring(regname(reg2));
   newline
	flushcode;
%end

%routine dumprr8(%integer opn, %integer reg1, %integer reg2)

	%if opn = SHL %start;	! special "shift by CL"
		putbyte(16_D2);
		modrmreg(4, reg1 - AL);
	%else
      %if opn = SHR %start
   		putbyte(16_D2);
	   	modrmreg(5, reg1 - AL);
	   %else
   		putbyte(op8value(opn));
	   	modrmreg(reg2 - AL, reg1 - AL);
      %finish
   %finish

	listpreamble;
   printstring(opname(opn))
   space
   printstring(reg8name(reg1));
   printsymbol(',')
   printstring(reg8name(reg2));
   newline
	flushcode;
%end

%const %byte %integer %array aximmediatevalue(NOP:XOR) =
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0,
	0, 16_B8, 0,
	16_15, 16_05, 16_25, 16_3D, 16_0D, 16_2D, 16_35

! Register immediate operations - can be MOV, Math, or Shift
! The immediate operand may be a relocated offset as part of
! an address calculation
%routine dumprioffset(%integer opn, %integer reg, %integer reloc, %integer immed)
	%integer subop;
   %switch ops(MOV:SHR)

	displayhint(reg) = 0;
	reloc = reloc >> 4;			! because we pass around the or-able version

	%if reg = AX %and opn <= XOR %start
		putbyte(aximmediatevalue(opn));
   	relocateoffset(reloc, immed);
      ->break
	%else
		->ops(opn)
   %finish

		ops(MOV):
			putbyte(16_B8 + reg - AX);
      	relocateoffset(reloc, immed);
			->break;

		ops(ADD): subop = 0; ->addsub
		ops(ADC): subop = 2; ->addsub
		ops(CMP): subop = 7; ->addsub
		ops(SUB): subop = 5
      addsub:
			%if issmall(immed) # 0 %and reloc = 0 %start
				putbyte(16_83);
				modrmreg(subop, reg - AX);
				putbyte(immed & 255);
			%else
				putbyte(16_81);
				modrmreg(subop, reg - AX);
           	relocateoffset(reloc, immed);
			%finish
			->break;
	
		ops(AND): subop = 4; ->logical
		ops(OR):  subop = 1; ->logical
		ops(XOR): subop = 6
      logical:
			putbyte(16_81);
			modrmreg(subop, reg - AX);
        	relocateoffset(reloc, immed);
			->break;

		ops(SHL):
			putbyte(16_D1);
			modrmreg(4, reg - AX);
			->break;

		ops(SHR):
			putbyte(16_D1);
			modrmreg(5, reg - AX);

break:
	listpreamble;
   printstring(opname(opn))
   space
   printstring(regname(reg));
   printsymbol(',')
	%if reloc # 0 %start
		printstring(relocname(reloc)); printsymbol('+')
   %finish
   write(immed, 1)
   newline
	flushcode;
%end

! Since many math operations don't involve relocated addresses
! we provide a chep version of dumprioffset
%routine dumpri(%integer opn, %integer reg, %integer immed)
   dumprioffset(opn, reg, 0, immed)
%end

! Memory (16 bit) immediate operations - can be MOV, Math, or Shift
%routine dumpmi(%integer opn, %integer base, disp, %integer immed)
	%integer subop;
   %switch ops(MOV:SHR)

		->ops(opn)

	ops(MOV):
		putbyte(16_C7)
		modrmmem(0, base, disp)
		putbyte(immed & 255)
		putbyte(immed >> 8)
		->break;

	ops(ADD): subop = 0; ->addsub
	ops(ADC): subop = 2; ->addsub
	ops(CMP): subop = 7; ->addsub
	ops(SUB): subop = 5
   addsub:
			%if issmall(immed) # 0 %start
				putbyte(16_83);
      		modrmmem(subop, base, disp)
				putbyte(immed & 255);
			%else
				putbyte(16_81);
      		modrmmem(subop, base, disp)
				putbyte(immed & 255);
				putbyte(immed >> 8);
			%finish
			->break;
	
		ops(AND): subop = 4; ->logical
		ops(OR):  subop = 1; ->logical
		ops(XOR): subop = 6
      logical:
			putbyte(16_81);
     		modrmmem(subop, base, disp)
			putbyte(immed & 255);
			putbyte(immed >> 8);
			->break;

		ops(SHL):
			putbyte(16_D1);
     		modrmmem(4, base, disp)
			->break;

		ops(SHR):
			putbyte(16_D1);
     		modrmmem(5, base, disp)

break:
	listpreamble;
   printstring(opname(opn))
   printstring(" WORD ");	! otherwise it's ambiguous for the reader
	printmemref(base, disp);
   printsymbol(',')
   write(immed, 1)
   newline
	flushcode;
%end

! Memory (8 bit) immediate operations - can be MOV, Math, or Shift
%routine dumpmi8(%integer opn, %integer base, disp, %integer immed)
	%integer subop;
   %switch ops(MOV:SHR)

		->ops(opn)

	ops(MOV):
		putbyte(16_C6)
		modrmmem(0, base, disp)
		putbyte(immed & 255)
		->break;

	ops(ADD): subop = 0; ->op80
	ops(ADC): subop = 2; ->op80
	ops(CMP): subop = 7; ->op80
	ops(SUB): subop = 5; ->op80
	ops(AND): subop = 4; ->op80
	ops(OR):  subop = 1; ->op80
	ops(XOR): subop = 6
   op80:
		putbyte(16_80);
  		modrmmem(subop, base, disp)
		putbyte(immed & 255);
			->break;
	
	ops(SHL):
		putbyte(16_D0);
  		modrmmem(4, base, disp)
		->break;

	ops(SHR):
		putbyte(16_D0);
   	modrmmem(5, base, disp)

break:
	listpreamble;
   printstring(opname(opn))
   printstring(" BYTE ");	! otherwise it's ambiguous for the reader
	printmemref(base, disp);
   printsymbol(',')
   write(immed, 1)
   newline
	flushcode;
%end

! Finally, a catch-all that recasts operations using generic
! Var Stack structures
! Plant a 16 bit Reg <- Var operation
%routine dumprv(%integer opn, %integer reg, %record(stackfm)%name v)

   %if v_form = V in R %start
      dumprr(opn, reg, v_base)
   %else
      %if v_form = V in S %start
         dumprm(opn, reg, v_base!v_scope, v_disp)
      %else
         %if v_form = constant %start
            dumprioffset(opn, reg, v_scope, v_disp)
         %else
            abort("Address Mode")
         %finish
      %finish
   %finish
%end

! Another special dumper - the only "Unary" operation that
! takes an immediate operand is PUSH
%routine dumppushi(%integer reloc, %integer immed)

	reloc = reloc >> 4;			! because we pass around the or-able version

   %if reloc = 0 %and is small(immed) # 0 %start
	   putbyte(16_6A);
	   putbyte(immed & 255);
   %else
	   putbyte(16_68);
     	relocateoffset(reloc, immed);
   %finish

	listpreamble;
   printstring("PUSH")
   space
	%if reloc # 0 %start
		printstring(relocname(reloc)); printsymbol('+')
   %finish
   write(immed, 1)
   newline
	flushcode
%end

%routine dumpvpush(%record(stackfm)%name v)

   %if v_form = V in R %start
      dumpur(PUSH, v_base)
   %else
      %if v_form = V in S %start
         dumpum(PUSH, v_base!v_scope, v_disp)
      %else
         %if v_form = constant %start
            dumppushi(v_scope, v_disp)
         %else
            abort("Push Mode")
         %finish
      %finish
   %finish
%end
      
!----------------------------------------------------------
! Floating point instructions - much simpler since there are
! only two forms - RR and RM

%conststring(10)%array flopname(FILD:FLDPI) =
   "FILD", "FLD DWORD", "FLD QWORD", "FISTP",
   "FSTP DWORD", "FSTP QWORD", "FADDP", "FSUBP",
	"FSUBRP", "FMULP", "FDIVP", "FDIVRP",
	"FCOMPP", "FCHS", "FABS",
   "FSTSW AX", "FLDZ", "FLDPI"

! The prefix opcode
%constbyteintegerarray flprefix(FILD:FLDPI) =
	16_DF, 16_D9, 16_DD, 16_DF,
	16_D9, 16_DD, 16_DE, 16_DE,
	16_DE, 16_DE, 16_DE, 16_DE,
	16_DE, 16_D9, 16_D9,
   16_DF, 16_D9, 16_D9

! The function selector to put in the field in the second byte
! (or the second byte)
%constbyteintegerarray flindex(FILD:FLDPI) =
	16_00, 16_00, 16_00, 16_03,
	16_03, 16_03, 16_C0, 16_E8,
	16_E0, 16_C8, 16_F8, 16_F0,
	16_D8, 16_E0, 16_E1,
   16_E0, 16_EE, 16_EB

! Plant a Floating Point Reg <- Memory operation
! BASE is an internal ID, not actual register number
! Destination regsiter is implicitly the stack top
%routine dumpfloprm(%integer opn, %integer base, %integer disp)

	%if opn <= FLDQ %start;	! a load type
		fpu stack = fpu stack + 1
		%if fpu stack > 8 %then abort("FPU Stack Overflow")
	%else
		fpu stack = fpu stack - 1
		%if fpu stack < 0 %then abort("FPU Stack Underflow")
	%finish

   putbyte(16_9B);         ! we prepend a WAIT to everything
	putbyte(flprefix(opn));
	modrmmem(flindex(opn), base, disp);

	listpreamble;
   printstring(flopname(opn))
   space
	printmemref(base, disp);
   newline
	flushcode;
%end

! Plant a Floating Point Reg <- Reg operation
! Both register parameters are internal ID's that we
! convert to stack offsets
%routine dumpfloprr(%integer opn, %integer reg1, %integer reg2)
	%integer top

	top = fpustack + (FR0 - 1)

	%if reg2 # top %then abort("FPU Stack Address")

	%if opn < FCHS %start;	! two operands - will pop one
		fpu stack = fpu stack - 1
      %if opn = FCMP %then fpu stack = fpu stack - 1;   ! COMPP pops both registers
		%if fpu stack < 0 %then abort("FPU Stack Underflow")
	%finish

   putbyte(16_9B);         ! we prepend a WAIT to everything
	putbyte(flprefix(opn));
	putbyte(flindex(opn)!(top - reg1))

	listpreamble;
   printstring(flopname(opn))
   space
   printstring("ST(");
	write(top-reg1, 1)
	printstring("),ST")
   newline
	flushcode;
%end

! Plant a "special" floating point operation
%routine dumpflopspec(%integer opn)

	%if opn >= FLDZ %start;       ! load a constant
      fpu stack = fpu stack + 1
	   %if fpu stack > 8 %then abort("FPU Stack Overflow")
   %finish

   putbyte(16_9B);         ! we prepend a WAIT to everything
	putbyte(flprefix(opn));
	putbyte(flindex(opn))

	listpreamble
   printstring(flopname(opn))
   newline
	flushcode
%end

%routine dumpjump(%integer opn, %integer labelid)
	%integer jtype, count;

	! we put conventional assembler into the pipe for the listing
	! (with a zero jump offset) but then re-use the pipe for the
	! pseudo-code for the jump
	putbyte(opvalue(opn));
	putbyte(0);
	%if opn = CALL %then putbyte(0);

	listpreamble;
   printstring(opname(opn))
   space
   printsymbol('L');
   write(labelid,1)
   newline

	objectptr = 0;		! zap the current contents of the pipe
	count = 0;
	%if opn = JMP %start
		jtype = IF JUMP;
	%else
      %if opn = CALL %start
		   jtype = IF CALL;
      %else
   		jtype = IF JCOND;
	   	objectbytes(0) = (opn - JE);
		   count = 1;
      %finish
   %finish

	objectbytes(count) = (labelid & 255);
	objectbytes(count+1) = (labelid >> 8);

	writeifrecord(jtype, count+2, objectbytes);

	! finally, calls may trash registers...
	%if opn = CALL %start
      displayhint(BX) = 0
      displayhint(SI) = 0
      displayhint(DI) = 0
   %finish
%end

! call the n'th external routine we've spec'ed
%routine dumpextcall(%integer labelid)

   displayhint(BX) = 0
   displayhint(SI) = 0
   displayhint(DI) = 0

	putbyte(opvalue(CALL));
	flushcode;	! plant the "CALL" instruction
	putbyte(labelid & 255);
	putbyte(labelid >> 8);
	listpreamble;
	printstring("CALL EXTERN")
   write(labelid, 1);
   newline

	writeifrecord(IF REFEXT, 2, objectbytes);
	objectptr = 0;		! zap the current contents of the pipe
%end

%routine dumplabel(%integer labelid)

	select output(listout)
   space; writehex(nextcad, 4); spaces(22);
   printsymbol('L');
   write(labelid, 1);
   printstring("  EQU $")
   newline

	objectbytes(0) = labelid & 255;
	objectbytes(1) = labelid >> 8;
	writeifrecord(IF LABEL, 2, objectbytes);

   displayhint(BX) = 0
   displayhint(SI) = 0
   displayhint(DI) = 0
%end

%routine dumpstaticalloc(%integer which)

	! we pretend to dump "83 ec 00 SUB SP,0000" but we actually plant a special pass 2 directive
	putbyte(16_83);
	putbyte(16_ec);
	putbyte(16_00);
	listpreamble;
	printstring("SUB SP,00");
   newline
	objectptr = 0;		! zap the current contents of the pipe

	objectbytes(0) = which & 255;
	objectbytes(1) = which >> 8;
	writeifrecord(IF FIXUP, 2, objectbytes);
%end

! Pass 3 goes back and plants the correct preamble code for
! the static allocation based on this directive
%routine dumpstaticfill(%integer which, %integer size)

	objectptr = 0;		! zap the current contents of the pipe

	objectbytes(0) = which & 255;
	objectbytes(1) = which >> 8;
	objectbytes(2) = size & 255;
	objectbytes(3) = size >> 8;
	writeifrecord(IF SETFIX, 4, objectbytes);
%end

! dump words for the constant segment or the data segment
! Adjusts CAD so that the diagnostic listing looks sensible
%routine dumpcdword(%integer word, %integer which)

	%integer tag, tmpcad, hi, lo
   %owninteger cptr = 0
   %owninteger dptr = 0
   %owninteger sptr = 0

   tmpcad = next cad
	%if which = 2 %start
		tag = IF SWTWORD;
      next cad = sptr; sptr = sptr + 2
	%else
      %if which = 1 %start
		   tag = IF COTWORD;
         next cad = cptr; cptr = cptr + 2
	   %else
		   tag = IF DATWORD;
         next cad = dptr; dptr = dptr + 2
      %finish
   %finish

   hi = word >> 8
   lo = word & 255
	putbyte(lo);
	putbyte(hi);
	listpreamble;
	printstring("db ")
   writehex(lo, 2); printsymbol(','); writehex(hi, 2);
   printstring(" ; ")
   %if lo > 32 %and lo < 127 %then printsymbol(lo) %else printsymbol('.')
   %if hi > 32 %and hi < 127 %then printsymbol(hi) %else printsymbol('.')
   newline
	writeifrecord(tag, 2, objectbytes);
	objectptr = 0;		   ! clear the pipe
   nextcad = tmp cad;   ! restore the real CAD
%end

! tell the object file maker what source line we are on
%routine dumplinenumber(%integer line)

	%byteintegerarray buffer(0:1);
	%own %integer lastcad = 0;		! code address last time we did this

	%if nextcad # lastcad %start
		buffer(0) = (line & 255);
		buffer(1) = (line >> 8);
		writeifrecord(IF LINE, 2, buffer);
		lastcad = nextcad;
	%finish
%end

! utility to copy an IMP string into a simple buffer to
! pass to the IF Record routine
%routine str to xsym(%string(255)%name s)
   %integer l

   l = length(s)
   xlen = 0
   %while xlen < l %cycle
      x sym buff(xlen) = charno(s, xlen+1)
      xlen = xlen + 1
   %repeat
%end

! tell the object maker the source file name
%routine dumpsourcename(%string(255) filename)

   str to xsym(filename)
	writeifrecord(IF SOURCE, xlen, x sym buff);

%end

! tell the linker about an external definition
%routine fill external(%integer seg, offset, %string(255) extname)
	
   str to xsym(extname)
	%if seg = CODE %start
		writeifrecord(IF DEFEXTCODE, xlen, x sym buff);
	%else
		writeifrecord(IF DEFEXTDATA, xlen, x sym buff);
	   ! er, this doesn't actually work yet!
	%finish
%end

! Plant a request to the linker for the external name, and
! return an index number to refer to it with in future
%integerfn externalref(%string(255) extname)
   %owninteger nextextref=1

   str to xsym(extname)
	writeifrecord(IF REQEXT, xlen, x sym buff);
   nextextref = nextextref + 1
	%result = nextextref - 1;
%end

!------------------------------------------------------
! Constant table utility routines
!
! Rather than dump literal constants as they occur, we
! collect them in a table.  Whenever the compiler wants
! any kind of literal, we look to see if we already
! have it.  Note this automatically solves re-use of
! things like floating point constants, string newline,
! and fixed array dope vectors.  When the table starts
! to get fairly full, we flush it.  Obviously that means
! in a large program we might not actually get full re-use
! of constants after we've flushed, but the idea is sound.
!
! For the convenience of the caller, several versions of
! pretty much the same thing are provided.
!------------------------------------------------------
%constinteger cot size = 2000
%ownbyteintegerarray contable(0:cot size)
%owninteger cotp = 0
%owninteger cotoffset = 0;       ! updated on a flush

%routine flushcot
   %integer i

! We output a position hint to the diagnostic stream
! Note that although this is intended to look like
! 8086 assembly directives the real work is done by
! pass 3 - this is only to guide the human reader as
! to what is going on

   selectoutput(listout)
   printstring("      _TEXT    ENDS
      CONST  SEGMENT WORD PUBLIC 'CONST'
")
	i = 0
	%while i < cotp %cycle
		dumpcdword((contable(i+1) << 8) ! contable(i), 1)
		i = i + 2;
   %repeat

! Update the pointers
   cotp = 0
   cotoffset = cotoffset + i

! and send another hint
   selectoutput(listout)
	printstring("      CONST    ENDS
      _TEXT  SEGMENT WORD PUBLIC 'CODE'
")

%end

! return the offset in the const segment of a byte
! with value b
%integerfn getcotb(%byteinteger b)
   %integer i

   i = 0
   %while i < cotp %cycle
      %if contable(i) = b %then %result = i + cotoffset
      i = i + 1
   %repeat

   ! value wasn't there
   %if cotp = cotsize %then flushcot

   contable(cotp) = b
   cotp = cotp + 1
   %result = (cotp - 1) + cotoffset
%end

! return the offset in the const segment of a word
! with value w
%integerfn getcotw(%integer w)
   %integer i, cw

   i = 0
   %while i < cotp-1 %cycle
      cw = contable(i)!(contable(i+1)<<8)
      %if cw = w %then %result = i + cotoffset
      i = i + 2
   %repeat

   ! value wasn't there - first make sure there is space
   %if cotp > cotsize-2 %then flushcot

   ! now round off the COT
   cotp = (cotp + 1) & (\1)
   contable(cotp) = w & 255
   contable(cotp+1) = w >> 8
   cotp = cotp + 2
   %result = (cotp - 2) + cotoffset
%end

! return the offset in the const segment of a double word
! with value dl:dh
%integerfn getcot2(%integer dl, dh)
   %integer i, cwl, cwh

   i = 0
   cwl = contable(i)!(contable(i+1)<<8)
   %while i < cotp-3 %cycle
      cwh = contable(i+2)!(contable(i+3)<<8)
      %if cwl = dl %and cwh = dh %then %result = i + cotoffset
      i = i + 2
      cwl = cwh
   %repeat

   ! value wasn't there - first make sure there is space
   %if cotp > cotsize-4 %then flushcot

   ! now round off the COT
   cotp = (cotp + 1) & (\1)

   contable(cotp) = dl & 255
   contable(cotp+1) = dl >> 8
   contable(cotp+2) = dh & 255
   contable(cotp+3) = dh >> 8
   cotp = cotp + 4
   %result = (cotp - 4) + cotoffset
%end

! return the offset in the const segment of a quad word
! with value q0:q1:q2:q3 (lo to hi)
%integerfn getcot4(%integer q0, q1, q2, q3)
   %integer i, cw0, cw1, cw2, cw3

   i = 0
   cw0 = contable(i)!(contable(i+1)<<8)
   cw1 = contable(i+2)!(contable(i+3)<<8)
   cw2 = contable(i+4)!(contable(i+5)<<8)
   %while i < cotp-3 %cycle
      cw3 = contable(i+6)!(contable(i+7)<<8)
      %if cw0 = q0 %and cw1 = q1 %and cw2 = q2 %and cw3 = q3 %then %result = i + cotoffset
      i = i + 2
      cw0 = cw1
      cw1 = cw2
      cw2 = cw3
   %repeat

   ! value wasn't there - first make sure there is space
   %if cotp > cotsize-8 %then flushcot

   ! now round off the COT
   cotp = (cotp + 1) & (\1)

   contable(cotp) = q0 & 255
   contable(cotp+1) = q0 >> 8
   contable(cotp+2) = q1 & 255
   contable(cotp+3) = q1 >> 8
   contable(cotp+4) = q2 & 255
   contable(cotp+5) = q2 >> 8
   contable(cotp+6) = q3 & 255
   contable(cotp+7) = q3 >> 8
   cotp = cotp + 8
   %result = (cotp - 8) + cotoffset
%end

%owninteger null string = -1

! get an index into the constant table for the string literal
! in the array s
%integerfn getcots(%byteintegerarrayname s)
	%integer i, first, len, match;

   len = s(0) + 1;      ! number of bytes to search for

! We optimise the Null String "" in comparisons, so we remember
! the location here
   %if len = 0 %start
      null string = getcotb(0)
      %result = null string
   %finish

   first = 0;           ! offset to search in contable

   %while first + len - 1 < cotp %cycle;   ! so long as there are that many bytes left
      match = 1
      %for i = 0, 1, len-1 %cycle
         %if s(i) # contable(first + i) %start
            match = 0
            %exit
         %finish
      %repeat
      %if match = 1 %then %result = first + cotoffset
      first = first + 1;               ! try the next solution
   %repeat

   ! if we get here, it wasn't already in the constant table
	%if (cotp + len) >= cotsize %then flushcot

	first = cotp
   i = 0;
	%while len > 0 %cycle
		contable(cotp) = s(i)
		cotp = cotp + 1
		i = i + 1
		len = len - 1
	%repeat
	%result = first + cotoffset
%end

!------------------------------------------------------
! Data segment utility routines
!
! Unlike constants, we can't re-use data segment items,
! which makes this much simpler
!------------------------------------------------------

!                                                            >> GBYTE <<
! Simple byte in data segment
%routine  gbyte(%integer  n)

   Abort("Data Seg Overflow") %if datatp > max dataseg   
   datat(datatp) = n & 255;
   datatp = datatp + 1
%end

!                                                            >> GPUT <<
! Put a word into data segment
%routine  gput(%integer  n)

   gbyte(n & 255)
   gbyte(n >> 8);
%end

!                                                             >> GFIX <<
! round off the datasegment pointer for alignment
%routine  gfix(%integer align)
   gbyte(0) %while datatp & align # 0
%end

%routine flush data
   %integer i

! We output a position hint to the diagnostic stream

   selectoutput(listout)
   printstring("              ENDS
      DATA  SEGMENT WORD PUBLIC 'DATA'
")
	i = 0
	%while i < datatp %cycle
		dumpcdword((datat(i+1) << 8) ! datat(i), 0)
		i = i + 2;
   %repeat

! and send another hint
   selectoutput(listout)
	printstring("      DATA    ENDS
")

%end

!-----------------------------------------------------
! The last table we collect as we go along is the switch
! table.  We don't provide individual routines to fill
! it in, but for tidyness we provide this routine to send
! the contents to pass 3

%routine flush switch
   %integer i

   select output(listout)
   printstring("              ENDS
      _SWTAB  SEGMENT WORD PUBLIC '_SWTAB'
")
	i = 0
	%while i < swtp %cycle
		dumpcdword(swtab(i), 2)
		i = i + 1
   %repeat

! and send another hint
   selectoutput(listout)
	printstring("      _SWTAB   ENDS
")

%end

!-------------------------------------------------------------
! Print the source code lines up to the indicated line
! number - these will interleave with the diagnostic assembly
! output

%owninteger echoline = 0

%routine echo source line
   %integer ch
   %owninteger source eof = 0

   echoline = echoline + 1;            ! update the count even if there's no input

   %if source eof # 0 %then %return;   ! silently ignore lack of source file

   select input(source)
   select output(listout)
   %cycle
      readsymbol(ch)
      printsymbol(ch)
      %exit %if ch = 10 %or ch < 0
   %repeat

   %if ch < 0 %then source eof = 1

   select input(icode)
   select output(objout)
%end

!-----------------------------------------------------------
! General descriptor and register manipulation routines
!-----------------------------------------------------------

!                                                         >> FLOATING <<
%integerfn  floating(%record(stackfm)%name v)
! check descriptor for floating point quantity
   %result = 1 %if v_type = real %or v_type = lreal
   %result = 0
%end

!                                                             >> ZERO <<
%integerfn  zero(%record(stackfm)%name  v)
! CHECK DESCRIPTOR FOR (INTEGER) ZERO
   %result = 0 %if v_disp # 0 %or v_base # 0 %or (v_form # constant %and v_form # AV in S)
   %result = 1
%end

!                                                            >> CONST <<
%integerfn  const(%record(stackfm)%name  v)
! CHECK DESCRIPTOR FOR CONSTANT (INTEGER) VALUE
   %result = 0 %unless v_form = constant
   %result = 0 %if v_type > byte
   %result = 1
%end

%integerfn Min Record Size(%record(stackfm)%name A, B)
   %integer N, M
   N = A_format;  N = var(N)_size & 16_7FFF %if N # 0
   M = B_format;  M = var(M)_size & 16_7FFF %if M # 0
   N = M %if N = 0 %or (M # 0 %and M < N)
   %result = N %if N > 0
   Abort("Min Rec Size")
%end

!                                                             >> MULSHIFT <<
%integerfn mulshift(%integer n)
   %integer shift, ref
   ref = 1
   %for shift = 1, 1, 14 %cycle
      ref = ref<<1
      %if ref >= n %start
         %if ref = n %then %result = shift %else %result = -1
      %finish
   %repeat
   %result = -1
%end

!                                                             >> SAME <<
%integerfn  same(%record(stackfm)%name  v,w)
! Test whether or not V and W describe the same object.
   %result = 0 %if v_disp # w_disp %or v_base # w_base
	%result = 0 %if v_type # w_type %or v_form # w_form
	%result = 0 %if v_extra # w_extra %or v_scope # w_scope
   %result = 1
%end

!                                                              >> READ TAG <<
%integerfn  read tag
   %integer s1, s2
   s1 = next sym
   readsymbol(s2)
   readsymbol(next sym)
   %result = s1<<8!s2
%end

!                                                            >> GET D <<
! Read a floating point literal.  Pass 1 treats these as strings
! and leaves it up to us to make a floating point number out of it
! We therefore expect [COUNT]NNN.NNN@NN
%routine  get d
	%integer n
	%longreal p
	n = read tag; read symbol(next sym);	! char count, skip comma
   rvalue = 0
! Start with the bit ahead of the decimal point
   %cycle
      sym = next sym;  read symbol(next sym)
      %exit %if sym = '.'
      n = n-1
      -> power %if sym = '@'
      rvalue = rvalue*10+(sym-'0')
      -> SIGN %if n = 0
   %repeat
   p = 1
   %cycle
      n = n-1;  -> SIGN %if n = 0
      sym = next sym;  read symbol(next sym)
      -> POWER %if sym = '@'
      p = p/10
      rvalue = rvalue + (sym-'0')*p
   %repeat
POWER:
   n = read tag
	! somewhat clunky so that we can be portable to a system that
	! doesn't do exponents
	%while n > 0 %cycle
	   rvalue = rvalue * 10
		n = n - 1
	%repeat
	%while n < 0 %cycle
		rvalue = rvalue / 10
		n = n + 1
	%repeat
SIGN:               ! sign of whole value
   %if next sym = 'U' %start
      read symbol(next sym)
      rvalue = -rvalue
   %finish
%end

! grab a slab of working store in the local stack
%integerfn getwork(%integer size)
	%integer cell;

	cell = worklist(level);
	%while cell # 0 %cycle
		%if gp tags(cell)_info = size %and gp tags(cell)_flags = 0 %start; ! suitable candidate?
			gp tags(cell)_flags = 1;		! mark it as in use
			%result = gp tags(cell)_addr;
		%finish
		cell = gp tags(cell)_link;
	%repeat

	! no space available already - make more
	cell = get gp tag
	frame = (frame - size) & (\1);   ! make them all even boundaries
	gp tags(cell)_addr = frame;
	gp tags(cell)_info = size;
	gp tags(cell)_link = worklist(level);
	worklist(level) = cell;
	gp tags(cell)_flags = 1;		! in use
	%result = gp tags(cell)_addr;
%end

! Return a slab of working store to the free pool.  Note that
! ReturnWork is cautious about what it accepts - it only takes
! in items it has previously given out, so we can call it
! fairly liberally with any old rubbish and it will do the
! right thing
%routine Return Work(%integer addr)
	%integer cell

	cell = worklist(level);
	%while cell # 0 %cycle
		%if gp tags(cell)_addr = addr %start
			%if gp tags(cell)_flags = 0 %then abort("Return Work")
			gp tags(cell)_flags = 0;		! mark it as free
			%return
		%finish
		cell = gp tags(cell)_link;
	%repeat

! Here, work area was not found - it probably wasn't a work area!
%end

! Check to see if a variable is in a work list assigned block
%integerfn Is Work(%record(stackfm)%name v)
	%integer cell

	%if v_base # BP %or v_disp >= 0 %or v_scope # 0 %or v_form # v in s %then %result = 0

	cell = worklist(level);
	%while cell # 0 %cycle
		%if gp tags(cell)_addr = v_disp %start
			%if gp tags(cell)_flags = 0 %then abort("Is Work")
			%result = 1
		%finish
		cell = gp tags(cell)_link;
	%repeat

	%result = 0
%end


!                                                          >> RELEASE <<
%routine  release(%integer  reg)
! Hazard the value in a register
!   abort("Release bad register") %if reg > fr7
   %return %if reg = 0 %or reg > fr7 %or activity(reg) < 0;  ! LOCKED
   activity(reg) = activity(reg)-1
   abort("Release inactive") %if activity(reg) < 0
   claimed = claimed - 1
%end

!                                                            >> CLAIM <<
%routine  claim(%integer  reg)
! Cherish the value in a register
   abort("Claim bad register") %if reg > fr7
   %return %if reg = 0 %or activity(reg) < 0
   activity(reg) = activity(reg)+1
   claimed = claimed+1
%end

!                                                           >> HAZARD <<
! Protect any value in register REG by storing in a temporary.
%routine  hazard(%integer  reg)
   %integer  i, n, t, type
   
         %routine  mod(%record(stackfm)%name  v)
            %switch  sw(0:a in rec)
            v_base = BP
            n = n-1
            -> sw(v_form)
         sw(a in rec):
         sw(av in rec):
         sw(v in rec):
         sw(constant): abort("Mod")

         sw(v in s):   %if v_disp = 0 %and v_scope = 0 %start
                          v_disp = t;  v_form = a in s;  ->out1
                       %finish
         sw(a in s):
         sw(av in s):
      !  change (X in S) to (X in REC)
            v_form = v_form + 3;  v_extra = t;  -> OUT1

         sw(v in r): v_form = v in s;  v_disp = t
            v_type = type
         OUT1:
         %end

   n = activity(reg);  %return %if n <= 0;  ! NOT IN USE OR CLAIMED

   claimed = claimed - n
   activity(reg) = 0
   %if reg >= fr0 %start
		! Note that the FPU can only save the top of the stack.
		! If we need to save something lower down, we need to pop
		! the things above me first...
		%if reg - FR0 >= FPU Stack %then hazard(reg+1); ! and recurse as required
      type = lreal
      t = getwork(8)
		dumpfloprm(FSTQ, BP, t)
   %else
      type = integer
      t = getwork(2)
      dumpmr(MOV, BP,t, reg)
   %finish
   %for i = 1, 1, stp %cycle
      mod(stack(i)) %if stack(i)_base = reg
   %repeat
   abort("Usage Outstanding") %if n # 0;      ! USE STILL OUTSTANDING
%end

!																					>> HAZARD ALL <<
%routine  hazard all
   %integer  j

   %if claimed # 0 %start;            ! at least one register claimed
      hazard(j) %for j = AX,1,FR7
   %finish
%end

!                                                              >> GP REG <<
! Get a general (integer) register
! Note that registers AX, CX, DX, BX are, in order
! numbers 1, 2, 3 and 4 (which is convenient)
%integerfn  gpreg
   %integer  r

! look for an empty one
   %for r = AX,1,BX %cycle
      %result = r %if activity(r) = 0
   %repeat

! look for an unlocked one
   %for r = AX,1,BX %cycle
      %if activity(r) > 0 %start
         hazard(r)
         %result = r
      %finish
   %repeat

   abort("Get Reg")
%end

!                                                              >> PT REG <<
%integerfn  pt reg
! Get a register we can use as a pointer.  We deliberately rotate
! around the candidates to make re-use more likely
   %constbyteintegerarray  pt pref(0:2) =
         7,  8,  4
       ! SI, DI, BX
   %owninteger next = 0
   %integer  r,j

! look for an empty one
   %for j = 1,1,3 %cycle
      r = pt pref(next)
      next = next+1; next = 0 %if next = 3
      %result = r %if activity(r) = 0
   %repeat

! look for an unlocked one
   %for j = 1,1,3 %cycle
      r = pt pref(j)
      %if activity(r) > 0 %start
         hazard(r)
         %result = r
      %finish
   %repeat

   abort("Get PT Reg")
%end

!                                                              >> FPU REG <<
! Returns the next register on the FPU stack
%integerfn  fpu reg
	%result = FR0 + FPU Stack
%end

!                                                              >> GET DISPLAY <<
! return the register to use to access display level <n>
%integerfn getdisplay(%integer l)
   %integer r, lev

   lev = l & 15;                  ! get rid of any relocation info
	%if lev = 0 %then %result = l; ! global
	%if lev = level %then %result = BP; ! local

   ! We now try the 3 pointer register - they are not contiguously
   ! numbered, which is why this is unrolled!
   %if displayhint(BX) = lev %then %result = BX
   %if displayhint(SI) = lev %then %result = SI
   %if displayhint(DI) = lev %then %result = DI

   r = pt reg
   dumprm(MOV, r, DISPLAY, lev*2)
   displayhint(r) = lev
	%result = r
%end

!																					>> SET DOPE VECTOR <<
! Plants a dope vector for a 1-D constant bound array (usually
! OWN or CONST arrays) in the CONST segment, returns the offset
! Note that it also modifies the vlb and vub variables - after
! the call, VLB contains the byte offset for the first member
! and VUB contains the size to allocate for the array in bytes.
%integerfn  set dope vector
   %integer  t, dv

   t = vub-vlb+1
   dv = getcot4(1, vlb, vub, data size)
   vub = t*data size;  vlb = vlb*data size
   %result = dv
%end

!																					>> PERM <<
! calls a PERM and adjusts the stack by SPACE bytes afterwards
%routine  perm(%integer  n, space)
! PERM routines are written in MS C, and they preserve SI and DI,
! but trash the general purpose registers
   hazard(AX); hazard(CX); hazard(DX); hazard(BX)
	dumpextcall(n)
   %if space # 0 %then dumpri(ADD, SP, space)
%end

!																					>> ASSEMBLE <<
! AMODE:
!  -3: initial call
!  -2: alternate record format
!  -1: record format
!   0: begin block
!   1: procedure
!   2: %spec

%routine Assemble(%integer  amode, labs, names)
   %switch c(33:127)
   %record(varfm)%name  v;                ! General purpose pointer
   %record(varfm)%name  procvar;          ! Var of the current procedure we're compiling
   %record(varfm)%name ap, fp;            ! Actual parameter & formal parameter ptrs, used to copy parms to parm area
   %record(stackfm)%name  lhs, rhs;       ! General stack pointers
   %record(stackfm) temp;                 ! A stack record for shuffling and copying
	%integer max frame;							! Used for alternate records to find the largest alternate
   %integer first name;                   ! First descriptor at this level
   %integer staticalloc;                  ! Tag used by pass 3 to fix up this level's stack allocation
   %integer skipproc, lastskip;           ! Used to jump around routines
   %integer First label;                  ! First label at this level
   %integer old frame;                    ! Previous level's static allocation
   %integer  i, j, k, n, t
   %integer opr, val, dv

   %routinespec compile to string(%record(stackfm)%name v)
   %routinespec  load(%record(stackfm)%name  v, %integer  reg)
   %routinespec  assign(%integer  assop)
   %routinespec  array ref(%integer  mode)
   %routinespec  operate(%integer  n)
   %routinespec  compare(%record(stackfm)%name  l,r)
   %routinespec test zero(%record(stackfm)%name v)
   %integerfnspec new tag


! Actual code for Assemble is down around label NEXT


!                                                       >> DEFINE VAR <<
%routine  define var
   %integer  type, form, tf, size, format, s, new, round, dimension
   %integer  dv;              ! dope vector offset
   %owninteger  prim no = 0

   internal id = "";  new = 0;  round = align

   ! Get the var index
   decl = read tag
   %if decl = 0 %start;         ! RECORD FORMAT ELEMENT NAME
      parms = parms-1;  abort("Def Var Parms") %if parms <= names
      decvar == var(parms)
      decvar = 0
   %else
      abort("Def Var Names") %if decl >= parms
      decvar == var(decl)
      %if decl > names %start
         names = decl;  new = 1
         decvar = 0
      %finish
   %finish

   ! Now get the variable name
   %cycle
      sym = next sym;  read symbol(next sym);  %exit %if sym = ','
      %if length(internal id) # 255 %start
         internal id = internal id.to string(sym)
      %finish
   %repeat

   ! Now read the type and form word
   tf = read tag;  read symbol(next sym)
   type = tf>>4;  form = tf&15

   ! And get the size/format word
   size = read tag;  read symbol(next sym)

   ! Map external type numbers into internal equivalents,
   ! and adjust for short/byte/long things
   %if type = integer %and size # 1 %start;			! INTEGER
      type = byte  %and round = 0 %if size = 2
      size = vsize(type)
   %else
      %if type = real %start;                      ! REAL
         type = lreal %if size = 4;                ! LONG REAL
         size = vsize(type)
      %else
         %if type = record %start;                 ! record
            format = size
            decvar_format = format
            size = var(format)_size %if format <= names
         %else
            %if type = string %start;              !  string
               round = 0
               decvar_size = size
               size = size + 1
            %else
               size = vsize(type)
            %finish
         %finish
      %finish
   %finish

   decvar_size = size %if type # string
   decvar_type = type;  decvar_form = form

   ! Now read the Scope word
   otype = read tag
   spec = (otype>>3)&1;  dimension = otype>>8&255;  otype = otype&7

   %if otype # 0 %start;      ! Set external linkage name if appropriate
      %if otype >= external %start
         %if length(alias) # 0 %start
            external id = alias
         %else
            %if otype = system %start
               external id = system prefix.internal id
            %else
               external id = "_".internal id
            %finish
         %finish
         otype = external %if otype <= dynamic; !external, system, dynamic
      %finish
   %finish
   alias = ""

   %if 7 <= form %and form <= 10 %start;        ! PROCEDURE
      block type = 1 + spec;                    ! 1 for normal proc, 2 for spec
      %if otype # 0 %and spec # 0 %start;       ! external spec
         %if otype = primrt %start
            primno = primno + 1
            decvar_level = 128
            decvar_disp = prim no
            %return
         %finish
			decvar_disp = externalref(external id)
         decvar_level = 0
         decvar_scope = EXT
         %return
      %finish

      %if in params = 0 %start;              ! NOT A PARAMETER
         potype = otype
         %if new # 0 %start;             ! NEW NAME
            decvar_disp = new tag;       ! Procedure ID
         %finish
         %return
      %finish

      otype = 0;  size = 2;  data size = 2;  ! procedure parameter

   %else;   ! This is not a procedure declaration

      data size = size

      %if form # 1 %start
         Round = Align
         %if type = general %start;                ! General %name
            decvar_extra = in params;            ! FOR LABELS
            size = 4
         %else
            %if form = array %or form = name array %start
               ! We will fill in dimensions and allocate space when
               ! we are told the bounds later
               size = 0
               data size = 2 %if form = name array
            %else
               %if form = array name %or form = name array name %start
                  decvar_dim = dimension
                  size = 4;  round = align;         ! array header
               %else
                  size = 2;                       ! integer (etc) %name
               %finish
            %finish
         %finish
      %finish
   %finish

! Now deal with OWN (or const/extern/etc) data items

   %if otype # 0 %start;      ! OWN DATA
      %if otype = con %start;      ! CONST INTEGER ETC.
         data size = 0 %if type = string %and form = 1;    ! use actual size for plain strings
         %if form = 2 %or form = arrayname %or form = namearrayname %start
            otype = 0;        ! Treat as special later
         %finish
      %else;                  ! OWN, not CONST
         gfix(round);         ! so make it even if needed
      %finish

      ! set globals used by our data collection utilities
      own type = type;  own form = form
      own type = integer %and data size = 2 %if form = 2; ! %name's are really integers

      %if spec = 0 %start
         %if form = array %or form = name array %start
            gfix(align)
            dv = set dope vector;      ! N.B.  changes vlb, vub
            ! We treat OWN and CONST arrays identically - both are in data segment
            gfix(align)
            decvar_disp = datatp - vlb;  decvar_level = 0; decvar_scope = DATA
            decvar_pbase = dv;                  ! save the dope vector pointer here
            decvar_dim = 1;                     ! own arrays are always 1-D
            fill external(DATA, decvar_disp, external id) %if otype = external
         %finish
      %else
         decvar_level = 0; decvar_scope = EXT
         decvar_disp = external ref(external id)
      %finish
      %return
   %finish

   %if form = 3 %start;         !%label
      decvar_disp = new tag
      %return
   %finish

   %if form = switch %start
      size = vub - vlb
      %if swtp + size > Max Switch %then abort("Switch Table Full")
      decvar_scope = SWT;  decvar_disp = swtp - vlb
      decvar_extra = set dope vector
      %for s = swtp, 1, swtp + size %cycle
         swtab(s) = 0;     ! should really deal with undefined switch entries
      %repeat
      swtp = swtp + size + 1
      %return
   %finish

   %if form = record format %start
      %if in params # 0 %start
         frame = decvar_size %if decvar_size > frame
      %else
         block type = -1;  spec = -1
      %finish
      %return
   %finish

   ! Here we've got an ordinary local variable, parameter or record entry
   
   decvar_level = level
   %if in params = 0 %start;      ! local variable
      frame = (frame - size) & (\round)
      decvar_disp = frame
   %else;                     ! parameter or record
      %if block type > 0 %start;   ! procedure parameters
			frame = (frame + size + 1) & (\1);	! parameters are always word aligned
			decvar_disp = frame;						! offset will be adjusted at '}'
		%else;								! records
			frame = (frame + round) & (\round)
			decvar_disp = frame
			frame = frame + size
         decvar_level = 0;          ! no base register
      %finish
   %finish
%end;        !    define var


!----------------------------------------------------------------------
! Stack manipulation routines
!----------------------------------------------------------------------

!                                                             >> POP STACK <<
! Pop the top of the stack
%routine  Pop Stack
   %if stp = 0 %then abort("Pop")
   monitor(top, "Pop") %if diagnose&1 # 0
   stp = stp - 1
   %if stp # 0 %then top == stack(stp) %else top == null
%end

!                                                             >> POP REL <<
! Pop the top of the stack, and release its' register
%routine  pop rel
   release(top_base)
   pop stack
%end

!                                                           >> PUSH VAR <<
! Push a descriptor on the stack corresponding to Var "var no"
! We map the variable form to a stack form, and assign a register
! for the base if it is non local.  Finally, we absorb the scope
! into the base register.
%routine  push var(%integer  var no)
   %record(varfm)%name  w
   %constbyteintegerarray fmap(0:15) = %c
    0,  4,        6,     10,          4,        0,
!   0, v in s, a in s, pgm label, recordformat, 0,
    6,      0, 1,      4,      1,      4,      6,      4,      6,      0
!   switch, 0, v in r, v in s, v in r, v in s, a in s, v in s, a in s, 0
   
   abort("Push Var Idx") %unless 0 <= var no %and var no <= max vars
   w == var(varno)
   stp = stp + 1
   %if stp > Max Stack %then abort("Push V Stack Overflow")
   top == stack(stp)
   top = 0

   ! Translate "level" into "base register" - if it is non local
   ! we flag it by adding 16 to the desired level, which later will
   ! force us to pick up a pointer register
   %if w_level # 0 %start
      %if w_level = level %then top_base = BP %else top_base = w_level + 16
   %else
      top_base = 0
   %finish

   top_scope = w_scope
   top_type = w_type

   ! AFORM contains the real original declared form, while
   ! FORM contains the on-the-stack subset of possible forms
   top_aform = w_form
   top_form = fmap(w_form)
	top_dim = w_dim
   top_disp = w_disp
   top_scope = w_scope
   top_format = w_format
   top_size = w_size
   top_extra = w_extra
   top_pbase = w_pbase

   top_varno = varno
   monitor(top, "Var stack") %if diagnose&1 # 0
%end

!                                                           >> PUSH COPY <<
! Push a duplicate of a stack record onto the stack
%routine  push copy(%record(stackfm)%name  v)
   stp = stp + 1
   %if stp > Max Stack %then abort("Stack Const")
   top == stack(stp)
   top = v
   monitor(top, "Stack Copy") %if diagnose&1 # 0
%end

!                                                          >> PUSH CONST <<
! Push a constant on the stack
%routine  push const(%integer  n)
   stp = stp + 1
   %if stp > Max Stack %then abort("Stack Const")
   top == stack(stp)
   top = 0
   top_disp = n
   top_type = integer
   top_form = constant
   monitor(top, "push const") %if diagnose&1 # 0
%end

!-----------------------------------------------------------------------
!STRING PROCESSING
!-----------------------------------------------------------------------

!                                                      >> DUMP STRING <<
! writes the latest received string (in Current String) to
! either const or data segment (depending on SIZE).  Returns
! the offset in that segment of the string.  The size of space
! to reserve is in SIZE.  In the special case where SIZE is zero
! this is a fixed constant string and can be re-used in the
! constant table, otherwise we put it in the data segment
%integerfn  dump string(%integer  size)
   %integer  j, p
   
   %if size = 0 %start;       ! Fixed constant string
      %result = getcots(current string)
   %finish

! check for overflow
   %if cslen+1 > size %start
      ! String constant too long - warn and truncate
      warn(5);  current string(0) = size-1
   %finish

   p = datatp
   %for j = 0,1,size-1 %cycle
      gbyte(current string(j))
   %repeat
   %result = p
%end

!                                                       >> GET STRING <<
! Read a string literal from the iCode stream
%routine  get string
   %integer  l
   l = next sym;                  !length
   cslen = 0
   %while l > 0 %cycle
      l = l-1
      read symbol(next sym)
      cslen = (cslen+1)&255;  current string(cslen) = next sym
   %repeat

   ! if this is about to be used as a literal, put it straight into
   ! the CONST segment and stack it, otherwise leave it in curr string to see
   ! what comes next and stack a dummy zero
   readsymbol(next sym)
   %if next sym # 'A' %and next sym # '$' %start
      otype = con;        ! anonymous %const
      push const(dump string(0));  top_type = string
      top_base = 0; top_scope = COT; top_form = VinS;  top_format = cslen+1
   %else
      push const(0);         ! explicit string initialisation coming next
   %finish
%end

!                                                    >> REAL CONSTANT <<
! Put the current rvalue in the constant table, return the offset
%integerfn  real constant
	%integer w1, w2, w3, w4

! Warning - this is horribly non-portable, and relies on this
! being a native compiler.  For cross-compiling, think again!
! It's also a wee bit messy 'cos we've redefined the word INTEGER
! so we can't use the built-in map!
	w1 = byteinteger(addr(rvalue)) ! byteinteger(addr(rvalue)+1)<<8
	w2 = byteinteger(addr(rvalue)+2) ! byteinteger(addr(rvalue)+3)<<8
	w3 = byteinteger(addr(rvalue)+4) ! byteinteger(addr(rvalue)+5)<<8
	w4 = byteinteger(addr(rvalue)+6) ! byteinteger(addr(rvalue)+7)<<8

   %result = getcot4(w1, w2, w3, w4)
%end;                  ! real constant

!-------------------------------------------------------
!LABEL PROCESSING
!
! Labels fixups are handled by pass 3 - we just plant
! numerical labels for code locations, and then jump to or call
! those labels.  Pass 3 turns them into real locations.
! Unfortunately Pass 3 needs unique label numbers whereas
! Pass 1 produces lame local label numbers that can
! be reused once they've been defined.  We therefore
! maintain an indirect database to map Pass 1 label numbers
! into unique tags

!                                                          >> NEW TAG <<
! Get the next consecutive Pass 3 label ID
%integerfn  new tag
   %owninteger free tag = 999

   free tag = free tag + 1
   %result = free tag
%end

!                                                        >> NEW LABEL <<
! Get the next available label database index
%integerfn  New Label
   labs = labs+1;  abort("Labels") %if labs > Max Labs
   %result = labs
%end

!                                                        >> FIND LABEL<<
! return the index in our label table of the Pass 1 label
%integerfn  Find Label(%integer  label)
	%integer  lp

   lp = labs
   %while lp # first label %cycle
      %result = lp %if labels(lp)_id = label
      lp = lp-1
   %repeat
   %result = 0
%end

!                                                     >> DEFINE LABEL <<
! This label is "here"
%routine  Define Label(%integer  label)
   %integer lp
   %record(labelfm)%name  l

	lp = Find Label(label)
	%if lp = 0 %start;				! Not yet been used
		lp = New Label
		l == labels(lp)
		l_id = label
		l_tag = new tag
	%else
		l == labels(lp)
		%if l_tag & 16_8000 # 0 %and label > 0 %then l_tag = new tag
	%finish

	dump label(l_tag)
	l_tag = l_tag ! 16_8000
   uncond jump = 0;            ! You can get here
%end;       ! define label

!                                                          >> JUMP TO <<
! A wrapper for conditional jumps to labels that we're going
! to map into tags
%routine  Jump To(%integer  label, op, flag)
	%record(labelfm)%name  l
	%integer  lp

	lp = Find Label(label)
	%if lp = 0 %start
		lp = New Label
		l == labels(lp)
		l_id = label
		l_tag = new tag
	%else
		l == labels(lp)
		%if flag # 0 %and l_tag & 16_8000 # 0 %then l_tag = new tag
	%finish

	dump jump(op, l_tag & 16_7FFF)

	%if op = JMP %then uncond jump = nextcad
%end;            ! jump to


!-------------------------------------------------------
! Stack variable transformations
!-------------------------------------------------------

!                                                           >> REDUCE <<
! Convert a variable which is addressed in a Rec into a simple variable
! by loading the indirect value into a register and changing the form
%routine  reduce(%record(stackfm)%name  v)
   %integer  type, form, disp, scope
   
   form = v_form - 3;         ! X in REC => X in S
   type = v_type
   disp = v_disp
! Here's a trick - we've got two displacements, DISP and EXTRA, but only
! one SCOPE hint.  Which does it belong to?  If the REC form came from
! a HAZARD then the scope belongs to the DISP, but for all other cases
! the scope belongs to the EXTRA.  If we got here through HAZARD then
! the BASE will be BP - for all other cases it will be either a different
! register, or zero.
   %if v_base = BP %start
      scope = v_scope
      v_scope = 0
   %else
      scope = 0
   %finish
   v_disp = v_extra;  v_type = integer;  v_form = v in s
   load(v, anyp)
   v_type = type;  v_form = form
   v_disp = disp;  v_scope = scope
%end

!                                                             >> AMAP <<
! convert V into a descriptor for the address of V
%routine  amap(%record(stackfm)%name  v)
   %integer f
   %constintegerarray  addr map(0:15) =
    -1, -2, -3, -4, 5,       -5, 4,      8,         -6, 7,
!   -1, -2, -3, -4, av in s, -5, v in s, av in rec, -6, v in rec,
    -7, -8, -9, -10, -11            , -12
!   -7, -8, -9, -10, -11 {PGM LABEL}, -12 {record format}

! ABD - should be code here to deal with ADDR(pgm label)

   f = addr map(v_form)
   %if f < 0 %start
      monitor(v, "AMAP target")
      abort("AMAP")
   %finish

! Try to simplify some forms...
   %if v_disp = 0 %and v_scope = 0 %start
      %if f = AVinS %start
         %if v_base = 0 %then f = constant %else f = VinR
      %else
         %if (f = VinREC %or f = AVinREC) %start; ! eliminate redundant LOAD
            %if f = VinREC %then f = AinS %else f = VinS
            v_disp = v_extra
         %finish
      %finish
   %finish
   v_type = integer
   v_form = f
%end

!                                                             >> VMAP <<
! The inverse of AMAP:  i.e. vmap(amap(x)) => x
%routine  vmap(%record(stackfm)%name  v)
   %integer  f, t
   %constintegerarray  var map(0:8) =
            4,       4,   -1, -2,    6,      4,   -3,    9,        7  
   !      v in s, v in s, -1, -2, a in s, v in s, -3, a in rec, v in rec

   %if v_form = a in s %or v_form = a in rec %start
      t = v_type
      amap(v)
      load(v,anyp)
      v_type = t;  v_form = VinS
   %finish
   f = var map(v_form);  abort("VMap") %if f < 0
   v_form = f
%end;            !  v map

!                                                          >> ADDRESS <<
! convert V into a form in which it is directly addressable
! that means either V in R, V in S or Constant
%routine  address(%record(stackfm)%name  v)
   %integer  type, form, r
   
   monitor(v, "ADDRESS") %if diagnose&2 # 0

   form = v_form;  type = v_type
   %if form >= V in REC %start
      reduce(v);  form = v_form
   %finish

! Now pick up a base register if we needed one...
   %if v_base > 16 %start
      v_base = get display(v_base - 16)
      claim(v_base)
   %finish

   %return %if form = V in R %or form = constant

   %if form = AV in S %start
      %if v_base = 0 %start
         v_form = constant
      %else
         %if v_disp = 0 %and v_scope = 0 %start
            v_form = V in R
         %else
            load(v, any)
         %finish
      %finish
      %return
   %finish

! It is possible to end up with an in-store form, but the
! base register isn't one that can be used in an address...
   %if v_base # 0 %and v_base < BX %start
      r = PT Reg; claim(r)
      dumprr(MOV, r, v_base); release(v_base)
      v_base = r
   %finish

   %return %if form = V in S
   
   %if form = A in S %start
      v_form = V in S;  v_type = integer
      load(v,anyp)
      v_type = type;  v_form = V in S;  v_disp = 0
   %finish
%end;            ! address

!                                                             >> LOAD <<
! Load variable V into register R
! Along the way any register the variable owned is released, and
! the new register is claimed.
%routine Load(%record(stackfm)%name v, %integer r)
   %switch f(0:9)
   %integer ptr, op

   monitor(v, "LOAD") %if diagnose&2 # 0

	%if r = anyf %then ->reals

! If the request is one of the variations on "any" then we need
! to first allocate a target register.  First, we make a local
! adjustment because we can't load bytes into "any" register,
! only into the GP registers...
	%if v_type = byte %start
		%if r = any %then r = anyg
! What's more, there is only one register that is both a pointer
! and a legal byte destination
		%if r = anyp %then r = BX
	%finish

! We also map the virtual display into a real register if we
! need to.  Also, it is possible that an in-store form may
! be derived from a non-pointer register, so we fix that too.
   %if v_base > 16 %then %start
      v_base = get display(v_base - 16)
      claim(v_base)
   %else
      %if v_base # 0 %and v_base < BX %and v_form >= V in S %start
         ptr = pt reg
         dumprr(MOV, ptr, v_base)
         claim(ptr); release(v_base)
         v_base = ptr
      %finish
   %finish

! Now go ahead and allocate a register
   %if r = any %then %start
! If we've got a base, it's not in use by anyone else, and isn't a display register, use it
      %if v_base # 0 %and activity(v_base) = 1 %and displayhint(v_base) = 0 %start
         r = v_base
      %else
         r = gp reg
      %finish
   %else
      %if r = anyg %then %start
         %if 0 < v_base %and v_base <= BX %and activity(v_base) = 1 %start
            r = v_base
         %else
            r = gp reg
         %finish
      %else
         %if r = anyp %then %start
            %if activity(v_base) = 1 %and (v_base = BX %or v_base = SI %or v_base = DI) %then %start
               r = v_base
            %else
               r = pt reg
            %finish
         %else
			   %if v_base = r %start
				   %if activity(r) > 1 %start;      ! protect other uses
					   release(r);  v_base = 0;      ! Hide my ownership for the moment
					   hazard(r);							! Zap everybody else
					   claim(r);    v_base = r;      ! Get it back
				   %finish
			   %else
				   hazard(r)
			   %finish
         %finish
      %finish
   %finish
   -> f(v_form)

f(constant):
ldconst:
      %if v_disp = 0 %and v_scope = 0 %start
         dumprr(XOR, r, r)
      %else
         dumprioffset(MOV, r, v_scope, v_disp)
      %finish
setup:v_base = r
      v_disp = 0
      v_scope = 0
      v_form = v in r
      claim(r)
      %return

f(v in r):
      %return %if v_base = r
      dumprr(MOV, r, v_base)
      release(v_base)
      -> setup

f(av in r):
f(a in r):
      Abort("Unexpected Stack Form")
         
f(a in s):
      ! is the register a pointer?
      %if r = BX %or r = SI %or r = DI %start
         ptr = r
      %else
         ptr = pt reg
      %finish
      dumprm(MOV, ptr, v_base!v_scope, v_disp)
		release(v_base); claim(ptr)
      v_base = ptr
      v_disp = 0
      v_scope = 0
      ! fall through to normal V in S

f(v in s):
      %if v_type = integer %start
         dumprm(MOV, r, v_base!v_scope, v_disp)
      %else
         %if v_type = byte %start
            dumprm8(MOV, r+16, v_base!v_scope, v_disp)
            dumprr8(XOR, r+20, r+20)
            v_type = integer
         %else; ! reals
				abort("Load Real")
         %finish
      %finish
		release(v_base)
      ->setup

f(av in s):
      %if v_base # 0 %start
         dumprm(LEA, r, v_base!v_scope, v_disp)
			release(v_base)
			v_type = integer
			->setup
      %finish
		! else
      ->ldconst
      
f(v in rec):
f(av in rec):
f(a in rec):
      reduce(v)
      ->f(v_form)

! Equivalents for real numbers...

reals:;		! because there's very little clever we can do, we first
				! simplify somewhat...
		Address(v)

		! Now it's either Constant, V in R or V in S - we now turn them
		! all into V in S - the only thing we can load

      ! Start with one we have no instructions for, and promote it to
		! something we know how to handle...
		%if v_type = byte %then load(v, any)

		%if v_form = V in R %start
			%return %if v_base >= FR0
			! This must be an integer in a CPU register - we need to store it
			! before we can use it
			v_disp = getwork(2)
			dumpmr(MOV, BP, v_disp, v_base)
			release(v_base)
			v_base = BP
			v_scope = 0
			v_form = V in S
			! Now it looks like an integer V in S
		%finish

		%if v_form = constant %start;		! This is an integer constant
			%if v_disp = 0 %start;			! We have a special instruction for zero
				r = FR0 + FPU Stack
				dumpflopspec(FLDZ)
				-> set up real
			%finish
			! Otherwise, we need it in store
			v_disp = getcotw(v_disp)
			v_form = V in S
			v_base = 0
			v_scope = COT
		%finish

	! Now everything that's left is a V in S

      %if v_type = integer %start
         op = FILD
      %else
			%if v_type = real %start
				op = FLDD
			%else
				op = FLDQ
         %finish
      %finish

		! register is going to be the top of stack
		r = FR0 + FPU Stack
		
		dumpfloprm(op, v_base!v_scope, v_disp)
		release(v_base)
set up real:
		v_base = r
		claim(r)
      v_disp = 0;
      v_form = v in r
		v_type = real
%end

!                                                          >> OPERATE <<
! perform the operation OP on the top two elements of the stack.
!   (single element for unary operators)
%routine  operate(%integer  op)
   %record(stackfm)%name  lhs, rhs
   %integer assign pending, work, value, s
   %switch oper(1:17), roper(1:17), fold(1:17)
%constintegerarray opmap(1:17) = 
   14,  18,  23,  22,  0,   15, 17,  19,  20,  21,  22,  0,   0,    0,    7,   6,   0
!  add, sub, mul, div, conc,and,or,  xor, lsh, rsh, rem, exp, rexp, rdiv, not, neg, abs

%constintegerarray flopmap(1:17) = 
   42,   43,   45,   46,   0, 0, 0, 0, 0, 0, 0,   0,   0,    46,   0, 49,   50
!  fadd, fsub, fmul, fdiv, 0, 0, 0, 0, 0, 0, rem, exp, rexp, fdiv, 0, fchs, fabs

%constintegerarray indec(-1:1) = 4, 0, 5; ! decrement, and increment opcodes

   %routine swap
      %record(stackfm) temp
      temp = lhs
      lhs = rhs
      rhs = temp
   %end

   assign pending = 0
   rhs == top
   %if op < Unaries %then %start
      lhs == stack(stp-1)
      %if lhs_type = real %or lhs_type = lreal %then ->reals
   %finish

   %if rhs_type = real %or rhs_type = lreal %then ->reals

   %if rhs_form = constant %and (op >= Unaries %or lhs_form = constant) %then ->fold(op)

   ! now look for optimisations for x = x <op> whatever
   %if next sym = 'S' %or next sym = 'j' %start;           ! the next task is an assignment
      %if op >= Unaries %start
         %if same(top, stack(stp-1)) # 0 %then assign pending = 1
      %else
         %if same(lhs, stack(stp-2)) # 0 %then assign pending = 1
      %finish
   %finish

   ->oper(op)

oper(op not):
oper(op neg):
   ! we optimise for e.g. fred = -fred as one instruction
   %if assign pending # 0 %then %start
      read symbol(next sym)
      address(rhs)
		%if rhs_type = byte %start
			dumpum8(opmap(op), rhs_base!rhs_scope, rhs_disp)
		%else
			dumpum(opmap(op), rhs_base!rhs_scope, rhs_disp)
		%finish
      pop rel
      pop rel
      %return
   %finish
   load(rhs, any)
   dumpur(opmap(op), rhs_base)
   %return

!  8086 has no "abs" instructions, so we do a test and jump
oper(op abs):
   load(rhs, any)
   dumpri(CMP, rhs_base, 0)
	work = new tag
	dumpjump(JGE, work)
   dumpur(NEG, rhs_base)
	dumplabel(work)
   %return


oper(op add):
   %if lhs_form = constant %then swap
   ! and fall through to minus

oper(op sub):
   ! First look for fred = fred + <whatever>
   ! We can only safely do this for bytes if we're jamming or ignoring overflow
   %if assign pending # 0 %and %c
      (lhs_type = integer %or control & check capacity = 0 %or Next Sym = 'j') %then %start
      readsymbol(next sym)  ; ! we will do the assignment ourselves
      address(lhs)      ; ! make LHS accessible
      %if rhs_form = constant %then %start
         value = rhs_disp
			%if value # 0 %start
				%if op = op sub %then value = -value
				! look for increment or decrement instructions
				%if value < 2 %and value > -2 %then %start
					%if lhs_type = byte %start
						dumpum8(indec(value), lhs_base!lhs_scope, lhs_disp)
					%else
						dumpum(indec(value), lhs_base!lhs_scope, lhs_disp)
					%finish
				%else
					%if lhs_type = byte %start
						dumpmi8(opmap(op), lhs_base!lhs_scope, lhs_disp, rhs_disp)
					%else
						dumpmi(opmap(op), lhs_base!lhs_scope, lhs_disp, rhs_disp)
					%finish
				%finish
			%finish
      %else;         ! RHS not a constant
         load(rhs, any)
			%if lhs_type = byte %start
				dumpmr8(opmap(op), lhs_base!lhs_scope, lhs_disp, rhs_base+16)
			%else
				dumpmr(opmap(op), lhs_base!lhs_scope, lhs_disp, rhs_base)
			%finish
      %finish
      pop rel
      pop rel
      pop rel
      %return
   %finish

! So, there is no assign pending        

   %if rhs_form = constant %then %start
      value = rhs_disp
      %if op = op sub %then value = -value
		! If it is already an address, do the math on the address offset
      %if lhs_form = avins %or lhs_form = avinrec %start
         lhs_disp = lhs_disp + value
      %else
         load(lhs, any)
			! We don't particulary try for it, but if we ended up with a pointer
			! register, we might as well convert this to use the address form...
			%if lhs_base = BX %start;		! BX is the only GP reg that's also a pointer
				lhs_form = avins
				lhs_disp = value
			%else;								! otherwise, don't bother deferring the operation
				! look for increment or decrement instructions
				%if value < 2 %and value > -2 %then %start
					%if value # 0 %then dumpur(indec(value), lhs_base)
				%else
					dumpri(opmap(op), lhs_base, rhs_disp)
				%finish
			%finish
      %finish
   %else;                  ! not a constant
      %if op = op add %and rhs_form = v in r %then swap; ! commutative, so flip it
      load(lhs, any)
		%if rhs_type = byte %start
			load(rhs, any)
		%else
		   address(rhs)
		%finish
      dumprv(opmap(op), lhs_base, rhs)
   %finish
   pop rel;    ! the RHS
   %return


oper(op and):
oper(op or):
oper(op xor):
!  Logical ops are a subset of ADD - similar behaviour, but no inc/dec/addr short forms
   %if lhs_form = constant %then swap

   ! First look for fred = fred <op> <whatever>
   %if assign pending # 0 %then %start
      readsymbol(next sym);   ! we will do the assignment ourselves
      address(lhs);				! make LHS accessible
      %if rhs_form = constant %then %start
         value = rhs_disp
			%if lhs_type = byte %start
            warn(8) %if rhs_disp & (\255) # 0
				dumpmi8(opmap(op), lhs_base!lhs_scope, lhs_disp, rhs_disp)
			%else
				dumpmi(opmap(op), lhs_base!lhs_scope, lhs_disp, rhs_disp)
			%finish
      %else;         ! RHS not a constant
         load(rhs, any)
			%if lhs_type = byte %start
				dumpmr8(opmap(op), lhs_base!lhs_scope, lhs_disp, rhs_base+16)
			%else
				dumpmr(opmap(op), lhs_base!lhs_scope, lhs_disp, rhs_base)
			%finish
      %finish
      pop rel; ! RHS
      pop rel; ! LHS
      pop rel; ! Assignment destination
      %return
   %finish

! So, there is no assign pending        

   %if rhs_form = constant %then %start
      value = rhs_disp
      load(lhs, any)
      dumpri(opmap(op), lhs_base, value)
   %else;                  ! not a constant
      %if rhs_form = v in r %then swap; ! all these are commutative, so flip it to make it easier
      load(lhs, any)
		%if rhs_type = byte %and op = op and %start;	! AND needs all the bits to make sense
			load(rhs, any);									! NB Load changes type to Integer
		%else
			address(rhs)
		%finish
		%if rhs_type = byte %start;		! must be V in S - everything else would be Integer
			dumprm8(opmap(op), lhs_base+16, rhs_scope!rhs_base, rhs_disp)
		%else
			dumprv(opmap(op), lhs_base, rhs)
      %finish
   %finish
   pop rel;    ! the RHS
   %return

oper(op mul):
   %if lhs_form = constant %or rhs_base = AX %then swap

   %if rhs_form = constant %then %start
      value = rhs_disp
      %if value = 0 %then %start; ! mul by zero is zero
         release(lhs_base)
         lhs = rhs
         pop stack
         %return
      %finish
      %if value = 1 %then %start; ! mul by 1 is the identity
         pop stack
         %return
      %finish
      s = mulshift(value);        ! find a shift factor
      %if s > 0 %then %start
         rhs_disp = s
         op = op lsh
         -> shift it
      %finish
      ! 8086 multiply instruction doesn't have an immediate operand form
      ! so we use an entry in the constant table...
      rhs_base = 0; rhs_scope = COT; rhs_disp = getcotw(value)
      rhs_form = V in S
		! and fall through to the not-a-constant path
   %finish

do mul:
   load(lhs, AX)
   address(rhs)
   hazard(DX)
   %if rhs_form = V in R %start
      dumpur(IMUL, rhs_base)
   %else
      dumpum(IMUL, rhs_base!rhs_scope, rhs_disp)
   %finish
   pop rel
   %return

oper(op div):
oper(op rem):
   load(lhs, AX)
   address(rhs)
   hazard(DX)
   dumpsimple(CWD)
	! Plain 8086 Divide instruction also has no immediate operand form, so
	! we move constants to the COT
	%if rhs_form = constant %start
		%if rhs_disp = 0 %then warn(1)
      rhs_base = 0; rhs_scope = COT; rhs_disp = getcotw(rhs_disp)
      rhs_form = V in S
	%finish
   %if rhs_form = V in R %start
      dumpur(IDIV, rhs_base)
   %else
      dumpum(IDIV, rhs_base!rhs_scope, rhs_disp)
   %finish
   pop rel
   %if op = op div %then %start
      lhs_base = AX
   %else
      lhs_base = DX
      release(AX)
      claim(DX)
   %finish
   %return

oper(op lsh):
oper(op rsh):
shift it:
   %if assign pending # 0 %and %c
      (op = op rsh %or lhs_type = integer %or control & check capacity = 0 %or Next Sym = 'j') %then %start
      readsymbol(next sym);   ! we will do the assignment ourselves
      address(lhs);				! make LHS accessible
		! Note that in-register shifting it is actually quicker for small
		! constant shifts to plant multiple "shift by one" instructions than
		! it is to load up CL with the shift count.  That is NOT true for
		! in-store shifts, since there's a fetch and store for every bit shift
		! Hence the test for disp = 1 below
		%if rhs_form = constant %and rhs_disp = 1 %start
			%if lhs_type = byte %start
	         dumpmi8(opmap(op), lhs_base!lhs_scope, lhs_disp, 1)
			%else
	         dumpmi(opmap(op), lhs_base!lhs_scope, lhs_disp, 1)
			%finish
      %else;         ! RHS not a one
			! Since the shift instruction only uses the bottom 4 bits of the
			! value in CX, the value is "byte safe".  Rather than do a full
			! "load(rhs,CX)" we therefore fiddle about and do it the hard way
			! to save redundant coding
			%if rhs_type = byte %start
				hazard(CX)
				address(rhs)
				dumprm8(MOV, CL, rhs_scope!rhs_base, rhs_disp)
			%else
				load(rhs,CX)
			%finish
			%if lhs_type = byte %start
	         dumpmr8(opmap(op), lhs_base!lhs_scope, lhs_disp, CL)
			%else
	         dumpmr(opmap(op), lhs_base!lhs_scope, lhs_disp, CX)
			%finish
      %finish
      pop rel; ! RHS
      pop rel; ! LHS
      pop rel; ! Assignment destination
      %return
	%finish

   ! deal with constant shifts first...
   %if rhs_form = constant %then %start
      value = rhs_disp
      %if value <= 4 %then %start;     ! plant individual shift 1 instructions
         load(lhs, any)
         %while value > 0 %cycle
            dumpri(opmap(op), lhs_base, 1)
            value = value - 1
         %repeat
      %else
         load(rhs, CX)
         load(lhs, any)
         dumprr(opmap(op), lhs_base, CX)
      %finish
   %else;                              ! RHS variable
		! Since the shift instruction only uses the bottom 4 bits of the
		! value in CX, the value is "byte safe".  Rather than do a full
		! "load(rhs,CX)" we therefore fiddle about and do it the hard way
		! to save redundant coding
		%if rhs_type = byte %start
			hazard(CX)
			address(rhs)
			dumprm8(MOV, CL, rhs_scope!rhs_base, rhs_disp)
         release(rhs_base)
         rhs_base = CX
         claim(CX)
		%else
			load(rhs,CX)
		%finish
      load(lhs, any)
		dumprr(opmap(op), lhs_base, CX);
   %finish
   pop rel
   %return

oper(op exp):
   %if rhs_form = constant %then %start
      %if rhs_disp = 0 %start
         pop rel
         pop rel
         push const(1)
         %return
      %finish

      %if rhs_disp = 1 %then %start
         pop rel
         %return
      %finish

      %if rhs_disp = 2 %then %start
         rhs = lhs
         claim(rhs_base)
         ->do mul
      %finish
   %finish

   load(rhs, any)
   dumpur(PUSH, rhs_base)
   pop rel
   load(lhs, any)
   dumpur(PUSH, lhs_base)
   release(lhs_base)
   perm(iexp, 4)
   lhs_base = AX; claim(AX)
   lhs_form = V in R
   %return

oper(op rexp):
oper(op rdiv):
   abort("Oper unexpected op")

!-----------------------------------------------
! Fold constant expressions at compile time
fold(op neg):  value = -rhs_disp; -> set unary
fold(op not):  value = \rhs_disp; -> set unary
fold(op abs):  value = rhs_disp; %if value < 0 %then value = -value; -> set value
fold(op add):  value = lhs_disp + rhs_disp; -> set value
fold(op sub):  value = lhs_disp - rhs_disp; -> set value
fold(op or):   value = lhs_disp ! rhs_disp; -> set value
fold(op and):  value = lhs_disp & rhs_disp; -> set value
fold(op xor):  value = lhs_disp !! rhs_disp; -> set value
fold(op lsh):  value = lhs_disp << rhs_disp; -> set value
fold(op mul):  value = lhs_disp * rhs_disp; -> set value
fold(op rsh):  value = lhs_disp >> rhs_disp; -> set value
fold(op exp):  %if rhs_disp < 0 %then abort("Fold -ve Exp")
               value = 1
               %for op=1, 1, rhs_disp %cycle
                  value = value * lhs_disp
               %repeat
               -> set value
fold(op rem):
fold(op div):  value = rhs_disp;
               warn(1) %and value = 1 %if value = 0
               value = lhs_disp // value
               %if op = op div %then -> set value
               value = lhs_disp - (rhs_disp * value)
               -> set value

fold(op rexp): abort("No reals yet")
fold(op rdiv): abort("No reals yet")

set value:
   pop stack
set unary:
   top_disp = value
   %return

fold(op conc): abort("Fold Conc")

!--------------------------------------------------------------------
! String operations - the only one is concatenate...

oper(op conc):

   %if assign pending # 0 %start;	! It's S = S.T
		amap(lhs)
		load(lhs, any)
		dumpur(PUSH, lhs_base)
		amap(rhs)
		load(rhs, any)
		dumpur(PUSH, rhs_base)
		pop rel
		pop rel
      dumppushi(0, lhs_size)
		%if Next Sym = 'S' %then perm(sconc, 6) %else perm(sjconc, 6)
		! and finally, skip the pending assignment, and drop the LHS
		readsymbol(Next Sym)
		pop rel
		%return
	%finish

	! here we've got T.U - if T is already in a WORK location
	! we've got a simple append.  If it is a user variable, we've
	! got to both copy it to a temp area and do the append
	
	%if Is Work(lhs) = 0 %start;		! Not a work area
		work = getwork(256)
		push const(work)
		top_form = av in s
		top_base = BP
		load(top, any)
		dumpur(PUSH, top_base)
		pop rel
		amap(lhs)
		load(lhs, any)
		dumpur(PUSH, lhs_base)
		release(lhs_base)
      dumppushi(0, 255)
		perm(smove, 6)
		! Now we need to redefine the LHS as our temporary area
		lhs = 0;			! gratuitous clear-it-all-out
		lhs_type = string
		lhs_form = V in S
		lhs_base = BP
		lhs_disp = work
		lhs_size = 255
	%finish

	! Here we are doing an in-situ concatenation

   ! We want to leave the result as a normal variable, so we
	! suck up a copy for the AMAP fiddling
	push copy(lhs)
	amap(top)
   load(top, any)
   dumpur(PUSH, top_base)
	poprel
   amap(rhs)
   load(rhs, any)
   dumpur(PUSH, rhs_base)
   pop rel
   dumppushi(0, lhs_size)
   perm(sconc, 6)
   %return

Reals:
	%if op < Unaries %then load(lhs, anyf)
	%if op # op rexp %then load(rhs, anyf)
	->roper(op)

roper(op neg):
roper(op abs):
	dumpfloprr(flopmap(op), rhs_base, rhs_base)
	%return
	
roper(op add):
roper(op mul):
	! Commutative, so we don't care
	%if lhs_base > rhs_base %then swap
	dumpfloprr(flopmap(op), lhs_base, rhs_base)
	pop rel
	%return

roper(op sub):
roper(op div):
roper(op rdiv):
	! We can't swap these, so we use the reverse form of
	! the opcode (which in our internal form is always one
	! more than the basic opcode index)
	op = flopmap(op)
	%if lhs_base > rhs_base %start
		swap
		op = op + 1
	%finish
	dumpfloprr(op, lhs_base, rhs_base)
	pop rel
	%return

roper(op rexp):
   ! This is implemented as a PERM routine
   load(rhs, any)
	dumpur(PUSH, rhs_base)
   pop rel
   ! The usual slightly clunky floating point "push"
	work = ptreg
	dumpri(SUB, SP, 8)
	dumprr(MOV, work, SP)
	dumpfloprm(FSTQ, work, 0)
   release(lhs_base)
   perm(fexp, 10)
   ! Since rexp is actually a standard C routine, the result will
   ! be in store, pointed to by AX...
   lhs_base = AX; claim(AX)
   lhs_form = V in S; lhs_disp = 0; lhs_scope = 0
   lhs_type = lreal
   load(lhs, anyf)
   %return

roper(op not):
roper(op and):
roper(op or):
roper(op xor):
roper(op rem):
roper(op lsh):
roper(op rsh):
roper(op exp):
	abort("Unsupported Real Operation")

%end;        !     operate

!                                                           >> ASSIGN <<
! ASSOP =   -1:  parameter assignment
!            0:  == assignment
!            1:  =  assignment
!            2:  <- assignment
%routine  assign(%integer  assop)
   %record(stackfm)%name  lh,rh
   %record(stackfm)  temp
   %integer  n, p, t, form, r

	! Store the item in RHS to LHS.  Encapsulates the dificulties
	! of variable length items and pushing things on the stack to
	! keep the rest of "Assign" looking tidy
	%routine Store(%record(stackfm)%name lhs, rhs)
		%integer pt, s, op

		%if lhs_base = SP %start;			! it's a push
			%if lhs_type = integer %or lhs_type = byte %start
				%if rhs_type = byte %start
               load(rhs, any)
            %else
               address(rhs)
            %finish
            dumpvpush(rhs)
			%else;		! must be a real
				%if lhs_type = real %start
					s = 4
					op = FSTD
				%else
					s = 8
					op = FSTQ
				%finish
				load(rhs, anyf)
				pt = ptreg
				dumpri(SUB, SP, s)
				dumprr(MOV, pt, SP)
				dumpfloprm(op, pt, 0)
			%finish
			%return
		%finish
		%if lhs_type = integer %start
			%if rhs_form = constant %and rhs_scope = 0 %start
				dumpmi(MOV, lhs_base!lhs_scope, lhs_disp, rhs_disp)
			%else
				load(rhs, any)
				dumpmr(MOV, lhs_base!lhs_scope, lhs_disp, rhs_base)
			%finish
		%else
			%if lhs_type = byte %start
				%if rhs_form = constant %and rhs_scope = 0 %start
					dumpmi8(MOV, lhs_base!lhs_scope, lhs_disp, rhs_disp)
				%else
               %if rhs_type = byte %start;   ! try to avoid pointless promoting to an int
                  ! We will reproduce a "Load" but without the word extension
                  address(rhs)
                  pt = gp reg
                  dumprm8(MOV, pt+16, rhs_base!rhs_scope, rhs_disp)
                  release(rhs_base)
                  rhs_base = pt; rhs_form = V in R; rhs_type = Integer
                  claim(pt)
               %else
					   load(rhs, any)
                  ! ABD - should add a capacity check here
               %finish
					dumpmr8(MOV, lhs_base!lhs_scope, lhs_disp, rhs_base+16)
				%finish
			%else
				load(rhs, anyf)
				%if lhs_type = real %start
					op = FSTD
				%else; ! long real
					op = FSTQ
				%finish
				dumpfloprm(op, lhs_base!lhs_scope, lhs_disp)
			%finish
		%finish
	%end

   abort("Assign Stack") %if stp < 2

   rh == top
   lh == stack(stp-1)

   form = lh_form;        ! to avoid the ravages of amap, load etc
   
   %if diagnose&4 # 0 %start
      monitor(lh, "ASS LH")
      monitor(rh, "ASS RH")
   %finish
   
   %if same(lh, rh) # 0 %then %start
      pop rel
      pop rel
      %return
   %finish

   %if assop < 0 %start;           ! Parameter
      %if lh_base >= 128 %start;     ! Special - prim routine
         temp = lh;  lh = rh;  rh = temp
         %return
      %finish

		! Extract the next formal parameter and make it our target
		lh_pbase = lh_pbase - 1
      push var(lh_pbase)
		! Now make our destination look reasonable
		lh == top
		lh_base = SP;							! target is the stack
      assop = 0 %if lh_form # v in s;	! %name parameter is '=='

		! We need special treatment for procedure parameters
		%if 7 <= lh_aform <= 10 %start;	! this is a procedure
			assop = 1;							! we will treat it as a value assignment
			rh_type = integer;				! of an integer
			lh_type = integer; lh_form = V in S
			%if rh_base # 0 %start;			! RH is already a parameter
				rh_form = V in S
			%else
				%if rh_scope = EXT %start;	! it is an external procedure
					rh_form = A V in S;		! pick up the addres
				%else;							! it is a local procedure
					! HACK: local procedures are Tags until Pass3 fixes them up.  The
					! only way we have of converting tags to addresses is with the switch
					! table - so we'll plant a fake switch entry for the label of the
					! local routine, and then load that value!
			      %if swtp >= Max Switch %then abort("Proc - Switch Table Full")
					swtab(swtp) = rh_disp; rh_disp = swtp * 2; swtp = swtp+1
					rh_scope = SWT
					rh_form = V in S
				%finish
			%finish
		%finish
	%finish

   %if array <= rh_aform %and rh_aform <= namearrayname %start;        ! Arrayname
      ! An array name is two words - a pointer to the data and a
      ! pointer to the dope vector.  If the RHS is already one of these
      ! then we just want to copy the two words.  If it is a static
      ! array, we need to map the data to make a pointer, and its' dope
      ! vector will be in the constant table, so we fetch that.
      amap(lh)
      address(lh)
      amap(rh);            ! This works because arrays are stacked as V in S, arraynames are A in S
      address(rh)
      ! We do the dope vector first - that makes it easier when we're parameter passing
      %if rh_aform = array %or rh_aform = name array %start;   ! simple static - DV in COT
         ! We will rustle up a dummy record for the DV address
         temp = 0
         temp_form = A V in S
         temp_type = integer
         temp_disp = rh_pbase
         temp_scope = COT
      %else;                                                   ! already an array name
         temp = rh; claim(temp_base)
         temp_disp = temp_disp + 2
      %finish
      lh_disp = lh_disp+2
      store(lh, temp)
      release(temp_base)
      lh_disp = lh_disp-2
      store(lh, rh)
      pop rel
      pop rel
      %return
   %finish

   %if lh_type = general %start;     ! general %name parameter
      abort("Assign GenName") %unless assop = 0; ! Only '==' is allowed
		! A general name pointer is two words - the pointer itself
		! and a second word to convey type information.  If the RHS
		! is already one of thse guys it's easy - just copy the two
		! words.  Otherwise, we need to rustle up the second word at
		! compile time
		amap(lh)
		address(lh)
      %if rh_type = general %start
			temp = rh;	! make a copy for the second word
			claim(temp_base); temp_disp = temp_disp + 2
			amap(temp)
		%else
			temp = 0
			temp_type = integer
			temp_disp = (rh_size << 4) + genmap(t)
		%finish
		amap(rh)
		store(lh, rh)
		lh_disp = lh_disp + 2
		store(lh, temp)
		release(temp_base)
      pop rel
      pop rel
      %return
   %finish

   %if assop = 0 %start;        ! ==
      amap(lh);      ! destination
      amap(rh);      ! ABD %string(*)%name  NOT handled special here - should be?
   %finish

   %if Lh_Type = record %start
		%if lh_base = SP %start;		! pass record by value - destination is the stack
			n = lh_size
			hazard(DI)
			dumpri(SUB, SP, lh_size)
			dumprr(MOV, DI, SP)
			claim(DI)
			lh_base = DI
		%else
			n = Min Record Size(Lh, Rh)
			amap(lh)
			load(lh, DI)
		%finish
      hazard(CX)
      dumpri(MOV, CX, n)
      %if rh_Form = Constant %start
         hazard(AX)
		   dumprr(XOR, AX,AX);		! get a zero
			dumprepstosb
      %else
         amap(rh)
         load(rh, SI)
         dumprepmovsb
      %finish
      pop rel
      pop rel
      %return
   %finish

   %if lh_type = string %start
      %if assop > 0 %and rh_format = 1 %start;    ! null string as zero byte ?
         lh_type = byte
         pop rel;       ! zap current RHS
         push const(0);     ! get a zero
         assign(assop); ! and assign it
         %return
      %finish

	! our copy routines expect DEST then SOURCE then LENGTH on the stack
		%if lh_base = SP %start;		! pass string by value - destination is the stack
         ! space is string size, plus one for length, plus make it even
         p = lh_size + 1; p = (p+1) & (\1)
			dumpri(SUB, SP, p)
			! we want to Push SP here - sadly different versions of x86
			! architecture have different interpretations of "PUSH SP", so...
			r = gp reg
			dumprr(MOV, r, SP)
			dumpur(PUSH, r)
		%else
			amap(lh)
			load(lh, any)
			dumpur(PUSH, lh_base)
		%finish
		! It is likely that the RH variable is a temporary work area
		! Before we trash the information, we try to release it
		Return Work(rh_disp)
		amap(rh)
		load(rh, any)
		dumpur(PUSH, rh_base)
      pop rel
      pop rel
      dumppushi(0, lh_size)
		%if assop = 2 %then perm(sjam, 6) %else perm(smove, 6)
      %return
   %finish

   address(lh)

	store(lh, rh)

   pop rel
   pop rel
%end;            !  assign

!                                                        >> ARRAY REF <<
! Array references always use the PERM unless they are 1 dimensional, AND
! the %control bit has been turned off
%routine  array ref(%integer mode)
   %record(stackfm)%name av
   %integer type, form, size, format

   %if mode#0 %then %start
   ! Put non-terminal index onto stack for PERM
      %if top_type = byte %start
         load(top, any)
      %else
         address(top)
      %finish
      dumpvpush(top)
      pop rel
      %return
   %finish
   
   av == stack(stp-1)
   size = av_size
   size = size + 1 %if av_type = string
   form = av_aform
   %if form=namearray %or form=namearrayname %then size = 2

   %if control & check array = 0 %and av_dim = 1 %start
      ! This will be unchecked, the top of the stack is the only index (1D),
      ! so we can do a cheap multiplication here
      %if size#1 %start;      ! multiply offset by var size
         push const(size)
         operate(op mul)
      %finish
   %else
      ! This is the final (and perhaps only) subscript for a checked array,
      ! so we are going to use the Perm - therefore pass this as a parameter
      %if top_type = byte %start
         load(top, any)
      %else
         address(top)
      %finish
      dumpvpush(top)
      pop rel
   %finish

   ! How we do the rest of the access depends on whether this is a simple
   ! static array, or an array name...

   %if form = arrayname %or form = namearrayname %start;   ! array is a "name"
      ! We will AMAP the name, so we remember the info and then put it all back later
      type = av_type
      format = av_format
      size = av_size
      %if form = arrayname %then form = v in s %else form = a in s
      amap(av)

      %if control & check array # 0 %or av_dim > 1 %start;     ! do the rest of the check
         ! This is a bit clunky, because we may load registers in order
         ! to access AV, only to Hazard them for the PERM
         address(av)
         push copy(av); claim(top_base)
         top_disp = top_disp + 2;   ! Dope Vector address follows A(0)
         dumpvpush(top)
         pop rel
         perm(aref, (av_dim * 2) + 2); ! DV word, plus a word for every subscript
         push const(0)
         top_form = V in R; top_base = AX; claim(AX)
      %finish

      load(top, anyp);        ! make sure index is in a pointer register
      operate(op add)
      top_type = type
      top_form = form
      top_format = format
      top_size = size
      top_disp = 0
   %else;     ! simple arrays are always 1D, but can still be checked
      %if control & check array # 0 %start
         ! Pass a pointer to the Dope Vector
         dumppushi(COT, av_pbase);     ! simple arrays have compile-time DV's in the COT
         perm(aref, 4)
         push const(0)
         top_form = V in R; top_base = AX; claim(AX)
      %finish

      address(av)
      %if av_form # v in s %then abort("Aref Form")
      %if top_form = constant %start;  ! simple constant a(k)
         av_disp = av_disp + top_disp; ! just add it to the offset
      %else
         load(top, anyp);              ! pick up index in a pointer
         %if av_base # 0 %start;       ! add the base we've already got
            dumprr(ADD, top_base, av_base)
            release(av_base)
         %finish
         av_base = top_base
      %finish
      %if form = array %then av_form = v in s %else av_form = a in s
      pop stack
   %finish

   top_aform = 0; ! not an array any more

%end;       !  array ref

!                                                        >> TEST ZERO <<
! test a real/integer/byte variable against zero
%routine  test zero(%record(stackfm)%name  v)

	%if v_type = integer %or v_type = byte %start
		load(v,any)
		dumpri(CMP, v_base, 0)
	%else
		abort("Test Zero")
	%finish
%end;      ! test zero

%routine Compare Records(%record(stackfm)%name L, R, %integer N)
	abort("Compare Records")
%end

!                                                    >> COMPARE REALS <<
%routine  compare reals(%record(stackfm)%name  l,r)
	load(l, anyf)
	load(r, anyf)
   hazard(AX)
	! who's ended up on top?
	%if l_base > r_base %start;   ! l_base is the top of the FPU stack
		dumpfloprr(FCMP, r_base, l_base)
	%else
		dumpfloprr(FCMP, l_base, r_base)
		invert = invert !! 1
	%finish
   dumpflopspec(FSTSW); ! puts status into AX
   dumpsimple(SAHF);    ! and move it to flags
   compare unsign = 1;  ! because FPU reports as if operands were unsigned
%end;        ! compare reals

!                                                  >> COMPARE STRINGS <<
%routine  compare strings(%record(stackfm)%name  l,r)
%record(stackfm)%name  temp
   %if l_base = COT %and l_disp = null string %start
      temp == r;  r == l; l == temp
      invert = invert !! 1
   %finish
   %if r_base = COT %and r_disp = null string %start
      l_type = byte
      test zero(l)
   %else
      amap(l)
      load(l, any)
      dumpur(PUSH, l_base)
      amap(r)
      load(r, any)
      dumpur(PUSH, r_base)
      perm(scomp, 4)
      dumpri(CMP, AX, 0)
   %finish
%end;       ! compare strings

!                                                          >> COMPARE <<
%routine  compare(%record(stackfm)%name  l,r)
   %if l_type = 0 %or l_type = string %start
      compare strings(l,r);  %return
   %finish
   %if floating(l)#0 %or floating(r)#0 %start
      compare reals(l,r);  %return
   %finish
   %if zero(r) # 0 %start
      test zero(l);  %return
   %finish
   %if zero(l) # 0 %start
      test zero(r);  invert = invert !! 1
      %return
   %finish
   %if L_Type = Record %start
      Compare Records(L, R, Min Record Size(L, R))
      %return
   %finish
   load(l,any)
	%if r_type = byte %start
		load(r, anyg)
	%else
	   address(r)
	%finish
   dumprv(CMP, l_base, r)
%end;        ! compare

!                                                          >> RESOLVE <<
%routine  resolve(%integer  flag)
!S -> A.(B).C
   %if flag&1 = 0 %then push const(0) %else amap(top);         ! C missing?
   load(top, any)
   dumpur(PUSH, top_base)
   pop rel
   amap(top);  ! B
   load(top, any)
   dumpur(PUSH, top_base)
   pop rel
   %if flag&2 = 0 %then push const(0) %else amap(top);         ! A missing?
   load(top, any)
   dumpur(PUSH, top_base)
   pop rel
   amap(top);  ! S
   load(top, any)
   dumpur(PUSH, top_base)
   pop rel
   perm(sresln, 8)
   %if flag&4 # 0 %then dumpri(CMP, AX, 0)
%end;           ! resolve


%integerfn enter
	%integer cad;

   uncond jump = -1; ! can get here

   ! This is a convenient place to include external definitions if needed
   %if potype >= external %start
      fill external(CODE, next cad, external id)
   %finish

	dumprm(MOV, BX, DISPLAY,level*2);
	dumpur(PUSH, BX);
	dumpur(PUSH, BP);
	dumprr(MOV, BP,SP);
	dumpmr(MOV, DISPLAY, level*2, BP);

	cad = nextcad;
	dumpstaticalloc(cad);	! plants code to subtract static allocation (if any) from SP
	%result = cad;
%end

!                                                           >> DUMP RETURN <<
%routine  dump return
   %return %if uncond jump = next cad;          !  can't get here ?

	dumprr(MOV, SP, BP);
	dumpur(POP, BP);
	dumpur(POP, BX);
	dumpmr(MOV, DISPLAY, level*2, BX);
	dumpsimple(RET);

   uncond jump = next cad
%end;          ! return

! Routine to do "to string" as an in-line, either by making
! a constant string in the CONST area, or putting one onto
! the current workspace
%routine  compile to string(%record(stackfm)%name  v)
   %integer tmp

   %if const(v)#0 %start
      current string(0) = 1;  current string(1) = v_disp&255
      v_base = COT; v_disp = getcots(current string)
   %else
      tmp = getwork(2)
      load(v,anyg);        ! Must be a byte-addressable register
      dumpri(AND, v_base, 255)
      dumpri(OR, v_base, 16_0100)
      dumprr8(XCHG, v_base+16, v_base+20)
      dumpmr(MOV, BP, tmp, v_base)
      release(v_base)
      v_base = BP;  v_disp = tmp
   %finish
   v_type = string;  v_form = VinS;  v_size = 1
%end

!                                                             >> COMPILE CALL <<
! Call the routine on the top of the stack.  Note - the parameters
! are all hidden underneath the routine, so we need to push them
! here
%routine  compile call(%record(stackfm)%name  v)
%switch  b(1:15)
!  1 = rem
!  2 = float
!  3 = to string
!  4 = addr
!  5 = integer
!  6 = byte integer
!  7 = string
!  8 = record
!  9 = real
! 10 = long real
! 11 = length
! 12 = charno
! 13 = type of ( type of general name parameter )
! 14 = size of ( physical length in bytes )
! 15 = int (from real)

   %constbyteintegerarray  new type(5:12) =
            1,       5,    3,       4,    2,    6,     5,    5
!         integer, byte, string, record, real, lreal, byte, byte
   %integer  t,l,p

   %if v_base >= 128 %start;         !  built-in primitive
      l = 0;  t = v_disp;  sym = 0;      ! 'sym=0' used as flag elsewhere
      pop rel
      -> b(t)
b(1):;                             ! REM
      operate(op rem);  %return
b(2):;                             ! FLOAT
      load(top, anyf)
      %return
b(3):;                             ! TO STRING
      compile to string(top)
      %return
b(4):;                             ! ADDR
      amap(top)
      %return

b(11):;                            ! LENGTH
      push const(0);					  ! length is charno zero
b(12):;                            ! CHARNO
      amap(stack(stp-1))
      operate(op add);  !LHS&RHS reversed in operate??
      -> map it
b(7):;                             ! STRING
b(5):b(6):;                        ! INTEGER, BYTE
b(8):;                             ! RECORD
b(9):b(10):;                       ! REAL, LONG REAL
map it:
      vmap(top);  top_type = new type(t)
      top_size = v size(top_Type)
      %return
b(13):;                              ! type of(..)
b(14):;                              ! size of(..)
      %if top_type # general %start;      ! type explicitly specified
         %if t = 14 %start;      ! type of
            p = gen map(top_type)
         %else
            p = top_size;  p = p+1 %if top_type = string
         %finish
         release(top_base)
         top_type = integer;  top_form = constant
         top_base = 0;  top_disp = p
      %else
         top_disp = top_disp + 2;      ! reference property-word
         top_form = V in S;  top_type = integer
         %if t = 14 %start;         ! type of
            push const(15);  operate(op and)
         %else;                     ! size of
            push const(4);  operate(op rsh)
         %finish
      %finish
      %return
b(15):;                             ! INT(real)
      load(top, anyf)
      release(top_base)
      p = getwork(2)
      dumpfloprm(FSTI, BP, p)
      top_type = integer
      top_form = V in S
      top_base = BP
      top_disp = p
      %return
   %finish

   !   -- normal routine calls --

   ! String functions have a hidden last parameter to point
   ! to the result area
   %if v_type = string %and v_aform = 8 %start
      t = getwork(v_size+1)
      p = gp reg
      dumprm(LEA, p, BP, t)
      dumpur(PUSH, p)
   %finish
   hazard all
   %if v_scope = EXT %start;        ! external
      dumpextcall(v_disp)
   %else
	   %if v_base # 0 %start;					! procedure-as-parameter
			dumpum(CALL, v_base, v_disp);		! plants call indirect through variable
	   %else;										! local routine
		   dumpjump(CALL, v_disp);				! plants fixup for the tag
		%finish
	%finish
	! adjust the stack
	%if v_extra # 0 %then dumpri(ADD, SP, v_extra)
	%if v_type = 0 %start;           ! not function or map
		pop rel
	%else;                           ! Here we've got a result
      v_scope = 0;                  ! Result is local, even if the function wasn't
      %if v_type = string %and v_aform = 8 %start
         v_base = BP;               ! String result will have been copied back here
         v_disp = t
         v_form = V in S
      %else
     	   v_base = AX;               ! Result is always in AX
         v_disp = 0;                ! Clear this for MAP results
         claim(AX)
         %if (v_type = real %or v_type = lreal) %and v_aform = 8 %start
         ! Floating result will be in the "FAC" location, pointed at by AX
         ! In other words, it comes out like a %map.  Unfortunately the
         ! FAC location is global, so we need to copy it back out of there
         ! before somebody steps on it
            v_form = V in S
            load(v, anyf)
         %finish
      %finish
	%finish
%end;            !  call

!                                                      >> COMPILE FOR <<
%routine  compile for
%record(stackfm)%name  cv, iv, inc, fv
%integer  lab, n

   ! Lock a value into a temporary to make sure it is invariant
   %routine  stab(%record(stackfm)%name  v,  %integer  type)
      %integer  t,r
      %return %if const(v)#0
      load(v,any);  r = v_base
      t = getwork(2)
      dumpmr(MOV, BP,t, r)
      v_base = BP; v_disp = t; v_scope = 0
      v_type = type;  v_form = V in S
      release(r)
   %end

   iv == top
   fv == stack(stp-1)
   inc == stack(stp-2)
   cv == stack(stp-3)
   lab = read tag

   stab(fv,integer)
   stab(inc,integer)

   %if cv_form # v in s %or (cv_base # 0 %and activity(cv_base) >= 0) %start
      n = cv_type
      amap(cv)
      stab(cv, n)
      cv_form = a in s
   %finish
   
   push copy(cv)
   push copy(iv)
   push copy(inc);
   operate(op sub)
   assign(1);        ! cv = iv - inc

   define label(lab)
   pop stack;! zap unwanted copy of IV

	! Stack is now  top->[FV[INC[CV 
   push copy(cv);		! in case compare alters it
   compare(top, fv)
   jump to(lab+1, JE, 1)
   invert = 0; ! because the compare might have flipped this (N/A for JE)
	! Stack is now top->[CV'[FV[INC[CV where CV' is a register copy of CV
   release(fv_base); fv = top;	! trash FV and make a copy of CV' in that slot
   pop stack;							! discard the top copy
   ! stack is now top->[CV'[INC[CV
   operate(op add)
   assign(1)
%end;            ! for

!--------------------------------------------------------------
! Code for ASSEMBLE starts here...


   First Name = Names
   First Label = Labs
   Proc Var == Decvar
   last skip = -1
   old frame = frame
	frame = 0

   %if amode >= 0 %start;                    ! NOT A RECORDFORMAT
      level = level+1;  abort("Level") %if level > max level %and spec = 0
		worklist(level) = 0

      %if amode = 0 %start;                  ! %begin block
         %if level = 1 %start;               ! Initial %begin ?
            external id = program ep;        ! linkage to program entry
            otype = external;  potype = otype
         %finish
         static alloc = enter
      %finish
   %else
      %if amode = -1 %start;              ! normal record format
         ProcVar_pbase = parms;           ! where our members start
		%else
			%if amode = -2 %then frame = oldframe;	! alternates start at the current offset
      %finish
      max frame = frame;						! start counting space here
   %finish

!  --- main compilation loop ---
next: sym = nextsym;  read symbol(nextsym)
      %if sym < 33 %or sym > 127 %then %start
			selectoutput(0)
			printsymbol('('); write(sym, 1); printsymbol(','); write(nextsym, 1); printsymbol(')')
			abort("Bad I Code")
		%finish
      -> c(sym)

c('l'): %if read tag # 0 %then abort("Language")
        ->next;        ! We only support standard IMP

c('O'):;                                        ! Set line number
      abort("Stack?") %if stp # 0
      abort("Claimed") %if claimed # 0
      current line = read tag
      ! Pass1 sends the line number multiple times if there's more than
      ! one statement per line - for debugging we only want "real" line numbers
      %if echo line < current line %start
         dump line number(current line)
         %while echo line < current line %cycle
            echo source line
         %repeat
      %finish
      ->next

c('$'): define var;  ->next;                 ! Define VAR

c('b'):;                                        ! Define constant bounded Dope Vector
      vub = top_disp; pop stack
      vlb = top_disp; pop stack
      ->next

! Utility routine used when dumping initialisers for OWNs
! Note non-portable use of real values
         %routine  adump
            %integer i
				%real		rv32
            %switch ot(0:6)

            ->ot(own type)

            ot(general):   abort("General Own?")

            ot(integer):   gput(ownval); -> done
            ot(real):      rv32 = rvalue;		! because our default variable is a 64 bit long real
									gput(byteinteger(addr(rv32))!byteinteger(addr(rv32)+1)<<8)
                           gput(byteinteger(addr(rv32)+2)!byteinteger(addr(rv32)+3)<<8)
                           -> done
            ot(string):    i = dump string(data size); ! need result for const strings
                           %if decvar_scope = COT %then decvar_disp = i
                           -> done
            ot(record):    %for i = 1,1,data size %cycle
                              gbyte(0)
                           %repeat
                           -> done
            ot(byte):      gbyte(ownval); -> done

            ot(lreal):     gput(byteinteger(addr(rvalue))!byteinteger(addr(rvalue)+1)<<8)
                           gput(byteinteger(addr(rvalue)+2)!byteinteger(addr(rvalue)+3)<<8)
                           gput(byteinteger(addr(rvalue)+4)!byteinteger(addr(rvalue)+5)<<8)
                           gput(byteinteger(addr(rvalue)+6)!byteinteger(addr(rvalue)+7)<<8)
            done:
         %end

c('A'):;                                           ! Initialise OWN variable
      i = read tag;           ! Number of values to assign
      %if stp # 0 %start;     ! Value supplied?
         own val = top_disp
         pop stack
         %if own type = real %or own type = lreal %start
            rvalue = own val;  ! copy integer supplied into floater
         %finish
      %else;            ! initialise to default pattern
         ownval = 0
         cslen = 0;     ! in case it's a string
      %finish

      %if own form = array %or own form = name array %start
         adump %for j = 1,1,i
      %else
         %if otype = 0 %start ;   ! %const .... %name
            Abort("Constant Name");
         %else
            ! constant strings are treated as literals - everything else is "DATA"
            %if otype = con %and own type = string %start
               decvar_scope = COT
            %else
               decvar_scope = DATA
            %finish
            decvar_level = 0; decvar_disp = datatp
            adump
         %finish
      %finish
      ->next

c(''''): get string;  ->next;             ! Stack string constant

c('G'):  get string;                         ! Alias for item about to be declared
         alias = ""
         %for j = 1, 1, cslen %cycle
            alias = alias.tostring(current string(j))
         %repeat
         pop stack
         ->next

c('N'):;                                      ! Stack integer constant
        ! pass1 is 32 bit, but we are only 16 bit
         j = read tag;  ! discard the low half
         push const(read tag);  ->next

c('D'):;                                        ! Stack real constant
      get d;                                    ! suck the value into rvalue
      push const(0) %and ->next %if rvalue = 0
      ->next %if next sym = 'A'
      otype = con;          !  anonymous %const
      j = real constant;    !  N.B. ** %fn + side-effect **
      push const(0)
      top_type = lreal; top_scope = COT; top_disp = j; top_form = v in s
      ->next

c('n'):;                                     ! Select member from record format
		! Contrary to earlier iCode versions, this one seems to use 'n' for
		! both normal record member access and alternate formats?

      lhs == top;										! Points to the base record
		i = read tag
      push var(var(top_format)_pbase - i);	! Push descriptor for the i-th member

      %if top_aform # recordformat %start;   !  not record format - must be a member
         %if lhs_form = v in s %or lhs_form = VinRec %start
            top_disp = lhs_disp + top_disp
            lhs_form = lhs_form - v in s + top_form
         %else
            %if lhs_form = a in rec %start
               lhs_form = VinRec;  lhs_type = integer
               load(lhs,any)
               lhs_form = top_form
            %else
               %if lhs_form <= VinR %start
                  lhs_form = top_form;    ! ????
               %else;		! A in S
                  lhs_extra = lhs_disp
                  lhs_form = top_form+3
               %finish
            %finish
         %finish
         lhs_disp = top_disp
         lhs_type = top_type
         lhs_aform = top_aform
         lhs_dim = top_dim
      %finish
      lhs_size = top_size;  lhs_format = top_format
      pop stack
		->next

c('@'):;																! Stack variable descriptor
      push var(read tag)
      ->next

c('E'):compile call(top);										! Call procedure
      ->next

c('M'):;                                              ! MAP result
      amap(top)
      load(top, AX)
      pop rel
      dump return
		->next

c('V'):;                                              ! FN result
      %if procvar_type = integer %start
         load(top, AX)
			pop rel
      %else
         %if procvar_type = real %or procvar_type = lreal %start
         ! Floating point results are put into store, and AX contains
         ! the address
            load(top, anyf)
				pop rel
            %if fp result loc = -1 %start;   ! clunky allocation coming up
               gfix(align)
               fp result loc = datatp
               gput(0); gput(0); gput(0); gput(0)
            %finish
            %if procvar_type = real %start
               dumpfloprm(FSTD, DATA, fp result loc)
            %else
               dumpfloprm(FSTQ, DATA, fp result loc)
            %finish
            dumprioffset(MOV, AX, DATA, fp result loc)
         %else ; ! string or record - pass back through the hidden parameter
            push copy(top);               ! Make a copy of the thing on top
            lhs == stack(stp-1);       ! point to the (now spare) next item
            lhs_type = procvar_type;   ! and make it look like a destination
            lhs_size = procvar_size
            lhs_format = procvar_format
            lhs_base = BP
            lhs_disp = 6;              ! At the offset of the last parameter
            lhs_form = a in s
            assign(1)
         %finish
      %finish

c('R'):;                                              ! RETURN
      dump return
      ->next

c('K'):;                     ! %false
      k = 0;  -> true false

c('T'):;                     ! %true
      k = -1
true false:
      dumpri(MOV, AX, k)
      dump return
      ->next

c('a'):  array ref(0);   ->next
c('i'):  array ref(1);   ->next
c('.'):  operate(op conc);  ->next
c('+'):  operate(op add);   ->next
c('\'):  operate(op not);   ->next
c('U'):  operate(op neg);   ->next
c('-'):  operate(op sub);   ->next
c('!'):  operate(op or);    ->next
c('%'):  operate(op xor);   ->next
c('&'):  operate(op and);   ->next
c('['):  operate(op lsh);   ->next
c(']'):  operate(op rsh);   ->next
c('*'):  operate(op mul);   ->next
c('/'):  operate(op div);   ->next
c('Q'):  operate(op rdiv);  ->next
c('X'):  operate(op exp);   ->next
c('x'):  operate(op rexp);  ->next
c('v'):  operate(op abs);  ->next

c('j'):  assign(2);   ->next;                      ! JAM transfer
c('S'):  assign(1);   ->next;                      ! Normal value assignment
c('Z'):  assign(0);   ->next;                      ! Assign address '=='

c('p'):	assign(-1);	 ->next;								! Pass a parameter

c('u'):	opr = op add; ->uq common;            !++
c('q'):	opr = op sub;                         !--
uq common:
	      lhs == stack(stp-1)
         t = lhs_type;  j = lhs_size
                        j = j+1 %if t = string
         amap(lhs)
         abort("++/-- size") %if j = 0
         push const(j)
         operate(op mul)
         operate(k)
         vmap(top);  top_type = t
         ->next

c('k'):  dumpri(CMP, AX, 0);                    ! Branch on FALSE (= 0)                                     ! Branch =
c('='):  opr = JE;    -> cond
c('t'):  dumpri(CMP, AX, 0);                    ! Branch on TRUE (# 0)
c('#'):  opr = JNE;   -> cond
c('<'):  %if invert # 0 %then opr = JG %else opr = JL;   -> cond
c('>'):  %if invert # 0 %then opr = JL %else opr = JG;   -> cond
c('('):  %if invert # 0 %then opr = JGE %else opr = JLE;   -> cond
c(')'):  %if invert # 0 %then opr = JLE %else opr = JGE;   -> cond
cond:
      ! move the > and < operators to unsigned versions if needed
      %if compare unsign # 0 %and opr >= JG %then opr = opr + 4
      compare unsign = 0
      val = read tag
      jump to(val, opr, 1);  invert = 0
      ->next

c('C'):;                                     ! Compare addresses
      amap(top);  amap(stack(stp-1));        ! fall through to values

c('?'):;                                     ! Compare values
      LHS == stack(stp-1)
      RHS == top
      compare(lhs,rhs)
      pop rel
      pop rel
      ->next

c('"'):;                                     ! Compare values, double sided
      LHS == stack(stp-1)
      RHS == top
      load(rhs, any)
		! We happen to know that Compare loads the left parameter in a register.
		! We've already got RHS in a register, so we flip the LHS and RHS to the
		! comparison and set Invert accordingly
      compare(rhs, lhs)
		invert = 1
      ! release LH and then overwrite it with RH
      release(lhs_base)
      lhs = rhs
      pop stack
      ->next

c('r'):  resolve(read tag);  ->next

c('_'):;                                     ! Define switch label
      uncond jump = 0
      v == var(read tag)
      j = top_disp; pop stack
		t = new tag
		dumplabel(t);
		swtab(v_disp+j) = t;
      ->next

c('W'):;                                     ! Jump to switch
      v == var(read tag)
      push const(1);  operate(op lsh);         ! subscript X 2
      load(top, anyp)
		dumpum(JMP, SWT!top_base, v_disp * 2);	! swtab is word-size, hence * 2
      pop rel
      uncond jump = next cad
      ->next

c('B'):;                                     ! Backward Jump
      val = read tag
		jump to(val, JMP, 0);
      ->next

c('F'):;                                     ! Forward Jump
      val = read tag
      %if val = 0 %then %start
         %if last skip # next cad %then %start
            skip proc = new tag
            dumpjump(JMP, skip proc)
         %finish
         ->next
      %finish
      jump to(val, JMP, 1)
      ->next

         %integerfn user label(%integer lab)
            %record(varfm)%name v
            %if lab > names %start
               names = lab
               v == var(lab)
               v = 0
               v_form = pgm label
               v_disp = new tag
               %result = v_disp
            %finish
            %result = var(lab)_disp
         %end
c('J'):;                                     ! Jump to user label
      dumpjump(JMP, user label(read tag))
      ->next

c('L'):;                                     ! Define user label
      dump label(user label(read tag))
      ->next

c(':'):;                                     ! Define compiler label
      j = read tag
		%if j = 0 %start
			dump label(skipproc)
			last skip = next cad
			Uncond Jump = 0
			-> next
		%finish
      define label(j);  ->next

c('f'):;                                     ! FOR loop header
      compile for;  ->next

c('w'):;                                     ! Machine code
      abort("Machine Code")
!      ->next

c('P'):;                                     ! Machine code literal
      abort("Machine Literal")
!      cput(top_disp)
!      pop stack
!      ->next

c('y'):;                                     ! %diagnose n
      j = read tag
      %if (j>>14)&3 = 2 %start;         ! it's for pass 2
         diagnose = j & 16_3FFF
      %else
         !***** should pass onto next pass ******
      %finish
      ->next

c('z'):;                                     ! %control n
      control = read tag;  ->next

c('m'):;                                     ! %monitor
      j = -1;  -> SIGNAL EVENT

c('s'):;                                     ! %stop
      j = 0;  -> SIGNAL EVENT

c('e'):;                                     ! %signal event
      j = read tag
SIGNAL EVENT:
      push const(0) %while stp < 2
      push const(j)
      load(top, any); dumpur(PUSH, top_base); pop rel
      load(top, any); dumpur(PUSH, top_base); pop rel
      load(top, any); dumpur(PUSH, top_base); pop rel
      perm(signal, 6)
      uncond jump = next cad
      ->next

c('o'):;                                     ! %on %event block
      ->next

c('h'):;   ! compiler op(n)
!      compiler op(tag)
      ->next

c('g'):;    !array formats
c('d'):
      ! in params:  =0 -> simple array,  # 0 -> array-in-record
      
      ! First get dimensions
      dim = read tag;  abort("Array Dim") %unless 0 < dim < 3
      
      ! Now get number of (previously declared) array variables we are defining
      read symbol(next sym);  n = read tag

      %if in params # 0 %start;         ! Array in record
         parms = parms+n
         vub = top_disp; pop stack
         vlb = top_disp; pop stack
         abort("Array Bounds") %if vlb > vub
         dv = set dope vector
      %else
         names = names-n
         ! Now we need to plant code to manufacture a dope vector
         frame = (frame - ((dim * 4)+4))&(\1);  ! space for :Dim:<bound pairs>:DataSize:
         dv = frame
         ! First store the dimension
         dumpmi(MOV, BP, dv, dim)
         ! And the data size is also constant
         dumpmi(MOV, BP, dv + (dim*4) + 2, data size)
         ! Now the bounds
         j = 0;                    ! points to before the first stack value
         %for i=1,1,dim*2 %cycle
            j = j + 1; lhs == stack(j)
            %if lhs_form = constant %start
               dumpmi(MOV, BP, dv + (i*2), lhs_disp)
            %else
               load(lhs, any)
               dumpmr(MOV, BP, dv + (i*2), lhs_base)
            %finish
         %repeat
         ! Now we need to allocate the space for the array
         %if dim > 1 %or control & check array # 0 %start
            ! Do it with the PERM
            %while stp # 0 %cycle
               pop rel; ! get rid of all the bounds - they are in the DV already
            %repeat
            dumprm(LEA, AX, BP, dv)
            dumpur(PUSH, AX)
            perm(adef, 2)
            ! We now need to make our result match the inline version
            ! by putting AX and DX into stacklike variables
            push const(0); lhs == top
            push const(0); rhs == top
            lhs_base = AX; lhs_form = V in R; claim(AX)
            rhs_base = DX; rhs_form = V in R; claim(DX)
            pop stack
            pop stack
         %else
            push const(1)
            operate(op add)
            push const(data size)
            operate(op mul)
            push copy(stack(stp-1));   ! suck up the lower bound
            push const(data size)
            operate(op mul)
            ! top is now the lower bound, next is the upper, and a bogus copy of lb is next
            load(top, any);      ! Make sure this is in a register
            lhs == top;          ! Point to it
            pop stack;           ! and drop (without release) this copy
            load(top, any);      ! This is now UB - load it in a register as well
            rhs == top;          ! Point to it
            pop stack;           ! and keep RHS (Upper)
            pop stack;           ! dump the bogus lb
         %finish
! Note - there are 4 GP registers, and we're going to need them ALL here
         t = gp reg;             ! get a working register for the dope vector address
         dumprm(LEA, t, BP, dv); ! load it
         dv = t; claim(dv);      ! use this to hold the register number
         t = gp reg;             ! the last one! (which we don't claim, 'cos we can't lose it)
			dumprr(MOV, t, SP);	   ! working copy of SP so that real SP is always "OK"
      %finish

      %for j = 1,1,n %cycle
         decvar_dim = dim
         %if in params = 0 %start;      !  array not in record
            names = names+1;  decvar == var(names)
            decvar_level = level
            frame = frame - 4;  ! 2-word header
            decvar_disp = frame
            %if decvar_form = array %or decvar_form = namearray %c
                     %then decvar_form = decvar_form + 1;         ! force arrayname
				dumprr(SUB, t, rhs_base);
				dumpmr(MOV, BP, frame, t);         ! store a(0) address
            dumpmr(MOV, BP, frame+2, dv);      ! store dope vector pointer
				dumprr(ADD, t, lhs_base);
         %else;          ! array-in-record
            parms = parms-1;  decvar == var(parms)
            decvar_disp = frame - vlb
            frame = frame + vub; ! noting that Set Dope Vector has changed VUB to the array size
            decvar_pbase = dv
         %finish
      %repeat

      %if in params = 0 %start
         dumprr(MOV,SP,t)
         release(lhs_base)
         release(rhs_base)
         release(dv)
      %finish
      ->next

c('^'):;                                     !   {Set Format}
      top_type   = record
      top_format = read tag
      ->next

c('I'):  Abort("Pascal?");!   {ESCAPE for Pascal etc.}

c('~'):;                  ! alternate record format
      sym = next sym;  read symbol(next sym)
      %if sym = 'A' %start;            ! alt start
         decvar == procvar
         assemble(-2,labs,names)
         ->next
      %finish

      %if sym = 'B' %then -> out;             ! alt end

      abort("Alt Record") %if sym # 'C';         ! faulty intermediate code
		! Compile the next alternate - update limit and set frame back to where we started
      max frame = frame %if frame > max frame
		frame = old frame
      ->next

c('{'):;                                              ! Start of formal parameters
      in params = -1
      ! this is either a record format, a procedure, or a proc spec;
      !   - block type was set by decvar to tell us which
      assemble(block type,labs,names)
      ->next

c('}'):;                                              ! End of formal parameters
      in params = 0
      -> OUT %if amode < 0;            ! end of %record %format defn.
      -> OUT %if procvar_level = 128;  ! prim routine reference

		! Here it's a real subroutine - copy any parameters to the PARM area
      %if names > first name %start
         procvar_pbase = parms;					! Point one beyond the first parameter
			frame = (frame + 1) & (\1);			! Even up the stack size
			%if procvar_type = string %and procvar_form = 8 %start
				frame = frame + 2;					! string functions have a hidden result parameter
			%finish
			procvar_extra = frame;					! Remember the stack offset
			procvar_dim = names - first name;	! and the number of parameters
			frame = frame + 6;						! leave space for return linkage
         %for j = first name+1, 1, names %cycle
            ap == var(j)
            parms = parms-1;  fp == var(parms)
            fp = ap
				! formal parameter base and displacement is implicit (on the stack)
				fp_level = 0
				! we also need to adjust the offsets of the actual parameters, because
				! they were allocated going "forwards", but will be pushed on the stack
				! "backwards" - that is, the first item passed will end up with the
				! highest address.  DefineVar has done part of the work for us by tagging
				! the displacements in the right style, but it can't tell the whole frame
				! offset, so we calculate the final offsets here...
				ap_disp = frame - ap_disp
         %repeat
         abort("Params") %if parms < names
      %finish
      -> OUT %if amode = 2;	! this was just a spec
      frame = 0
      dumplabel(procvar_disp)
      static alloc = enter
      ->next

c('H'):;                                              ! Start of BEGIN block
      decvar == begin;  decvar_disp = new tag
      otype = 0;  spec = 0;  potype = 0
      %if level # 0 %start;  ! not outermost %begin
         push const(decvar_disp)
			top_type = 0;											! it's not a function!
         compile call(top)
         skip proc = new tag
         dump jump(JMP, skip proc)
      %finish
      assemble(0,labs,names)
      %if level # 0 %start
         dump label(skip proc)
         last skip = next cad
         Uncond Jump = 0
      %finish
      ->next

   ! To catch the sinners!!
C(*):
      abort("Bad I Code")

c(';'):
   %if amode >= 0 %start;     ! No return code for %endoffile
      dump return
	   dumpstaticfill(staticalloc, frame);
   %finish

OUT:
   %if amode >= 0 %start;         ! end of declarative block
      level = level-1
   %else;                        ! end of record format defn
      %if amode = -2 %start;                  ! end of alternative only
		   frame = max frame %if max frame > frame;	! use the longest alternative
         old frame = frame
      %else
         frame = (frame+align)&(\align);   ! **** temporary ****
         procvar_size = frame
      %finish
   %finish
   frame = old frame

%end;           !  assemble


!              -------- it all starts here ---------

   select input(icode)
   select output(objout) 

   var(0) = 0;              !  for %RECORD(*) . . . . .
   parms = max vars

	cslen == current string(0)

! Initialise the GP Tag ASL
	%for i=1,1,Max GP %cycle
		GP Tags(i)_link = i - 1
	%repeat
	gp asl = Max GP

! Tell the linker our source file name
	dumpsourcename("TEST.IMP");   ! ABD - a bit bogus at the moment!

! predefine the perms for the linker.  We ignore
! the number (j) because we know they are in sequence
	%for i=1,1,lastperm %cycle
		j = externalref(permname(i))
	%repeat

   read symbol(next sym);          !  Prime SYM/NEXT pair

   Spec = 0
   decvar == begin
   assemble(-3,0,0)

! We flush constants
   flush cot
   flush data
   flush switch

%ENDOFPROGRAM
