// DLSConfig.bcpl -- Alto DLS configuration file reader

// Last modified June 20, 1982  10:46 AM by Taft
// Last modified January 15, 1985  11:11 AM by Diebert

get "DLSDriver.decl"
get "Pup.decl"
get "DLSControl.decl"
get "DLSConfig.decl"


// Syntax of DLS configuration file:
// One or more entries, each terminated by return.
// <entry> ::= <line> <description> | // <comment>
// <line> ::= <octal line number of line being described>
// <description> ::= <attribute> | <description> <attribute>
// <attribute> ::= <lineType> | <controlLine> | <defaultHost> |
// 	<baudRate> | <terminalType> | <length> | <width> |
// 	<diallerInfo> | <stopBits> | <loopLine> | <otherAttribute>
// <lineType> ::= Unused | Hardwired | DataSet | Telenet | Log | <diallerStuff>
// <diallerStuff> ::= Dialler <diallerSignal> <diallerType> | Dialler <diallerSignal>
// <controlLine> ::= Control <line>
// <defaultHost> ::= Host <hostName>
// <baudRate> ::= Baud <decimal number>
// <terminalType> ::= Type <decimal number>	// Tenex terminal type
// <length> ::= Length <decimal number>		// Lines/page
// <width> ::= Width <decimal number>		// Chars/line
// <diallerInfo> ::= DialOut <decimal number> <decimal number>
// <stopBits> ::= Stop 1 | Stop 2
// <loopLine> ::= Loop <line>			// Implemented only in ChollaDLS
// <diallerSignal> ::= CRQ | DP | NB1 | NB2 | NB4 | NB8
// <diallerType> ::= DiallerType <decimal number>
// <otherAttribute> ::= DialOutOnly | NoPad | 8Bit

// Each entry must contain a <lineType>, and entries with <lineType> of
// DataSet or Telenet must contain a <ControlLine>.

// If a <baudRate> is specified, the line will run at that fixed rate.
// Normally <baudRate> is omitted, and speed determination is performed
// at connection time.

// Dial-out lines, as well as having a <controlLine> attribute, also
// have a <diallerInfo> attribute with two arguments:
//	Modem address, i.e., Vadic card cage slot number (1-16) in the
//		cage that has the dialler.
//	Modem type: 1 = 103-compatible modem, 0 = Vadic modem
//		3 = 103/212 compatible modem, 2 = illegal

// There are 6 lines that control the dialler itself; each is declared by
// a "Dialler <diallerSignal>" entry, where the argument is the name
// of the output signal being controlled.  Some of the lines also have
// input signals connected to them; the correspondence is:
//	output:	CRQ	DP	NB1	NB2	NB4	NB8
//	input:	PND	ACR	DSS	--	--	--

// Each dialler has an associated DiallerType; declared by a
// a "DiallerType <decimal number>" entry, wher the number is 
// 1 for a Vadic VA811 dialler and 0 for any other types

// You must code one "Log" line

// DialOutOnly means that the line is controlled exclusively from the network
// side; input from the line is ignored when the line is idle.

external
   [
// Outgoing procedures
   ReadDLSConfig

// Incoming procedures
   OpenFile; Closes; Gets; FilePos; SetFilePos; ReadUserCmItem
   Zero; CallSwat; Allocate; MoveBlock
   ReturnFrom
   ]

static [ file; char; startPos; uniqueStringList = 0 ]

structure UniqueStr:
   [
   next word
   string @String
   ]

