// SCI1.bcpl -- companion to SCI.bcpl
// Last modified May 30, 1983  1:50 PM by Boggs

get "SCI.decl"
get "Streams.d"

external
[
// outgoing procedures
WLFile; OtherFile
ReadToken; OutPut; FlushBlock

// incoming procedure -- SCI
TSP

// incoming procedures -- packages
ConcatenateStrings; StringCompare
PutTemplate; ExtractSubstring

// incoming procedures -- OS
OpenFile; Closes; Gets; Puts; Endofs
EraseBits; CharWidth; Wss
Allocate; Free; CallSwat

// incoming statics -- OS
sysZone; dsp; keys

// incoming statics -- SCI
lf; in; out; cpuType
token; board; numBlocks
block; blockCnt; record; recordCnt
]

static termChar

//----------------------------------------------------------------------------------------
let WLFile() be
//----------------------------------------------------------------------------------------
// Reads 'Board.wl', the wirelist produced by Route,
//  and generates the PARTS & NETLIST subfiles for SCI.
[
let wlName = ConcatenateStrings(board, ".wl")
let wl = OpenFile(wlName, ksTypeReadOnly, charItem)
Free(sysZone, wlName)
test wl
   ifso [ PutTemplate(dsp, "*NReading $S.wl", board); in = wl ]
   ifnot [ PutTemplate(dsp, "*NFailed to open $S.wl", board); return ]

Wss(dsp, "*NMultibus or Dandelion board (M or D): ")
let boardType = Gets(keys) & 137b; Puts(dsp, boardType)
ReadToken()  //board type. discard.

// Make parts list
Wss(out, "*NPARTS")
let width, length, pins = nil, nil, nil
   [
   ReadToken()  //part designator
   if token>>String.length eq 0 %
    (token>>String.length eq 1 & token>>String.char↑1 eq $@) break
   let desig = ExtractSubstring(token, (token>>String.char↑1 eq $#? 2, 1))
   ReadToken()
   let firstSlash, lastSlash, len = 0, 0, token>>String.length
   for i = 1 to len if token>>String.char↑i eq $/ test firstSlash eq 0
      ifso firstSlash = i
      ifnot lastSlash = i
   width = 3; for i = lastSlash+1 to len do
      if IsNumber(i, i) then width = token>>String.char↑i - $0
   pins = 0; for i = firstSlash+1 to lastSlash-1 do
      pins = pins*10 + (token>>String.char↑i - $0)
   length = width eq 1?  pins, pins/2
   PutTemplate(out, "*NHOLE MODULE HM$2F0D$2F0D$2F0D $S", width, length, pins, desig)
   Free(sysZone, desig)
   ] repeat
Wss(out, "*NEOS*N")

// Make net list
Wss(out, "*NNET LIST CONTINUE")
let flush, nodeCnt, netName = false, 0, 0
   [  //main loop
   manifest	//token types
      [
      calibrate = 1; signal = 2; boardLoc = 3
      gnd = 4; gndNet = 5; gndNub = 6
      vcc = 7; vccNet = 8; vccNub = 9
      pointyBracket = 10; parenthesis = 11; curlyBracket = 12
      edgePin = 13; connPin = 14
      ]
   ReadToken()
   let len = token>>String.length; if len eq 0 break
   let tokenType = signal
   if StringCompare("CALIBRATE", token) eq 0 then tokenType = calibrate
   if StringCompare("GND", token) eq 0 then tokenType = gnd
   if StringCompare("GND", token) eq -2 test token>>String.char↑4 eq $-
      ifso if IsNumber(5, len) then tokenType = gndNet
      ifnot if IsNumber(4, len) then tokenType = termChar eq $:? gndNet, gndNub
   if StringCompare("VCC", token) eq 0 then tokenType = vcc
   if StringCompare("VCC", token) eq -2 test token>>String.char↑4 eq $-
      ifso if IsNumber(5, len) then tokenType = vccNet
      ifnot if IsNumber(4, len) then tokenType = termChar eq $:? vccNet, vccNub
   let firstChar, lastChar = token>>String.char↑1, token>>String.char↑len
   if firstChar eq $( & lastChar eq $) then tokenType = parenthesis
   if firstChar eq ${ & lastChar eq $} then tokenType = curlyBracket
   if firstChar eq $< & lastChar eq $> then tokenType = pointyBracket
   if (firstChar eq $E % firstChar eq $C) & IsNumber(2, len) then
      tokenType = firstChar eq $E? edgePin, connPin
   if tokenType eq signal & (termChar eq $*S % firstChar eq $#) then tokenType = boardLoc

   // NetList (cont'd)

   if tokenType eq edgePin % tokenType eq connPin % tokenType eq boardLoc then
      [
      if flush loop
      if nodeCnt rem 5 eq 0 test nodeCnt ne 0 & nodeCnt rem 50 ne 0
         ifso Wss(out, "$*N   ")
         ifnot PutTemplate(out, "*NNN $S ", netName)
      nodeCnt = nodeCnt +1
      ]

   switchon tokenType into
      [
      case calibrate: [ flush = true; endcase ]
      case signal:
         [
         if netName ne 0 then Free(sysZone, netName)
         netName = ExtractSubstring(token)
         flush, nodeCnt = false, 0
         endcase
         ]
      case gndNet: token>>String.length = 3
      case gnd: test StringCompare("GND", netName) ifso docase signal ifnot endcase
      case vccNet: token>>String.length = 3
      case vcc: test StringCompare("VCC", netName) ifso docase signal ifnot endcase
      case edgePin: case connPin:
         [
         switchon boardType into
            [
            case $M: [ Multibus(); endcase ]
            case $D: test tokenType eq edgePin
               ifso [ DandelionE(); endcase ]
               ifnot [ DandelionC(); endcase ]
            default: [ Universal(); endcase ]
            ]
         endcase
         ]
      case boardLoc:
         [
         let dot = 0; for i = 1 to len do
            if token>>String.char↑i eq $. then [ dot = i; break ]
         let firstChar = token>>String.char↑1 eq $#? 2, 1
         for i = firstChar to dot-1 do Puts(out, token>>String.char↑i)
         Puts(out, $*S)
         for i = dot+1 to len-1 do Puts(out, token>>String.char↑i)
         Puts(out, $*S)
         endcase
         ]
      ]
   ] repeat
Wss(out, "*NEOS*N")
Closes(wl)
]

//----------------------------------------------------------------------------------------
and Universal() be
//----------------------------------------------------------------------------------------
[
Puts(out, token>>String.char↑1)
Puts(out, $*S)
for i = 2 to token>>String.length do
   Puts(out, token>>String.char↑i)
Puts(out, $*S)
]

//----------------------------------------------------------------------------------------
and DandelionE() be
//----------------------------------------------------------------------------------------
[
let number = 0
for i = 2 to token>>String.length do
   number = number*10 + token>>String.char↑i -$0

let conn, side, pin = nil, nil, nil
if number ge 1 & number le 50 then
   [ conn = $2; side = $T; pin = 51-number ]
if number ge 51 & number le 100 then
   [ conn = $1; side = $T; pin = 101-number ]
if number ge 101 & number le 150 then
   [ conn = $2; side = $B; pin = 151-number ]
if number ge 151 & number le 200 then
   [ conn = $1; side = $B; pin = 201-number ]

PutTemplate(out, "P$C-$C $D ", conn, side, pin)
]

//----------------------------------------------------------------------------------------
and DandelionC() be
//----------------------------------------------------------------------------------------
[
let number = 0
for i = 2 to token>>String.length do
   number = number*10 + token>>String.char↑i -$0

let conn = number ls 100? $1, $2
let pin = number ls 100? number, number-100

PutTemplate(out, "J$C $D ", conn, pin)
]

//----------------------------------------------------------------------------------------
and Multibus() be
//----------------------------------------------------------------------------------------
[
test token>>String.char↑1 eq $C
   ifso [ Puts(out, $J); Puts(out, token>>String.char↑2 -2) ]
   ifnot [ Puts(out, $P); Puts(out, token>>String.char↑2) ]
Wss(out, (token>>String.char↑4 & 1) ne 0? "-T ", "-B ")
let n = ((10*(token>>String.char↑3 -$0)) + (token>>String.char↑4 -$0) +1)/2
Puts(out, $0 + n/10)
Puts(out, $0 + n rem 10)
Puts(out, $*S)
]

//----------------------------------------------------------------------------------------
and OtherFile() be
//----------------------------------------------------------------------------------------
// Reads Board.other and writes it unmodified to tape.
// Records are delimited by bare carriage returns.
[
let otherName = ConcatenateStrings(board, ".other")
let other = OpenFile(otherName, ksTypeReadOnly, charItem)
Free(sysZone, otherName)
test other
   ifso [ PutTemplate(dsp, "*NReading $S.other", board); in = other ]
   ifnot [ PutTemplate(dsp, "*NFailed to open $S.other", board); return ]

until Endofs(other) do Puts(out, Gets(other))

Puts(out, $*N)
Closes(other)
]

//----------------------------------------------------------------------------------------
and OutPut(st, char) be
//----------------------------------------------------------------------------------------
// ST.puts operation for the stream 'out'.
// It feeds characters to a plain text listing file and to the tape.
// Carriage returns are replaced by record boundaries on the tape.
[
Puts(lf, char)
Puts(dsp, char)
if char ne $*N then
   [
   record>>Byte↑recordCnt = char
   recordCnt = recordCnt +1
   if recordCnt eq (cpuType eq $V? 80, 81) then CallSwat("Record too long")
   return
   ]

// end of record
test cpuType eq $V
   ifso  //RecLen = var, max 74, BlkLen = 2048, Fortran carriage control
      [
      if recordCnt eq 5 then recordCnt = 4
      if 2048-blockCnt ls recordCnt then FlushBlock()
      record>>Byte↑0 = $0
      record>>Byte↑1 = $0
      record>>Byte↑2 = $0 + recordCnt/10
      record>>Byte↑3 = $0 + recordCnt rem 10
      record>>Byte↑4 = $*S  //Fortran carriage control!
      for i = 0 to recordCnt-1 do
         block>>Byte↑(blockCnt+i) = record>>Byte↑i
      blockCnt = blockCnt + recordCnt
      recordCnt = 5
      ]
   ifnot  //RecLen = 80, BlkLen = 800  (Prime computer system)
      [
      if blockCnt eq 800 then FlushBlock()
      for i = 0 to recordCnt-1 do
         block>>Byte↑(blockCnt+i) = record>>Byte↑i
      for i = recordCnt to 79 do
         block>>Byte↑(blockCnt+i) = $*S  //pad record with $*S
      blockCnt = blockCnt + 80
      recordCnt = 0
      ]
]

//----------------------------------------------------------------------------------------
and FlushBlock() be
//----------------------------------------------------------------------------------------
// Flushes the current block to tape
[
if blockCnt eq 0 return
test cpuType eq $V
   ifso
      [
      for i = blockCnt to 2047 do block>>Byte↑i = 136b  //pad block with $*136
      TSP(cmdWriteRecord, block, 0, 2048)
      ]
   ifnot
      [
      for i = blockCnt to 799 do block>>Byte↑i = $*S  //pad block with $*S
      TSP(cmdWriteRecord, block, 0, 800)
      ]
numBlocks = numBlocks +1
blockCnt = 0
]

//----------------------------------------------------------------------------------------
and ReadToken() be
//----------------------------------------------------------------------------------------
// Reads a token from the 'in' stream and puts it in 'token'.
// Note that it may have 0 characters if EOF (disk stream) or <Del> (key stream).
// Terminating character is left in static 'termChar'.
[
let count = 0
until Endofs(in) & in ne keys do
   [
   termChar = Gets(in)
   switchon termChar into
      [
      case $*001: case $*010:
         [
         if count ne 0 & in eq keys then
            [
            EraseBits(dsp, -CharWidth(dsp, token>>String.char↑count))
            count = count -1
            ]
         endcase
         ]
      case $;:	//flush input stream until CR
         until Endofs(in) % Gets(in) eq $*N loop  //fall through
      case $*N: case $*S: case $*T: case $::
         [
         if count ne 0 break
         endcase
         ]
      case $*177:
         if in eq keys then
            [ Wss(dsp, " XXX "); count = 0; break ]
      default:
         [
         count = count +1
         if termChar ge $a & termChar le $z then termChar = termChar & 137b
         if termChar eq $← then termChar = $<
         token>>String.char↑count = termChar
         if in eq keys then Puts(dsp, termChar)
         endcase
         ]
      ]
   ]
token>>String.length = count
]

//----------------------------------------------------------------------------------------
and IsNumber(first, last) = valof
//----------------------------------------------------------------------------------------
[
if last ls first resultis false
for i = first to last do
   if token>>String.char↑i ls $0 % token>>String.char↑i gr $9 resultis false
resultis true
]