/**
***  IPDial     Script program for initializing a SLIP connection
***  Copyright  (C)   1994    Jochen Wiedmann
***
***  This program is free software; you can redistribute it and/or modify
***  it under the terms of the GNU General Public License as published by
***  the Free Software Foundation; either version 2 of the License, or
***  (at your option) any later version.
***
***  This program 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 General Public License for more details.
***
***  You should have received a copy of the GNU General Public License
***  along with this program; if not, write to the Free Software
***  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***
***
***  This is the main part of the program.
***
***
***  Computer: Amiga 1200                       Compiler: Dice 3.01
***
***  Author:    Jochen Wiedmann
***             Am Eisteich 9
***             72555 Metzingen
***             Germany
***
***             Phone: (+0049) 7123 / 14881
***             Internet: jochen.wiedmann@zdv.uni-tuebingen.de
***
***
***  History:   V 1.1   23.11.94        Initial version
***
***             V 1.2   27.02.95        Added terminal mode
***                                     Now using ReadArgs() for
***                                     command line parsing.
***
***             V 1.3   09.03.95        Added environment variable
***                                     aupport to "send" command.
***                                     Added unit support.
***
***             V 1.4   21.04.95        Added "system" command.
***                                     Added environment variable
***                                     support to "echo" command.
***                                     Terminal mode now converts
***                                     LF to CR/LF, so that modem
***                                     recognizes commands.
***                                     (Let's hope, that will still
***                                     work for entering passwords. :-(
***
***             V 1.5   30.04.95        Added "scan" command.
***
***             V 1.6   26.06.95        "delay" command supporting
***                                     ticks; ParseString()
***                                     supporting octal characters;
***                                     "wait" command using
***                                     ParseString()
***                                     (All Suggested by Will Bow.)
***
***                                     Fixed bug in SerialSend():
***                                     *.io_Device = *.io_Unit
**/





#define VERSION     1
#define REVISION    6
#define VSTRING     "IPDial 1.6 (26.06.95)"
#define VERSTAG     "\0$VER: IPDial 1.6 (26.06.95)"






/**
***  Include files
**/
#ifndef IPDIAL_H
#include "IPDial.h"
#endif

#include <ctype.h>
#include <clib/alib_protos.h>





/**
***  This structure describes one command. All commands are stored
***  in a table at the end of the file.
**/
struct ScriptLine;
typedef VOID (*CommandFunc) (struct ScriptLine *);
struct Command
{ CommandFunc Func;
  STRPTR Name;
};





/**
***  Each line of the script file is stored in a structure like below.
**/
struct ScriptLine
{ struct MinNode mn;
  ULONG Num;
  CommandFunc CommFunc;
  STRPTR Label;
  STRPTR Args;
};





/**
***  Global variables
**/
LONG StatusVar;

struct MinList ScriptLineList;

struct ScriptLine *CurrentScriptLine;

const UBYTE VersTag [] = VERSTAG;
const UBYTE VString [] = VSTRING;

ULONG EchoMode = FALSE;
ULONG VerboseMode = FALSE;

STRPTR SerialDeviceName = NULL;

struct RDArgs *MainRDArgs = NULL;





/**
***  This function is used to skip blanks.
**/
STRPTR SkipBlanks(const UBYTE *ptr)

{ while(*ptr == ' '  ||  *ptr == '\t')
  { ++ptr;
  }
  return((STRPTR) ptr);
}





/**
***  This function is used to parse a string for characters
***  like '\r' or '\n'. $VAR or ${VAR} may be used to insert
***  the value of environment variable VAR, $$ may be used to
***  insert the '$' character itself, likewise \\.
***
***  Returns a pointer to a string allocated with malloc().
***  It is the task of the caller, to free() this string.
**/
STRPTR ParseString(const UBYTE *ptr)

