--
--	Inmos Libraries
--	Copyright (C) 1988, 1989, 1990, 1991, 1993  Inmos Limited
--
--	This library is free software; you can redistribute it and/or
--	modify it under the terms of the GNU Lesser General Public
--	License as published by the Free Software Foundation; either
--	version 2 of the License, or (at your option) any later version.
--
--	This library is distributed in the hope that it will be useful,
--	but WITHOUT ANY WARRANTY; without even the implied warranty of
--	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
--	Lesser General Public License for more details.
--
--	You should have received a copy of the GNU Lesser General Public
--	License along with this library; if not, write to the Free Software
--	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
--

--** @module hostio

#COMMENT "Host i/o library, V1.1, 24/05/90"
#COMMENT "(c) Copyright INMOS Limited 1989"
#INCLUDE "hostio.inc"
#USE "splib.lib"

--* Read the server command line and parse it for specified options and
-- associated parameters.
--
-- @param fs from server
-- @param ts to server
-- @param option.strings a list of all possible options.  These options
--   must not be longer than 256 bytes and any letters should be in
--   upper case, even though the options as given by the user on the
--   command line are case insensitive.  To read a parameter that has
--   no preceding option ( such as a filename ) then the first option
--   string should contain only spaces.  Because all of these option
--   strings must be the same length, trailing spaces should be used to
--   pad.
-- @param option.parameters.required indicates if the corresponding
--   option ( in option.strings ) requires a parameter.  The
--   permissible values are: spopt.never     never takes a parameter
--   spopt.maybe     optionally takes a parameter spopt.always    must
--   take a parameter
-- @param option.exists TRUE if the corresponding option was present on
--   the command line; otherwise FALSE
-- @param option.parameters if an option was followed by a parameter
--   then the position in the array line where the parameter starts and
--   the length of the parameter are given by the first and second
--   elements respectively in the corresponding element of this
--   parameter, as long as an error did not occur
-- @param error.len zero if no error occurs whilst the command line is
--   being parsed; otherwise greater than zero and is the number of
--   bytes in line ( which will contain an error message )
-- @param line contains the command line as supplied by the server if
--   there was no error ( in which case error.len is zero ); otherwise
--   contains an error message ( the length of which is given by
--   error.len ).  There is no limitation on the size of line - if it
--   is not long enough for an error message then that message is
--   truncated to fit; if not long enough for the command line, then an
--   error message is put into line.
PROC so.parse.command.line (CHAN OF SP fs, ts,
                            VAL [][]BYTE option.strings,
                            VAL []INT    option.parameters.required,
                            []BOOL       option.exists,
                            [][2]INT     option.parameters,
                            INT error.len, []BYTE line)
  --{{{  constants
  VAL parameter.ptr IS 0 : -- zeroth element of option.parameters [i] is ptr,
  VAL parameter.len IS 1 : -- first is length
  VAL command.line.error.msg IS "Command line error: " :
  VAL max.option.size IS 256 : -- longest word/filename that can be recognised
  --}}}
  --{{{  PROC set.error
  PROC set.error (VAL []BYTE error.msg, extra.msg)
    --{{{  app.text
    PROC app.text (INT len, []BYTE str, VAL []BYTE text)
      -- The text is written into [str FROM len]
      -- with the new length returned in len.
    
      INT this.len :
      SEQ
        IF
          (len + (SIZE text)) > (SIZE str)
            this.len := (SIZE str) - len
          TRUE
            this.len := SIZE text
    
        [str FROM len FOR this.len] := [text FROM 0 FOR this.len]
        len := len + this.len
    :
    --}}}app.text
    SEQ
      app.text (error.len, line, command.line.error.msg)
      app.text (error.len, line, error.msg)
      app.text (error.len, line, extra.msg)
  :
  --}}}PROC set.error
  --{{{  variables
  BYTE esc.char :
  INT  line.length :
  BOOL first.row.is.blank :
  --}}}

  SEQ
    error.len := 0
    --{{{  check for correct array sizes
    IF
      ((SIZE option.strings) = 0)                         OR
      ((SIZE option.strings) <> (SIZE option.exists)    ) OR
      ((SIZE option.strings) <> (SIZE option.parameters)) OR
      ((SIZE option.strings) <> (SIZE option.parameters.required))
        set.error ("called incorrectly", "")
      TRUE
        SKIP
    --}}}check for correct array sizes
    --{{{  initialise the 'output arrays'
    IF
      error.len = 0  -- in case there were any funny array sizes
        SEQ
          option.parameters [0] [parameter.len] := 0
          option.parameters [0] [parameter.ptr] := 0
          option.exists     [0]                 := FALSE
          SEQ i = 1 FOR (SIZE option.parameters) - 1
            SEQ
              option.parameters [i] := option.parameters [0]
              option.exists     [i] := FALSE
          --{{{  test whether the first row is blank
          VAL first.row IS option.strings [0] :
          IF
            IF i = 0 FOR SIZE first.row
              first.row [i] <> ' '
                first.row.is.blank := FALSE
            TRUE
              first.row.is.blank := TRUE
          --}}}test whether the first row is blank
      TRUE
        SKIP
    --}}}initialise the 'output arrays'
    --{{{  read the server version to determine host system command line option
    IF
      error.len = 0
        BYTE os :
        SEQ
          BYTE version, host, board, result :  -- these are all ignored
          sp.version (fs, ts, version, host, os, board, result)
          CASE os
            spo.DOS, spo.VMS
              esc.char := '/'
            ELSE  -- spo.HELIOS, spo.SUNOS, spo.CMS, or any other
              esc.char := '-'
      TRUE
        SKIP
    --}}}read the server version to determine host system command line option
    --{{{  read the command line
    IF
      error.len = 0
        BYTE result :
        SEQ
          sp.commandline (fs, ts, sp.short.commandline, line.length, line, result)
          IF
            result <> spr.ok
              set.error ("cannot read server*'s command line", "")
            TRUE
              SKIP
      TRUE
        SKIP
    --}}}read the command line
    --{{{  parse it
    --{{{  variables
    INT ptr :
    BOOL waiting.for.parameter :  -- if just received an option requiring a param
    INT  last.option :
    BOOL found.filename :  -- whether found one yet
    --}}}
    SEQ
      --{{{  initialise
      waiting.for.parameter := FALSE -- therefore can leave 'last.option' undef
      found.filename        := FALSE
      ptr := 0
      --}}}
      #PRAGMA DEFINED line.length, ptr, esc.char, first.row.is.blank, last.option
      WHILE (error.len = 0) AND (ptr < line.length)
        --{{{  PROC get.next.string  -- use space or esc.char as terminator
        PROC get.next.string (INT len, [max.option.size]BYTE str) 
          INT end :
          SEQ
            IF
              IF i = ptr FOR (line.length - ptr)
                VAL char IS line [i] :
                (char = ' ') OR (char = esc.char)
                  end := i
              TRUE
                end := line.length
            len := end - ptr
            IF
              len > (SIZE str)
                len := (SIZE str)
              TRUE
                SKIP
            [str FROM 0 FOR len] := [line FROM ptr FOR len]
            ptr := end   -- this will point at the following space, or end of line
        :
        --}}}PROC get.next.string  -- use space or esc.char as terminator
        --{{{  string
        [max.option.size]BYTE str :
        INT str.len :
        --}}}
        IF
          --{{{  space - skip it
          line [ptr] = ' '
            ptr := ptr + 1
          --}}}
          --{{{  escape option - check it
          line [ptr] = esc.char
            SEQ
              ptr := ptr + 1 -- skip esc.char
              get.next.string (str.len, str)
              IF
                str.len > 0
                  INT option.number :
                  this.option IS [str FROM 0 FOR str.len] :
                  SEQ
                    option.number := -1
                    --{{{  search for the option
                    INT i :
                    SEQ
                      IF
                        first.row.is.blank
                          i := 1
                        TRUE
                          i := 0
                      WHILE (i < (SIZE option.strings)) AND (option.number < 0)
                        SEQ
                          VAL ith.option IS option.strings [i] :  -- must be upper case
                          INT ith.option.len :
                          SEQ
                            --{{{  calculate how long 'ith.option' is
                            IF
                              IF j = 0 FOR SIZE ith.option
                                ith.option [j] = ' '
                                  ith.option.len := j
                              TRUE
                                ith.option.len := SIZE ith.option
                            --}}}calculate how long 'ith.option' is
                            --{{{  test whether 'this.option' equals 'ith.option'
                            IF
                              ( ith.option.len > (SIZE this.option))     OR
                              ((ith.option.len < (SIZE this.option)) AND   -- don't allow it to 'run-into'
                               (option.parameters.required [i] = spopt.never)) -- next if there's no parameter
                                SKIP  -- not this one
                            
                              --{{{  see if ith.option is NOT a prefix of this.option
                              IF j = 0 FOR ith.option.len
                              
                                --(this.option [j]) <> (ith.option [j])
                              
                                -- No - check it's in upper case (without using FUNCTIONs!!!!)
                                VAL c IS this.option [j] :
                                VAL a.to.A IS (INT 'A') - (INT 'a') :
                              
                                (INT ith.option [j]) <> ((INT c) +
                                                         (a.to.A * (INT ((c >= 'a') AND (c <= 'z')))) )
                                  SKIP -- not this one
                              --}}}see if ith.option is NOT a prefix of this.option
                            
                              TRUE  -- it IS a prefix
                                SEQ
                                  option.number := i
                                  -- move 'back' to point at the 'merged' parameter
                                  ptr := ptr - ((SIZE this.option) - ith.option.len)
                            --}}}test whether 'this.option' equals 'ith.option'
                          i := i + 1
                    --}}}search for the option
                    --{{{  check for duplicate entry, and flag this occurrence
                    IF
                      error.len <> 0
                        SKIP
                      option.number = (-1)
                        set.error ("unknown option: ", this.option)
                      (NOT (option.parameters.required [option.number] = spopt.never)) AND
                       (option.exists [option.number])
                        set.error ("duplicate option: ", this.option)
                      TRUE
                        SEQ
                          option.exists [option.number] := TRUE
                          last.option                   := option.number
                          waiting.for.parameter         :=
                                option.parameters.required [option.number] <> spopt.never
                    --}}}check for duplicate entry, and flag this occurrence
                TRUE
                  set.error ("escape character on its own: ", [esc.char])
          --}}}escape option - check it
          --{{{  otherwise parameter / filename
          TRUE
            INT start.ptr :
            SEQ
              start.ptr := ptr
              get.next.string (str.len, str)
              --{{{  check for filename parameter
              -- If the first row of the option.strings table is not blank,
              -- there must be no filename
              IF
                NOT waiting.for.parameter
                  IF
                    first.row.is.blank AND (NOT found.filename)
                      SEQ
                        last.option       := 0  -- first row of table
                        option.exists [0] := TRUE
                        found.filename    := TRUE
                    TRUE
                      set.error ("no option supplied: ", [str FROM 0 FOR str.len])
                TRUE
                  SKIP
              --}}}check for filename parameter
              --{{{  flag this one
              IF
                error.len = 0
                  SEQ
                    option.parameters [last.option] [parameter.len] := str.len
                    option.parameters [last.option] [parameter.ptr] := start.ptr
                TRUE
                  SKIP
              --}}}flag this one
              waiting.for.parameter := FALSE
          --}}}otherwise parameter / filename
      --{{{  check that the filename exists
      IF
        (error.len = 0) AND first.row.is.blank AND (NOT found.filename) AND
        (option.parameters.required [0] = spopt.always)
          -- i.e. filename is DEFINITELY REQUIRED
          set.error ("no filename supplied", "")
        TRUE
          SKIP
      --}}}check that the filename exists
    --}}}parse it
    --{{{  check that all 'required' parameters were supplied
    IF
      error.len <> 0
        SKIP
      IF i = 0 FOR SIZE option.parameters.required
        (option.exists [i]                            ) AND
        (option.parameters.required [i] = spopt.always) AND
        (option.parameters [i] [parameter.len] = 0    )
          -- a parameter is missing
          set.error ("this option must be followed by a parameter: ",
                      option.strings [i])
      TRUE
        SKIP
    --}}}check that all 'required' parameters were supplied
    -- if anything caused an error, it has been put into
    -- line, and error.len will be non-zero
:
