// Template.bcpl  --  Formatted printing procedure
//			E. Taft  -- September 1976
// Copyright Xerox Corporation 1979


external
   [
   // procedures defined herein
   PutTemplate; PutNum

   // procedures defined elsewhere
   Puts; Wss
   MyFrame; GotoFrame; CoReturn
   SysErr
   ]

manifest
   [
   // error codes
   ecTooFewArgsForTemplate = 2300
   ecMalformedTemplate = 2301
   ]

structure String: [ length byte; char↑1,255 byte ]
structure NT:  // number template passed to PutNum
   [
   radix word	// numeric radix (in range [2,10])
   width word	// minimum field width
   signed word	// true for signed, false for unsigned
   double word	// true if double precision, false if single
   fill word	// fill character to replace leading spaces
   ]


// ----------------------------------------------------------------
let PutTemplate(str,template,args,nil,nil,nil,nil,nil,nil,nil,
   nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil; numargs na) be
// ----------------------------------------------------------------
// Copy the template string "template" to the stream "str",
// performing substitutions for escape sequences in the template
// of the form  $ number letter(s)  , where the letter(s) say what
// to do and the number is an optional argument (which may be
// omitted).  Each such escape sequence will result in substitution
// of the next parameter (starting at "args") at that point,
// with conversion performed as follows:
//	$S	the argument is a BCPL string
//	$US	the argument is an unpacked string
//	$C	the argument is a single character

// The following three specifications may be preceded by any
// combination (represented by "#" below) of:
// (1) a decimal number, specifying the minimum field width.
// (2) "U", specifying "unsigned" (default = signed)
// (3) "E", specifying "extended (32-bit rather than 16 bit, and
//     the argument is a pointer to the 32-bit number rather than
//     the number itself).
// (4) "Fx", specifying a filler character "x" to be used in place
//     of space when replacing leading zeroes.

//	$#D	the argument is a signed decimal number
//	$#O	signed octal
//	$#B	signed binary (for whatever that's worth)

//	$$	this is how you include a "$" literally
//	$P	calls the arg as a procedure, passing it the
//		stream and the next arg as arguments.
[
let frame = InterpretTemplate(str,lv args,na-2)  //init coroutine
for i = 1 to template>>String.length do  //feed chars to coroutine
   GotoFrame(frame,template>>String.char↑i)
]

// ----------------------------------------------------------------
and InterpretTemplate(str,args,nArgs) be
// ----------------------------------------------------------------
// This procedure does the real work.
// It operates as a coroutine with PutTemplate.  Each CoReturn
// returns the next character from the template.
[
// the following variables must be declared in the order
// declared in the structure NT:
let radix, width, signed, double, fill = nil, nil, nil, nil, nil
let nt = lv radix
let i = 0  // init argument counter
   [
   let char = CoReturn(MyFrame())
   test char eq $$
      ifnot Puts(str,char)  //normal char, not in escape sequence
      ifso  //start of escape sequence
         [
         char = CoReturn()  //get char after "$"
         if char eq $$ then [ Puts(str,char); loop ]
         if i ge nArgs then SysErr(nArgs,ecTooFewArgsForTemplate)
         width = 0; signed = true; double = false; fill = $*s
            [  //repeat loop starts here.
               //Command characters that simply modify subsequent
               //characters finish with "endcase" so as to fall
               //through, get the next char, and loop.  Command
               //characters that actually execute the operation
               //and terminate an escape sequence end in "break".
            switchon char into
               [
               //non-terminating modifier characters
               case $0: case $1: case $2: case $3: case $4:
               case $5: case $6: case $7: case $8: case $9:
                  [ width = 10*width+char-$0; endcase ]
               case $U: case $u:  // "unsigned" or "unpacked"
                  [ signed = false; endcase ]
               case $E: case $e:  // "extended" (double precision)
                  [ double = true; endcase ]
               case $F: case $f:  //fill char replacing leading 0's
                  [ fill = CoReturn(); endcase ]

               //terminating command characters
               case $S: case $s:  //string argument
                  [
                  test signed  // U means unpacked in this context
                     ifso Wss(str,args!i)  // normal string
                     ifnot  // unpacked string
                        [
                        let v = args!i
                        for j = 1 to v!0 do Puts(str,v!j)
                        ]
                  break
                  ]
               case $C: case $c:  //single character argument
                  [ Puts(str,args!i); break ]
               case $D: case $d:  //decimal number
                  [ radix = 10; PutNum(str,args!i,nt); break ]
               case $O: case $o:  //octal number
                  [ radix = 8; PutNum(str,args!i,nt); break ]
               case $B: case $b:  //binary number
                  [ radix = 2; PutNum(str,args!i,nt); break ]
               case $P: case $p:  //arbitrary procedure call
                  [ (args!i)(str,args!(i+1)); i = i+1; break ]
               default:
                  SysErr(char,ecMalformedTemplate)
               ]
            char = CoReturn()
            ] repeat
         i = i+1
         ]
   ] repeat
]

// ----------------------------------------------------------------
and PutNum(str,arg,nt) be
// ----------------------------------------------------------------
[
let num0 = nil; manifest n0 = 7  //frame offset 7.
let num1 = nil; manifest n1 = n0+1
let negative = nil
let width = nt>>NT.width
test nt>>NT.double
   ifso
      [  // double-precision number
      num0 = arg!0
      num1 = arg!1
      negative = nt>>NT.signed & num0 ls 0
      ]
   ifnot
      [  // single-precision number
      num1 = arg
      negative = nt>>NT.signed & num1 ls 0
      num0 = negative  //extend sign (note true = -1, false = 0)
      ]
let v = vec 31  // Max space needed for unpacked digits
if negative then
   [
   (table
      [  //double-precision negate
       #21000+n0	// lda 0 n0 2
       #25000+n1	// lda 1 n1 2
      #124423		// negz 1 1 snc
      #100001		//  com 0 0 skp
      #100400		//  neg 0 0
       #41000+n0	// sta 0 n0 2
       #45000+n1	// sta 1 n1 2
        #1401		// jmp 1 3
      ])()
   width = width-1  // Space for minus sign
   ]
let i = 0
   [
   v!i = (table	// Divide (num0,num1) by radix, put quotient back
      [		//  in (num0,num1), and return remainder.
       #55001		// sta 3 1 2
      #155000		// mov 2 3	// preserve frame pointer
      #111000		// mov 0 2	// ac2 ← radix
       #25400+n0	// lda 1 n0 3	// get num0 (high part)
      #102460		// mkzero 0 0	// high dividend  =  0
       #61021		// div
       #77400		// Swat		// if divide fails
       #45400+n0	// sta 1 n0 3	// store high quotient
       #25400+n1	// lda 1 n1 3	// get num1 (low part)
       #61021		// div
       #77400		// Swat
       #45400+n1	// sta 1 n1 3	// store low quotient
      #171000		// mov 3 2	// recover frame
       #35001		// lda 3 1 2
        #1401		// jmp 1 3
      ])(nt>>NT.radix)
   i = i+1
   ] repeatuntil num0 eq 0 & num1 eq 0
while width gr i do [ Puts(str,nt>>NT.fill); width = width-1]
if negative then Puts(str,$-)
while i ne 0 do [ i = i-1;  Puts(str,$0+v!i)]
]