// CmdScanAux.bcpl -- Auxiliary command scanner utilities
// Copyright Xerox Corporation 1979, 1981, 1982

// Last modified April 9, 1982  6:02 PM by Taft

get "cmdscan.decl"

external
[
// outgoing procedures
GetNumber; GetString; GetFile; Confirm; GetKeyword; KeywordHelp

// incoming procedures
GetPhrase; TerminatingChar; CurrentPhrase; AppendChar
LookupKeyword; EnumerateKeywordTable
DefaultArgs; Gets; Puts; Endofs; Errors; Wss; Wns
Allocate; Free; OpenFile; TruePredicate; FalsePredicate; Zero; DoubleAdd
]

structure String [ length byte; char↑1,255 byte ]

//---------------------------------------------------------------------------
let GetNumber(cs, radix, lvResult; numargs na) = valof
//---------------------------------------------------------------------------
// Interprets the next phrase as a signed or unsigned number in the specified
// radix (default 10).  If lvResult is supplied, accepts a 32-bit number and
// stores it in @lvResult.  In any event, returns the low-order 16 bits of
// the result.
[
let digit = nil
let myResult = vec 1
DefaultArgs(lv na, -1, 10, myResult)

// Frame offsets of above args and locals used by assembly language:
manifest [ oRadix = 5; olvResult = 6; oDigit = 8 ]  // na is offset 7

let digitSeen = false
let negative = false
Zero(lvResult, 2)
GetPhrase(cs, 0, 0, 0, NumberHelp, radix)
until Endofs(cs) do
   [
   digit = Gets(cs)-$0
   if digit uge radix then
      [
      if (digit eq $+-$0 % digit eq $--$0) & not digitSeen then
         [ negative = digit eq $--$0; loop ]
      Errors(cs, ecNonNumericChar)
      ]
   digitSeen = true
   if (table
      [  // @lvResult = (@lvResult)*radix + digit with overflow check
       #55001		// sta 3 1 2
      #155000		// mov 2 3
       #11400+olvResult	// isz olvResult 3
       #21400+oDigit	// lda 0 oDigit 3
       #27400+olvResult	// lda 1 @olvResult 3
       #31400+oRadix	// lda 2 oRadix 3
       #61020		// mul
       #47400+olvResult	// sta 1 @olvResult 3
       #15400+olvResult	// dsz olvResult 3
       #27400+olvResult	// lda 1 @olvResult 3
       #61020		// mul
       #47400+olvResult	// sta 1 @olvResult 3
      #171000		// mov 3 2
       #35001		// lda 3 1 2
        #1401		// jmp 1 3
      ])() ne 0 then Errors(cs, ecNumberOverflow)
   ]

unless digitSeen do Errors(cs, ecEmptyNumber)
if lvResult!0 ne 0 & lvResult eq myResult then Errors(cs, ecNumberOverflow)
if negative then
   [
   lvResult!0 = not lvResult!0; lvResult!1 = not lvResult!1
   DoubleAdd(lvResult, table [ 0; 1 ])
   ]
resultis lvResult!1
]

//---------------------------------------------------------------------------
and NumberHelp(str, radix) be
//---------------------------------------------------------------------------
[
switchon radix into
   [
   case 10:
      [ Wss(str, "decimal"); endcase ]
   case 8:
      [ Wss(str, "octal"); endcase ]
   default:
      [ Wss(str, "base "); Wns(str, radix) ]
   ]
Wss(str, " number")
]

//---------------------------------------------------------------------------
and GetString(cs, PhraseTerminator, Help, helpArg, Echo; numargs na) = valof
//---------------------------------------------------------------------------
// Returns the next phrase as a bcpl string
[
DefaultArgs(lv na, -1, 0, 0, 0, 0)
let n = GetPhrase(cs, 0, PhraseTerminator, Echo, Help, helpArg)
let s = Allocate(cs>>CS.zone, n rshift 1 +1)
s>>String.length = n
for i = 1 to n do s>>String.char↑i = Gets(cs)
resultis s
]

//---------------------------------------------------------------------------
and GetFile(cs, ksType, itemSize, versionControl, hintFp, errRtn, zone,
    logInfo, disk; numargs na) = valof
//---------------------------------------------------------------------------
// Returns a stream for a file whose name is the next phrase.
// All arguments except the first are passed directly to OpenFile.
[
DefaultArgs(lv na, -1, 0, 0, 0, 0, 0, 0, 0, 0)
let string = GetString(cs, 0, Wss, "file name")
let stream = OpenFile(string, ksType, itemSize, versionControl, hintFp,
 errRtn, zone, logInfo, disk)
Free(cs>>CS.zone, string)
if stream eq 0 then Errors(cs, ecCantOpenFile)
resultis stream
]

//---------------------------------------------------------------------------
and Confirm(cs, string; numargs na) = valof
//---------------------------------------------------------------------------
// Inputs and returns a true or false confirmation
[
if na gr 1 & string ne 0 then Wss(cs, string)
Wss(cs, " [Confirm] ")
GetPhrase(cs, TruePredicate, TruePredicate, FalsePredicate, Wss, "y or n")
switchon TerminatingChar(cs) into
   [
   case $Y: case $y: case $*n:
      [ Wss(cs, "yes. "); resultis true ]
   case $N: case $n:
      [ Wss(cs, "no. "); resultis false ]
   default:
      Errors(cs, ecBadConfirmingChar)
   ]
]

//---------------------------------------------------------------------------
and GetKeyword(cs, kt, returnOnFail, PhraseTerminator; numargs na) = valof
//---------------------------------------------------------------------------
// Looks up phrase in keyword table kt and returns pointer to corresponding
// table entry.  If returnOnFail is true, returns zero on failure.
// If an abbreviated keyword is recognized, appends the remainder of the
// keyword to the command line iff the terminating character did not echo.
[
DefaultArgs(lv na, -2, false, 0)
let key = GetString(cs, PhraseTerminator, KeywordHelp, kt)
let length = key>>String.length
let tableKey = nil
let entry = LookupKeyword(kt, key, lv tableKey)
Free(cs>>CS.zone, key)
if entry eq 0 then
   test tableKey eq 0 % length eq 0  // ambiguous or outright failure?
      ifso resultis returnOnFail? 0, Errors(cs, ecKeyNotFound)
      ifnot Errors(cs, ecKeyAmbiguous)
if length ls tableKey>>String.length then
   [
   let char = TerminatingChar(cs)
   unless CurrentPhrase(cs)>>PD.Echo(cs, char) do
      [  // append tail of keyword as if it had been typed in
      cs>>CS.iChIn = cs>>CS.iChIn-1
      for i = length+1 to tableKey>>String.length do
         Puts(cs, tableKey>>String.char↑i)
      CurrentPhrase(cs)>>PD.iLast = cs>>CS.iChIn
      AppendChar(cs, char, false)
      ]
   ]
resultis entry
]

//---------------------------------------------------------------------------
and KeywordHelp(str, kt) be
//---------------------------------------------------------------------------
[
Wss(str, "one of the following:*n")
let count, str1 = 0, str
EnumerateKeywordTable(kt, PrintKeyword, lv count)
]

//---------------------------------------------------------------------------
and PrintKeyword(entry, kt, key, lvCount) be
//---------------------------------------------------------------------------
[
let count = @lvCount
let str = lvCount!1
test count+key>>String.length gr 60
   ifso [ Wss(str, ",*n"); count = 0 ]
   ifnot if count ne 0 then [ Wss(str, ", "); count = count+2 ]
Wss(str, key)
@lvCount = count+key>>String.length
]