{ STRPTR result;
  STATIC APTR parseBuffer = NULL;

  /**
  ***  Be sure, that buffer is valid.
  **/
  if (!parseBuffer  &&  !(parseBuffer = BufferCreate()))
  { perror("malloc");
    exit(10);
  }

  /**
  ***  Clear the buffer.
  **/
  BufferClear(parseBuffer);

  while(*ptr)
  { if (*ptr == '\\')
    { ++ptr;
      switch(*ptr)
      { case 'r':
	  BufferExtend(parseBuffer, (STRPTR) "\r", 1);
	  break;
	case 'n':
	  BufferExtend(parseBuffer, (STRPTR) "\n", 1);
	  break;
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	  { char c;

	    c = *ptr - '0';
	    if (ptr[1] >= '0'  &&  ptr[1] <= '7')
	    { c = (c << 3) + *(++ptr) - '0';
	      if (ptr[1] >= '0'  &&  ptr[2] <= '7')
	      { c = (c << 3) + *(++ptr) - '0';
	      }
	    }
	    BufferExtend(parseBuffer, (STRPTR) &c, 1);
	  }
	  break;
	default:
	  BufferExtend(parseBuffer, ptr, 1);
	  break;
      }
      ++ptr;
    }
    else if (*ptr == '$')
    { ++ptr;
      if (*ptr == '$')
      { BufferExtend(parseBuffer, (STRPTR) "$", 1);
	++ptr;
      }
      else
      { char *varName;
	const char* varPtr;
	size_t varLen = 0;

	if (*ptr == '{')
	{ ++ptr;
	  varPtr = (const char*) ptr;
	  while (*ptr && *ptr != '}')
	  { ++ptr;
	    ++varLen;
	  }
	 if (*ptr == '}')
	  { ++ptr;
	  }
	}
	else
	{ varPtr = (const char*) ptr;
	  while (*ptr  &&  isalnum(*ptr))
	  { ++ptr;
	    ++varLen;
	  }
	}

	if (!(varName = malloc(varLen + 1)))
	{ perror("malloc");
	  exit(10);
	}
	strncpy(varName, varPtr, varLen);
	varName[varLen] = '\0';

	if ((varPtr = getenv(varName)))
	{ BufferExtend(parseBuffer, (STRPTR) varPtr, strlen(varPtr));
	}
      }
    }
    else
    { BufferExtend(parseBuffer, ptr, 1);
      ++ptr;
    }
  }

  BufferExtend(parseBuffer, (STRPTR) "", 1);

  if (!(result = (STRPTR) strdup((char*) BufferBuffer(parseBuffer))))
  { perror("malloc");
    exit(10);
  }

  return(result);
}





/**
***  This is an empty function. Just to allow lines with labels only.
**/
VOID NoneFunc(struct ScriptLine *line)

{
}





/**
***  This is the echo function.
**/
VOID EchoFunc(struct ScriptLine *line)

{ STRPTR *args;
  int i;

  if (!StrReadArgs(line->Args, (LONG *) &args, (STRPTR) "ARGS/M"))
  { fprintf(stderr, "Line %ld: Argument or memory error\n", line->Num);
    exit(10);
  }

  for(i = 0;  *args;  ++i, ++args)
  { char *str;

    if (i)
    { putchar(' ');
    }
    str = (char*) ParseString(*args);
    fputs((char *) str, stdout);
    free(str);
  }
}





/**
***  This is the device function.
**/
VOID DeviceFunc(struct ScriptLine *line)

{ struct
  { STRPTR Device;
    STRPTR Protocol;
    LONG *Unit;
  } args;

  if (SerialDeviceName)
  { fprintf(stderr, "Line %ld: Device already open, ignoring.\n", line->Num);
  }

  args.Unit = NULL;
  args.Device = NULL;
  args.Protocol = NULL;

  if (!StrReadArgs(line->Args, (LONG *) &args,
		   (STRPTR) "DEVICE,PROTOCOL,UNIT/K/N"))
  { fprintf(stderr, "Line %ld: Argument or memory error\n", line->Num);
    exit(10);
  }

  if (!args.Device)
  { fprintf(stderr, "Line %ld: Missing device name.\n", line->Num);
    exit(10);
  }
  if (!(SerialDeviceName = (STRPTR) strdup((char *) args.Device)))
  { perror("malloc");
    exit(10);
  }

  if (VerboseMode)
  { printf("Opening %s.\n", args.Device);
  }

  if (!SerialOpen(args.Device, args.Protocol, args.Unit ? *args.Unit : 0))
  { fprintf(stderr, "Line %ld: Unknown protocol.\n", line->Num);
    exit(10);
  }
}





/**
***  This function is used to set the serial.device parameters.
**/
VOID SetFunc(struct ScriptLine *line)