// ---------------------------------------------------------------------------
let ReadDLSConfig(dlsConfig, zone) be
// ---------------------------------------------------------------------------
// Reads DLS configuration file and loads data into dlsConfig.
// Calls Swat if the file is malformed.
[
Zero(dlsConfig, lenDLSConfig)
let parsed = false

// First look for [DLS] section in User.cm
let userCm = OpenFile("User.cm", ksTypeReadOnly, charItem)
if userCm ne 0 then
   [
   let token = vec 127
   let inDLSSection = false
   switchon ReadUserCmItem(userCm, token) into
      [ // repeat
      case $N:
         inDLSSection = StringEq(token, "DLS")
         endcase
      case $L:
         if inDLSSection then
            test StringEq(token, "Indirect") ifso
               [  // Indirect: fileName
               ReadUserCmItem(userCm, token)
               ParseDLSConfig(dlsConfig, token, zone)
               parsed = true
               ]
            ifnot test StringEq(token, "Name") ifso
               [  // Name: name
               ReadUserCmItem(userCm, token)
               dlsConfig>>DLSConfig.name = MakeUniqueString(token, zone)
               ]
            ifnot test StringEq(token, "Registry") ifso
               [  // Registry: reg
               ReadUserCmItem(userCm, token)
               dlsConfig>>DLSConfig.thisRegistry = MakeUniqueString(token, zone)
               ]
            ifnot test StringEq(token, "AuthorizationList") ifso
               [  // AuthorizationList: list
               ReadUserCmItem(userCm, token)
               dlsConfig>>DLSConfig.thisOutList = MakeUniqueString(token, zone)
               ]
            ifnot test StringEq(token, "InAuthorizationList") ifso
               [  // AuthorizationList: list
               ReadUserCmItem(userCm, token)
               dlsConfig>>DLSConfig.thisInList = MakeUniqueString(token, zone)
               ]
            ifnot test StringEq(token, "WizardList") ifso
               [  // WizardList: list
               ReadUserCmItem(userCm, token)
               dlsConfig>>DLSConfig.thisWizardList = MakeUniqueString(token, zone)
               ]


            ifnot CallSwat("[ReadDLSConfig] Undefined User.cm item:", token)
         endcase
      case $E:
         break
      ] repeat
   Closes(userCm)
   ]

// If nothing found in User.cm, assume correct name is DLS.config.
unless parsed do ParseDLSConfig(dlsConfig, "DLS.config", zone)
if dlsConfig>>DLSConfig.name eq 0 then
   dlsConfig>>DLSConfig.name = MakeUniqueString("DLS", zone)
if dlsConfig>>DLSConfig.thisRegistry eq 0 then
   dlsConfig>>DLSConfig.thisRegistry = MakeUniqueString("PA", zone)
if dlsConfig>>DLSConfig.thisWizardList eq 0 then
   dlsConfig>>DLSConfig.thisWizardList = MakeUniqueString("DLSWizards↑.PA", zone)
]

// ---------------------------------------------------------------------------
and ParseDLSConfig(dlsConfig, fileName, zone) be
// ---------------------------------------------------------------------------
[
file = OpenFile(fileName, ksTypeReadOnly, charItem)
if file eq 0 then CallSwat("[ReadDLSConfig] Can't find configuration file:", fileName)
let MyStreamError(file, ec) be [ Closes(file); ReturnFrom(ParseDLSConfig) ]
file>>ST.error = MyStreamError

   [  //repeat
   char = Gets(file) repeatwhile char eq $*n % char eq $*s % char eq $*t % char eq $*014
   if char eq $; % char eq $/ then
      [ char = Gets(file) repeatuntil char eq $*n; loop ]
   startPos = FilePos(file)-1
   let line = ReadLineNumber(file)
   let lc = lv dlsConfig>>DLSConfig.lc↑line
   if lc>>LC.lineType ne ltUnused then
      Malformed("[ReadDLSConfig] Duplicate line entry")
   let string = vec 127
   until char eq $*n do
      [
      ReadToken(file, string)
      test StringEq(string, "Baud") ifso
         lc>>LC.data.constantBaud = ReadNumber(file)
      ifnot test StringEq(string, "Control") ifso
         [
         let otherLine = ReadLineNumber(file)
         lc>>LC.otherLine = otherLine
         dlsConfig>>DLSConfig.lc↑otherLine.lineType = ltControl
         dlsConfig>>DLSConfig.lc↑otherLine.otherLine = line
         ]
      ifnot test StringEq(string, "DataSet") ifso
         lc>>LC.lineType = ltDataSet
      ifnot test StringEq(string, "Log") ifso
         lc>>LC.lineType = ltLog
      ifnot test StringEq(string, "Dialler") ifso
         [
         lc>>LC.lineType = ltDialler
         ReadToken(file, string)
         lc>>LC.dialler.signalIndex =
          StringEq(string, "CRQ")? 0, StringEq(string, "DP")? 1,
          StringEq(string, "NB1")? 2, StringEq(string, "NB2")? 3,
          StringEq(string, "NB4")? 4, StringEq(string, "NB8")? 5, Malformed()
         ]
      ifnot test StringEq(string, "DiallerType") ifso
         lc>>LC.data.diallerType = ReadRangeCheckedNumber(file, 10, 0, 1)
      ifnot test StringEq(string, "DialOut") ifso
         [
         lc>>LC.dialOut = true
         lc>>LC.data.modemAddress = ReadRangeCheckedNumber(file, 10, 1, 15)-1
         lc>>LC.data.modemType = ReadRangeCheckedNumber(file, 10, 0, 3)
         ]
      ifnot test StringEq(string, "DialOutOnly") ifso
         lc>>LC.data.dialOutOnly = true
      ifnot test StringEq(string, "Hardwired") ifso
         lc>>LC.lineType = ltHardwired
      ifnot test StringEq(string, "Host") ifso
         lc>>LC.data.host = ReadUniqueString(file, zone)
      ifnot test StringEq(string, "Length") ifso
         lc>>LC.data.terminalLength = ReadNumber(file)
      ifnot test StringEq(string, "Loop") ifso
         lc>>LC.otherLine = ReadLineNumber(file)
      ifnot test StringEq(string, "NoPad") ifso
         lc>>LC.data.noPad = true

//  ParseDLSConfig(dlsConfig, fileName, zone) continued

      ifnot test StringEq(string, "8Bit") ifso
         lc>>LC.data.eightBit = true
      ifnot test StringEq(string, "Stop") ifso
         lc>>LC.data.stopBits = ReadRangeCheckedNumber(file, 10, 1, 2)
      ifnot test StringEq(string, "Telenet") ifso
         lc>>LC.lineType = ltTelenet
      ifnot test StringEq(string, "Type") ifso
         lc>>LC.data.terminalType = ReadNumber(file)
      ifnot test StringEq(string, "Unused") ifso
         lc>>LC.lineType = ltUnused
      ifnot test StringEq(string, "Width") ifso
         lc>>LC.data.terminalWidth = ReadNumber(file)
      ifnot Malformed("[ReadDLSConfig] Undefined attribute")
      ]
   ] repeat
]

