// BFSTest.bcpl -- Basic File System test program
// Copyright Xerox Corporation 1979, 1982
// Last modified March 28, 1982  3:46 PM by Boggs

get "SysInternals.d"
get "AltoFileSys.d"
get "AltoDefs.d"
get "Streams.d"
get "Disks.d"
get "BFS.d"

external
[
// outgoing procedures
AfterJunta; MyIdle; SysErr
GetString; GetNumber; Confirm
Ws; Wss; Ding; UpdateTitle

// incoming procedures from OS and packages
Puts; Gets; Resets; Allocate; Free; AddToZone
EraseBits; CharWidth; InvertLine; GetLinePos; SetBitPos
CreateKeywordTable; EnumerateKeywordTable
InsertKeyword; LookupKeyword
ExtractSubstring; CopyString
PutTemplate; DefaultArgs

// incoming procedures from other BFSTest modules
BeforeJuntaInit; AfterJuntaInit
Exercise; Certify; Erase; CreateFile

// incoming statics
lvUserFinishProc
keys; dsp; sysZone; eng; title
]

static [ savedUFP; kbdKT ]

manifest
[
editAppend = 0
editEcho = 1
editReplace = 2
]

structure String [ length byte; char↑1,1 byte ]

//----------------------------------------------------------------------------
let BFSTest() be BeforeJuntaInit()
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and AfterJunta() be
//----------------------------------------------------------------------------
[
AfterJuntaInit()
AddToZone(sysZone, BeforeJuntaInit, sysZone-BeforeJuntaInit)
savedUFP = @lvUserFinishProc; @lvUserFinishProc = MyFinishProc
Command()  //never returns
]

//----------------------------------------------------------------------------
and MyFinishProc() be
//----------------------------------------------------------------------------
[
@activeInterrupts = @activeInterrupts & not kbInterruptBit
@displayInterrupt = @displayInterrupt & not kbInterruptBit
@displayListHead = 0; for i = 0 to 30000 loop
@lvUserFinishProc = savedUFP
]

//----------------------------------------------------------------------------
and SysErr(p1, errNo, p2, p3, p4, p5) be
//----------------------------------------------------------------------------
[
let temp = p1; p1 = errNo; errNo = temp
(table [ 77403b; 1401b ])("Sys.Errors", lv p1)
]

//----------------------------------------------------------------------------
and MyIdle() be
//----------------------------------------------------------------------------
[
let MulDiv = table
   [
   055001B	// sta 3 savedPC,2
   155000B	// mov 2 3
   111000B	// mov 0 2
   102460B	// mkzero 0 0
   061020B	// mul
   031403B	// lda 2 3 3
   061021B	// div
   077400B	// Swat
   121000B	// mov 1 0
   171000B	// mov 3 2
   035001B	// lda 3 savedPC,2
   001401B	// jmp 1,3
   ]
@mouseX = 200 + 200*diskAddress>>DA.disk
@mouseY = diskAddress>>DA.track ls 0? 0,
 20 + MulDiv(808-40-16, diskAddress>>DA.track, 406)
]

//----------------------------------------------------------------------------
and Command() be
//----------------------------------------------------------------------------
[
kbdKT = CreateKeywordTable(7, 1)
InsertKeyword(kbdKT, "Certify")!0 = Certify
InsertKeyword(kbdKT, "CreateFile")!0 = CreateFile
InsertKeyword(kbdKT, "Erase")!0 = Erase
InsertKeyword(kbdKT, "Exercise")!0 = Exercise
InsertKeyword(kbdKT, "Help")!0 = Help
if eng gr 3 then InsertKeyword(kbdKT, "Partition")!0 = Partition
InsertKeyword(kbdKT, "Quit")!0 = Quit

   [
   Ws("*N**")
   let key = 0
      [
      key = GetString(0, key, editEcho+editAppend, CmdList)
      if key eq 0 break
      let tableKey = nil
      let kte = LookupKeyword(kbdKT, key, lv tableKey)
      if kte eq 0 then [ Ding(dsp); loop ]
      for i = key>>String.length+1 to tableKey>>String.length do
         Puts(dsp, tableKey>>String.char↑i)
      Free(sysZone, key)
      (kte!0)()  //execute command
      break
      ] repeat
   ] repeat
]

//----------------------------------------------------------------------------
and CmdList() be
//----------------------------------------------------------------------------
[
Ws("? one of the following:*N")
let count = 0
EnumerateKeywordTable(kbdKT, PrintCmd, lv count)
if count ne 0 then Puts(dsp, $*N)
Puts(dsp, $**)
]

//----------------------------------------------------------------------------
and PrintCmd(kte, kt, key, lvCount) be
//----------------------------------------------------------------------------
[
unless @lvCount eq 0 do Ws(", ")
Ws(key)
test @lvCount eq 5
   ifso [ @lvCount = 0; Puts(dsp, $*N) ]
   ifnot @lvCount = @lvCount +1
]