{ struct
  { ULONG *Baud;
    ULONG *DataBits;
    ULONG *StopBits;
    ULONG *BufSize;
    STRPTR Parity;
    STRPTR Protocol;
  } args;

  if (!SerialDeviceName)
  { fprintf(stderr, "Line %ld: Must open serial.device first.\n", line->Num);
    exit(10);
  }

  args.Baud = NULL;
  args.DataBits = NULL;
  args.StopBits = NULL;
  args.BufSize = NULL;
  args.Parity = NULL;
  args.Protocol = NULL;

  if (!(StrReadArgs(line->Args, (LONG *) &args,
		    (STRPTR) "BAUD/K/N,DATABITS/K/N,STOPBITS/K/N,BUFSIZE/K/N,"
			     "PARITY/K,PROTOCOL/K")))
  { fprintf(stderr, "Line %ld: Argument or memory error.\n", line->Num);
    exit(10);
  }

  if (args.Baud)
  { SerialSetBaud(*args.Baud);
  }
  if (args.DataBits)
  { SerialSetDataBits(*args.DataBits);
  }
  if (args.StopBits)
  { SerialSetStopBits(*args.StopBits);
  }
  if (args.BufSize)
  { SerialSetBufSize(*args.BufSize);
  }
  if (args.Parity)
  { if (!SerialSetParity(args.Parity))
    { fprintf(stderr, "Line %ld: Unknown parity.\n", line->Num);
      exit(10);
    }
  }
  if (args.Protocol)
  { if (!SerialSetProtocol(args.Protocol))
    { fprintf(stderr, "Line %ld: Unknown protocol.\n", line->Num);
      exit(10);
    }
  }

  if (VerboseMode)
  { printf("%s Parameters modified:\n\n", SerialDeviceName);
    SerialShowParms();
  }
}





/**
***  This function shows the serial.device parameters.
**/
VOID ShowParmsFunc(struct ScriptLine *line)

{ if (!SerialDeviceName)
  { fprintf(stderr, "Line %ld: Must open serial.device first.\n", line->Num);
    exit(10);
  }

  printf("%s Parameters:\n\n", SerialDeviceName);
  SerialShowParms();
}





/**
***  This function sends a string to the serial.device.
**/
VOID SendFunc(struct ScriptLine *line)

{ STRPTR *args;

  if (!SerialDeviceName)
  { fprintf(stderr, "Line %ld: Must open serial.device first.\n", line->Num);
    exit(10);
  }

  if (!StrReadArgs(line->Args, (LONG *) &args, (STRPTR) "ARGS/M"))
  { fprintf(stderr, "Line %ld: Argument or memory error\n", line->Num);
    exit(10);
  }

  if (VerboseMode)
  { printf("\nSending ");
  }

  for(;  *args;  args++)
  { STRPTR send = ParseString(*args);

    printf("%s", send);
    SerialSend(send, strlen((char *) send));
    free(send);
  }

  if (VerboseMode)
  { printf("\n");
  }
}





/**
***  This is similar to "if".
**/
VOID DoGoto(STRPTR Label, int Num)

{ struct ScriptLine *sl;

  for(sl = (struct ScriptLine *) ScriptLineList.mlh_Head;
	    sl->mn.mln_Succ;
	    sl = (struct ScriptLine *) sl->mn.mln_Succ)
  { if (sl->Label  &&  strcmp((char *) sl->Label, (char *) Label) == 0)
    { CurrentScriptLine = (struct ScriptLine *) sl->mn.mln_Pred;
      return;
    }
  }

  fprintf(stderr, "Line %d: Unknown label.\n", Num);
  exit(10);
}
VOID OnFunc(struct ScriptLine *line)

{ STRPTR ptr = line->Args;

  if (strnicmp((char *) ptr, "status", 6) == 0)
  { ptr = SkipBlanks(ptr+6);
    if (strnicmp((char *) ptr, "goto", 4) == 0)
    { STRPTR *labels;
      int i;

      if (!StrReadArgs(ptr+4, (LONG *) &labels, (STRPTR) "LABELS/M"))
      { fprintf(stderr, "Line %ld: Memory error.\n", line->Num);
	exit(10);
      }
      for (i = -1;  *labels;  ++i, ++labels)
      { if (i == StatusVar)
	{ DoGoto(*labels, line->Num);
	  return;
	}
      }
      return;
    }
  }

  fprintf(stderr, "Line %ld: Condition syntax.", line->Num);
  exit(10);
}