// ---------------------------------------------------------------------------
and ReadLineNumber(file) = ReadRangeCheckedNumber(file, 8, 0, numLines-1)
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
and ReadRangeCheckedNumber(file, radix, min, max) = valof
// ---------------------------------------------------------------------------
[
let num = ReadNumber(file, radix)
if num ls min % num gr max then
   Malformed("[ReadDLSConfig] Number out of range")
resultis num
]

// ---------------------------------------------------------------------------
and ReadNumber(file, radix; numargs na) = valof
// ---------------------------------------------------------------------------
[
if na ls 2 then radix = 10
let string = vec 127
let length = ReadToken(file, string)
let num = 0
for i = 1 to length do
   [
   let digit = string>>String.char↑i - $0
   if digit ls 0 % digit ge radix then Malformed()
   num = radix*num + digit
   ]
resultis num
]

// ---------------------------------------------------------------------------
and ReadUniqueString(file, zone) = valof
// ---------------------------------------------------------------------------
[
let string = vec 127
ReadToken(file, string)
resultis MakeUniqueString(string, zone)
]

// ---------------------------------------------------------------------------
and MakeUniqueString(string, zone) = valof
// ---------------------------------------------------------------------------
[
let uniqueStr = uniqueStringList
while uniqueStr ne 0 do
   [
   if StringEq(string, lv uniqueStr>>UniqueStr.string) then
      resultis lv uniqueStr>>UniqueStr.string
   uniqueStr = uniqueStr>>UniqueStr.next
   ]
let len = string>>String.length rshift 1 +1
uniqueStr = Allocate(zone, len + offset UniqueStr.string/16)
MoveBlock(lv uniqueStr>>UniqueStr.string, string, len)
uniqueStr>>UniqueStr.next = uniqueStringList
uniqueStringList = uniqueStr
resultis lv uniqueStr>>UniqueStr.string
]

// ---------------------------------------------------------------------------
and ReadToken(file, string) = valof
// ---------------------------------------------------------------------------
[
let length = 0
while char eq $*s % char eq $*t do char = Gets(file)
if char eq $*n then Malformed()

   [
   length = length+1
   string>>String.char↑length = char
   char = Gets(file)
   ] repeatuntil char eq $*s % char eq $*t % char eq $*n % char eq $;

string>>String.length = length
while char eq $*s % char eq $*t do char = Gets(file)
if char eq $/ then until char eq $*n do char = Gets(file)
resultis length
]

// ---------------------------------------------------------------------------
and StringEq(str1, str2) = valof
// ---------------------------------------------------------------------------
[
if str1>>String.length ne str2>>String.length resultis false
for i = 1 to str1>>String.length do
   [
   let c1 = str1>>String.char↑i
   if c1 ge $a & c1 le $z then c1 = c1-($a-$A)
   let c2 = str2>>String.char↑i
   if c2 ge $a & c2 le $z then c2 = c2-($a-$A)
   if c1 ne c2 resultis false
   ]
resultis true
]

// ---------------------------------------------------------------------------
and Malformed(message; numargs na) be
// ---------------------------------------------------------------------------
[
if na eq 0 then message = "[ReadDLSConfig] Configuration file malformed"
let text = vec 127
SetFilePos(file, 0, startPos)
for i = 1 to 255 do
   [
   text>>String.char↑i = Gets(file)
   text>>String.length = i
   if text>>String.char↑i eq $*n break
   ]
CallSwat(message, text)
]