//----------------------------------------------------------------------------
and Quit() be finish
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and Help() be
//----------------------------------------------------------------------------
[
Ws("*NThis is the Basic File System (BFS) test and utility program.")
Ws("*NWARNING: several commands can destroy the contents of disks.")
Ws("*NIf you don't know what you are doing, you should QUIT now.")
]

//----------------------------------------------------------------------------
and Partition() be
//----------------------------------------------------------------------------
[
let currentPartition = (table [ 61037b; 1401b ])(0)
let newPartition = GetNumber(" number: ", currentPartition)
let result = (table [ 61037b; 1401b ])(newPartition)
if currentPartition ne newPartition then UpdateTitle()
]

//----------------------------------------------------------------------------
and UpdateTitle() be
//----------------------------------------------------------------------------
[
Resets(title)
InvertLine(title, GetLinePos(title))
Wss(title, "BFSTest of May 6, 1982  6:21 PM")
if eng gr 3 then
   [
   SetBitPos(title, 400)
   PutTemplate(title, "Partition $D", (table [ 61037b; 1401b ])(0))
   ]
]

//----------------------------------------------------------------------------
and GetString(prompt, def, mode, question; numargs na) = valof
//----------------------------------------------------------------------------
[
DefaultArgs(lv na, 0, 0, 0, editEcho+editReplace, 0)
let echo = (mode & editEcho) ne 0
let replace = (mode & editReplace) ne 0
if prompt then Ws(prompt)
let string, count = vec 128, 0
if def then
   [
   count = def>>String.length
   CopyString(string, def)
   if echo & replace then Ws(def)
   Free(sysZone, def)
   ]

   [
   let char = Gets(keys)
   switchon char into
      [
      case $*001: case $*010:
         [
         replace = false
         if count ne 0 then
            [
            if echo ne 0 then
               EraseBits(dsp, -CharWidth(dsp, string>>String.char↑count))
            count = count -1
            ]
         endcase
         ]
      case $*S: case $*N: case $*033: break
      case $?:
         [
         if count eq 0 & question ne 0 then
            [ question(); if prompt then Ws(prompt) ]
         endcase
         ]
      case $*177:
         [ Ws(" XXX"); resultis 0 ]
      default:
         [
         if char eq $*027 % replace then
            [
            if echo then for i = count to 1 by -1 do
               EraseBits(dsp, -CharWidth(dsp, string>>String.char↑i))
            count, replace = 0, false
            ]
         if char ge $*S & char le $*177 then
            [
            count = count +1
            string>>String.char↑count = char
            if echo then Puts(dsp, char)
            ]
         endcase
         ]
      ]
   ] repeat

if count eq 0 resultis 0
string>>String.length = count
resultis ExtractSubstring(string)
]

//----------------------------------------------------------------------------
and GetNumber(prompt, def, radix; numargs na) = valof
//----------------------------------------------------------------------------
[
DefaultArgs(lv na, 0, 0, 0, 10)
if prompt then Ws(prompt)
if na gr 1 then PutTemplate(dsp, "$D", def)
let number = def
let digitTyped = na gr 1
   [
   let char = Gets(keys)
   switchon char into
      [
      case $*N: case $*S: case $*033:
         [ if digitTyped resultis number; endcase ]
      case $*177:
         [ Ws(" XXX"); resultis 0 ]
      case $0 to $9:
         [
         if na gr 1 then
            [
            na = 0
            while number ne 0 do
               [
               EraseBits(dsp, -CharWidth(dsp, (number rem radix)+$0))
               number = number/radix
               ]
            ]
         number = number*radix + char-$0
         Puts(dsp, char)
         digitTyped = true
         endcase
         ]
      case $*001: case $*010:
         [
         na = 0
         if number ne 0 then
            EraseBits(dsp, -CharWidth(dsp, (number rem radix)+$0))
         number = number/radix
         endcase
         ]
      ]
   ] repeat
]

//----------------------------------------------------------------------------
and Confirm(prompt) = valof
//----------------------------------------------------------------------------
[
Ws(prompt)
switchon Gets(keys) into
   [
   case $Y: case $y: case $*N:
      [ Ws("Yes"); resultis true ]
   case $N: case $n: case $*177:
      [ Ws("No"); resultis false ]
   case $?:
      [ Ws("Y, y, <cr>, or N, n, <del>"); loop ]
   default:
      [ Ding(dsp); endcase ]
   ] repeat
]

//----------------------------------------------------------------------------
and Wss(stream, string) be
//----------------------------------------------------------------------------
   for i = 1 to string>>String.length do
      Puts(stream, string>>String.char↑i)

//----------------------------------------------------------------------------
and Ws(string) be Wss(dsp, string)
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
and Ding(stream) be
//----------------------------------------------------------------------------
[
let dcb = stream>>DS.fdcb
   [
   dcb>>DCB.background = not dcb>>DCB.background
   dcb = dcb>>DCB.next
   if dcb eq stream>>DS.ldcb break
   ] repeat
for i = 0 to 32000 loop
let dcb = stream>>DS.fdcb
   [
   dcb>>DCB.background = not dcb>>DCB.background
   dcb = dcb>>DCB.next
   if dcb eq stream>>DS.ldcb break
   ] repeat
]