/**
***  This function waits for certain strings.
**/
VOID WaitFunc(struct ScriptLine *line)

{ struct
  { STRPTR *WaitArgs;
    ULONG *TimeOut;
  } args;
  STRPTR* parsedArgs;
  LONG TimeOut;
  int i = 0;

  if (!SerialDeviceName)
  { fprintf(stderr, "Line %ld: Must open serial.device first.\n", line->Num);
    exit(10);
  }

  args.WaitArgs = NULL;
  args.TimeOut = NULL;

  if (!(StrReadArgs(line->Args, (LONG *) &args, (STRPTR) "ARGS/M,TIMEOUT/N/K")))
  { fprintf(stderr, "Line %ld: Argument or memory error.\n", line->Num);
    exit(10);
  }

  if (!args.WaitArgs)
  { fprintf(stderr, "Line %ld: Missing argument.\n", line->Num);
    exit(10);
  }
  if (!args.TimeOut)
  { fprintf(stderr, "Line %ld: Missing timeout argument.\n", line->Num);
    exit(10);
  }
  TimeOut = *args.TimeOut;


  while (args.WaitArgs[i])
  { ++i;
  }

  if (!(parsedArgs = malloc(sizeof(STRPTR) * (i+1))))
  { perror("malloc");
    exit(10);
  }

  for (i = 0;  args.WaitArgs[i];  i++)
  { parsedArgs[i] = ParseString(args.WaitArgs[i]);
  }
  parsedArgs[i] = NULL;

  StatusVar = SerialWait(parsedArgs, TimeOut);
}





/**
***  The delay function waits for a certain amount of time.
**/
VOID DelayFunc(struct ScriptLine *line)

{ ULONG tics = (int) (atof((char *) line->Args) * 50.0);

  if (tics == 0)
  { fprintf(stderr, "Line %ld: Missing argument.\n", line->Num);
    exit(10);
  }

  Delay(tics);
}





/**
***  And this is the Exit function.
**/
VOID ExitFunc(struct ScriptLine *line)

{ LONG result = atol((char *) line->Args);

  exit(result);
}





/**
***  The Goto command
**/
VOID GotoFunc(struct ScriptLine *line)

{ STRPTR Label;

  if (!(StrReadArgs(line->Args, (LONG *) &Label, (STRPTR) "LABEL/A")))
  { fprintf(stderr, "Line %ld: Missing argument or memory error.\n",
	    line->Num);
    exit(10);
  }
  DoGoto(Label, line->Num);
}





/**
***  This function enters terminal mode.
**/
VOID TermFunc(struct ScriptLine *line)

{ SerialTerminal();
}





/**
***  Execute external commands.
**/
VOID SystemFunc(struct ScriptLine *line)

{ STRPTR *args;
  int i, result;
  char *cmd;
  STATIC APTR systemBuffer = NULL;

  /**
  ***  Initialize buffer.
  **/
  if (!systemBuffer  &&  !(systemBuffer = BufferCreate()))
  { perror("malloc");
    exit(10);
  }
  BufferClear(systemBuffer);


  if (!StrReadArgs(line->Args, (LONG *) &args, (STRPTR) "ARGS/M"))
  { fprintf(stderr, "Line %ld: Argument or memory error\n", line->Num);
    exit(10);
  }

  for(i = 0;  *args;  ++i, ++args)
  { char *str;

    if (i)
    { BufferExtend(systemBuffer, (STRPTR) " ", 1);
    }
    str = (char*) ParseString(*args);
    BufferExtend(systemBuffer, (STRPTR) str, strlen(str));
    free(str);
  }
  cmd = (char*) BufferBuffer(systemBuffer);

  if (VerboseMode)
  { printf("Executing command: \"%s\"\n", cmd);
  }
  result = system(cmd);
  if (result)
  { if (result > 1  &&  result < 10)  /* Warning */
    { if (VerboseMode)
      { printf("Returned %d.\n", result);
      }
    }
    else
    { printf("Error: Command \"%s\" returned %d.\n", cmd, result);
      exit(result);
    }
  }
}  





/**
***  Scan the buffer received by the last "wait" command.
**/
VOID ScanFunc(struct ScriptLine *line)

{ struct
  { STRPTR format;
    ULONG global;
    ULONG save;
  } args;
  ULONG flags;

  args.format = NULL;
  args.global = FALSE;
  args.save = FALSE;

  if (!(StrReadArgs(line->Args, (LONG *) &args,
		    (STRPTR) "FORMAT/A,GLOBAL/S,SAVE/S")))
  { fprintf(stderr, "Line %ld: Argument or memory error.\n", line->Num);
    exit(10);
  }

  flags = 0;
  if (args.save)
  { args.global = TRUE;
    flags |= GVF_SAVE_VAR;
  }
  if (args.global)
  { flags |= GVF_GLOBAL_ONLY;
  }
  else
  { flags |= GVF_LOCAL_ONLY;
  }

  StatusVar = Vsscanf(SerialWaitBuffer(), args.format, flags);
}




/**
***  This is the command table.
**/
struct Command CommandTab[] =
{ { EchoFunc,       (STRPTR) "echo"         },
  { DeviceFunc,     (STRPTR) "device"       },
  { SetFunc,        (STRPTR) "set"          },
  { ShowParmsFunc,  (STRPTR) "showparms"    },
  { SendFunc,       (STRPTR) "send"         },
  { OnFunc,         (STRPTR) "on"           },
  { WaitFunc,       (STRPTR) "wait"         },
  { DelayFunc,      (STRPTR) "delay"        },
  { ExitFunc,       (STRPTR) "exit"         },
  { GotoFunc,       (STRPTR) "goto"         },
  { TermFunc,       (STRPTR) "terminal"     },
  { SystemFunc,     (STRPTR) "system"       },
  { ScanFunc,       (STRPTR) "scan"         },
  { NULL,           NULL                    }
};





/**
***  This function is used to process the file. Rather easy,
***  isn't it? :-)
**/
VOID ProcessFile(VOID)

{ for(CurrentScriptLine = (struct ScriptLine *) ScriptLineList.mlh_Head;
      CurrentScriptLine->mn.mln_Succ;
      CurrentScriptLine = (struct ScriptLine *) CurrentScriptLine->mn.mln_Succ)
  { (*CurrentScriptLine->CommFunc)(CurrentScriptLine);
  }
}





/**
***  This function reads and parses the file.
**/
VOID ParseFile(STRPTR file)

{ FILE *fp;
  STATIC UBYTE buffer[4096];
  ULONG linenum = 0;
  ULONG success = TRUE;

  if (!(fp = fopen((char *) file, "r")))
  { fprintf(stderr, "Could not open %s for reading.\n", file);
    exit(10);
  }

  NewList((struct List *) &ScriptLineList);

  while(fgets((char *) buffer, sizeof(buffer), fp))
  { ULONG len = strlen((char *) buffer);
    STRPTR line;
    STRPTR Label = NULL;
    CommandFunc CommFunc;

    ++linenum;

    if (buffer[len-1] != '\n')
    { fprintf(stderr, "Line %ld too long.\n", linenum);
      exit(10);
    }

    if (!(line = malloc(len+1)))
    { perror("malloc");
      exit(10);
    }
    strcpy((char*) line, (char*) buffer);
    line = SkipBlanks(line);

    /**
    ***  Check for a label
    **/
    { STRPTR ptr = line;

      if (isalpha(*ptr))
      { do
	{ ++ptr;
	}
	while(isalnum(*ptr));

	if (*ptr == ':')
	{ *ptr = '\0';
	  Label = line;
	  line = SkipBlanks(ptr+1);
	}
      }
    }

    /**
    ***  Check for empty line or comment line
    **/
    if (*line == ';'  ||  *line == '\r'  ||  *line == '\n')
    { CommFunc = NoneFunc;
      if (!Label)
      { continue;
      }
    }

    /**
    ***  If no empty line: Check for command
    **/
    else
    { struct Command *comm;

      for (comm = &CommandTab[0];  comm->Func;  ++comm)
      { ULONG len = strlen((char *) comm->Name);

	if (strnicmp((char *) comm->Name, (char *) line, len) == 0  &&
	    (line[len] == ' '  ||  line[len] == '\t'  ||
	     line[len] == '\r' ||  line[len] == '\n'))
	{ line = SkipBlanks(line+len);
	  CommFunc = comm->Func;
	  break;
	}
      }

      if (!comm->Func)
      { fprintf(stderr, "Line %ld: Unknown command.\n", linenum);
	success = FALSE;
      }
    }

    /**
    ***  Allocate a new scriptline structure.
    **/
    { struct ScriptLine *sl;

      if (!(sl = malloc(sizeof(*sl))))
      { perror("malloc");
	exit(10);
      }

      AddTail((struct List *) &ScriptLineList, (struct Node *) sl);
      sl->Num = linenum;
      sl->CommFunc = CommFunc;
      sl->Args = line;
      sl->Label = Label;
    }
  }

  if (ferror(fp))
  { perror("fgets");
    exit(10);
  }

  if (!success)
  { exit(10);
  }

  fclose(fp);
}





/**
***  This is the Cleanup() function, called, when the program terminates.
**/
VOID Cleanup(VOID)

{ SerialCleanup();
  StrReadArgsFree();
  if (MainRDArgs)
  { FreeArgs(MainRDArgs);
  }
}





/**
***  This function hints about the GPL.
**/
VOID ShowGPL(FILE *fp)

{ fprintf(fp, "This program is governed by the terms and conditions of the\n");
  fprintf(fp, "GNU General Public License. A copy should have come with\n");
  fprintf(fp, "this distribution. (See the file COPYING.) In that license\n");
  fprintf(fp, "it is made clear that you are welcome to redistribute either\n");
  fprintf(fp, "verbatim or modified copies of the program and the documentation\n");
  fprintf(fp, "under certain conditions. Further you are told that this program\n");
  fprintf(fp, "comes with ABSOLUTELY NO WARRANTY!\n");
}





/**
***  This function displays the Usage() message.
**/
VOID Usage(VOID)

{ fprintf(stderr, "Usage: IPDial SCRIPT,DEVICE/K,PROTOCOL/K,TERMINAL/S,ECHO/S,VERBOSE/S,HELP/S\n\n");
  fprintf(stderr, "\tSCRIPT:    Script file to execute.\n");
  fprintf(stderr, "\tDEVICE:    Device to use (default serial.device)\n");
  fprintf(stderr, "\tPROTOCOL:  Protocol to use; XONXOFF, 7WIRE (default) or NONE)\n");
  fprintf(stderr, "\tUNIT:      Unit to use (default 0)\n");
  fprintf(stderr, "\tECHO:      Show modems replies.\n");
  fprintf(stderr, "\tVERBOSE:   Be verbose.\n\n");
  fprintf(stderr, "\tTERMINAL:  Run in terminal mode.\n");
  fprintf(stderr, "\tHELP:      Print this message.\n");
  fprintf(stderr, "\n\n%s  @ 1994 by Jochen Wiedmann\n\n", VString);
  ShowGPL(stderr);
  exit(5);
}





/**
***  This is main().
**/
int main(int argc, char *argv[])

{ struct
  { STRPTR file;
    STRPTR device;
    STRPTR protocol;
    LONG *unit;
    ULONG terminal;
    ULONG help;
    ULONG echo;
    ULONG verbose;
  } args;

  args.file = NULL;
  args.device = (STRPTR) "serial.device";
  args.protocol = (STRPTR) "7wire";
  args.unit = NULL;
  args.terminal = FALSE;
  args.echo = FALSE;
  args.verbose = FALSE;
  args.help = FALSE;

  if (!argc)    /*  No WB handling.     */
  { exit(-1);
  }

  if (atexit(Cleanup))
  { fprintf(stderr, "Memory error.\n");
    exit(20);
  }

  if (!(MainRDArgs = ReadArgs((STRPTR) "SCRIPT,DEVICE/K,PROTOCOL/K,"
			      "UNIT/K/N,TERMINAL/S,"
			      "HELP/S,ECHO/S,VERBOSE/S",
			      (LONG *) &args, NULL)))
  { fprintf(stderr, "Cannot parse command line.\n");
  }

  if (args.help  ||
      (!args.file  &&  !args.terminal))
  { Usage();
  }

  EchoMode = args.echo;
  if (args.verbose)
  { VerboseMode = TRUE;
    printf("%s  @ 1994 by Jochen Wiedmann\n\n", VString);
    ShowGPL(stdout);
    printf("\n\n");
  }

  if (args.terminal)
  { SerialOpen(args.device, args.protocol, args.unit ? *args.unit : 0);
    SerialTerminal();
  }
  else if (args.file)
  { ParseFile(args.file);
    ProcessFile();
  }

  exit(0);
}
