home *** CD-ROM | disk | FTP | other *** search
/ GEMini Atari / GEMini_Atari_CD-ROM_Walnut_Creek_December_1993.iso / files / cli / cli_mod / cli.mod
Encoding:
Text File  |  1993-10-02  |  32.6 KB  |  1,322 lines

  1. (*
  2.     A very simple command line interface for the Atari 520ST TOS.
  3.  
  4.     Syntax is a VERY small subset of the Bourne shell supplied with
  5.     the Unix operating system.  Specifically:
  6.  
  7.     Each command is a seperate line.
  8.     Each command consists of several words separated by white space.
  9.     The first word is the command name.
  10.     If a word matches "name=value" for some name and value, it is
  11.         variable assignment and not considered part of the command
  12.         line.
  13.     A dollar sign followed by a variable name is replaced by the
  14.         value of the variable.
  15.     Single quotes protect characters from all interpretation.
  16.     Double quotes protect characters from all interpretation EXCEPT
  17.         for variable replacement.
  18.     '*' and '?' wild card characters are supported, but only in the
  19.         "leaf" part of the filename.
  20.     Input and output redirection is supported to and from disk files.
  21.     The only variables with special meaning are:
  22.         PATH    command search path
  23.         SUFFIXES    supported command types
  24.       The elements in the PATH and SUFFIXES list are separated
  25.       by commas.
  26.     The only built-in commands are:
  27.         .        temporarily read commands from file
  28.         cd        change directory
  29.         echo    print arguments on screen
  30.         meminfo    print some memory usage info; mainly for debugging
  31.         pwd        print name of current directory
  32.         set        list defined variables
  33.         -v     print lines as they are read
  34.         -x    print lines as they are executed
  35.         version    print CLI and GEMDOS versions
  36.     Control-D exits from the program.
  37.  
  38.     Dave Clemans, 2/86
  39. *)
  40. MODULE CLI;
  41.  
  42. (* Define our alphabet *)
  43. IMPORT ASCII;
  44.  
  45. (* Get a string package *)
  46. IMPORT String;
  47.  
  48. (* Define our storage interface *)
  49. IMPORT Storage;
  50.  
  51. (* Get a filesystem interface *)
  52. FROM Streams IMPORT Stream,StreamKinds,OpenStream,CloseStream,Read8Bit,EOS;
  53.  
  54. (* Get some conversion stuff *)
  55. FROM M2Conversions IMPORT ConvertInteger,ConvertCardinal,ConvertAddrHex,
  56.     ConvertAddrDec;
  57.  
  58. (* Get our screen/keyboard interface *)
  59. FROM Terminal IMPORT Read,Write,WriteLn,WriteString;
  60.  
  61. (* Get some hardware dependent stuff *)
  62. FROM SYSTEM IMPORT ADDRESS, ADR, TSIZE, REGISTER, SETREG, CODE;
  63.  
  64. (* Get the operating system stuff we need *)
  65. IMPORT GEMVDIbase, VDIControls, VDIEscapes,
  66.        GEMAESbase, AESGraphics, AESApplications, AESEvents, AESMenus,
  67.        AESForms;
  68. IMPORT GEMDOS;
  69.  
  70. CONST
  71.     (* Our current version... *)
  72.     CLIVersion = "CLI Version 1.0; dgc; 3/8/86";
  73.     GEMDOSVersion = "GEMDOS Version ";
  74.  
  75.     (* Set non-zero if this build is for a desk accessory *)
  76.     (* If a desk accessory, AESApplications, AESEvents, AESMenus *)
  77.     (* must be IMPORTed. *)
  78.     DeskAccessory = 1;
  79.     (* Currently the run time startup code must be patched for *)
  80.     (* a desk accessory written in Modula-2 to work, since desk *)
  81.     (* are called by the system without having any stack whatsoever *)
  82.     (* set up.  Basically, you just no-op out everything except the *)
  83.     (* initialization of the base page address and insert in a stack *)
  84.     (* pointer initialization to some area of memory that should *)
  85.     (* hopefully be free.  I use 5FFF8, so the patch is: *)
  86.     (*        2E 7C 00 05 FF F8    *)
  87.  
  88.     MaxStack = 7168;        (* Reserve this much space for a stack *)
  89.     MaxHeap = 4096;        (* This much space for a heap area *)
  90.  
  91.     MaxLine = 256;        (* Maximum size of a command line *)
  92.  
  93.     VarStart = '$';        (* Start of a variable reference *)
  94.     VarDefine = '=';        (* Signifies a variable definition *)
  95.     Space = ' ';        (* A white space character *)
  96.     Tab = ASCII.HT;        (* Another white space character *)
  97.     SQuote = "'";        (* Fully protected quoted string *)
  98.     DQuote = '"';        (* Partially protected quoted string *)
  99.     LBracket = '{';        (* Start of bracketed variable reference *)
  100.     RBracket = '}';        (* End of bracketed variable reference *)
  101.     DriveSep = ':';        (* Separator between drive and pathname *)
  102.     PathSep = '\';        (* Separator between parts of pathname *)
  103.     PartSep = ',';        (* Separator between parts of PATH,SUFFIXES *)
  104.     SuffixSep = '.';        (* Separator between file name and suffix *)
  105.  
  106.     Prompt = "$ ";        (* Constant prompts for now *)
  107.  
  108.     (* Error messages *)
  109.     UnClosedString = "Missing single or double quote in command line.";
  110.     LineTooLong = "Input line is too long; maximum length is 256 characters.";
  111.     DirNotFound = "Directory not found.";
  112.     CmdArgsTooLong = "Command arguments are too long; maximum is 127 chars.";
  113.     FileNotFound = "File not found.";
  114.     FileNotExecuted = "File could not be executed.";
  115.     FlagNotFound = "Flag to command not known.";
  116.     NotEnoughMemory = "Not enough memory available to run command.";
  117.     NoMatch = "No match.";
  118.     VariableNotTerminated = "Shell variable not terminated.";
  119.  
  120.     (* Built-in commands *)
  121.     ChangeDirectory     = "cd";
  122.     EchoCommand     = "echo";
  123.     PrintDirectory     = "pwd";
  124.     SetCommand         = "set";
  125.     SourceCommand     = ".";
  126.     VersionCommand     = "version";
  127.  
  128.     (* Pre-Defined shell variables *)
  129.     PathVarName     = "PATH";
  130.     PathDefault     = ",a:,a:\bin\";
  131.     SuffixesVarName     = "SUFFIXES";
  132.     SuffixesDefault     = ".ttp,.tos,.prg,.app";
  133.  
  134. TYPE
  135.     CharSet = SET OF CHAR;
  136.     Line = ARRAY[0..MaxLine-1] OF CHAR;
  137.     VarPointer = POINTER TO Variable;
  138.     CharPointer = POINTER TO Line;
  139.     Variable = RECORD        (* Variable definition record *)
  140.     name: CharPointer;    (* ... The variable name *)
  141.     value: CharPointer;    (* ... The value of the variable *)
  142.     link: VarPointer;    (* ... Next variable record *)
  143.     END;
  144.     PagePointer = POINTER TO ARRAY[0..1023] OF ADDRESS;
  145.  
  146. VAR
  147.     LineBuffer, CommandName, CommandLine: Line;
  148.     VarTop, VarBottom: VarPointer;
  149.     FirstIndex, LastIndex, Counter: CARDINAL;
  150.     Result: INTEGER;
  151.     KeepGoing: BOOLEAN;
  152.     ReadFromFile: BOOLEAN;
  153.     PrintLinesRead: BOOLEAN;
  154.     PrintLinesExecuted: BOOLEAN;
  155.     SaveCh: CHAR;
  156.     PathPointer: CharPointer;
  157.     SuffixesPointer: CharPointer;
  158.     FileStream: Stream;
  159.     oldDTAaddr: ADDRESS;
  160.     VDIHandle: INTEGER;
  161.     WorkIn: GEMVDIbase.VDIWorkInType;
  162.     WorkOut: GEMVDIbase.VDIWorkOutType;
  163.     WidthChar, HeightChar, WidthFont, HeightFont: INTEGER;
  164.     ScreenWidth, ScreenHeight: INTEGER;
  165.     ApplId, MenuId, EventId: INTEGER;
  166.     MsgBuf: ARRAY[0..7] OF INTEGER;
  167.     Dummy: INTEGER;
  168.     BlockAddr: ADDRESS;
  169.     StackPage, OldStackPage: PagePointer;
  170.  
  171. (* Print a standard format error message *)
  172. PROCEDURE ErrorMessage(VAR msg: ARRAY OF CHAR; rc: INTEGER);
  173. VAR
  174.     buffer: Line;
  175. BEGIN
  176.     WriteString("CLI: ");
  177.     WriteString(msg);
  178.     IF (rc < 0)
  179.     THEN
  180.     WriteString(" Status=");
  181.     ConvertInteger(rc,1,buffer);
  182.     WriteString(buffer);
  183.     END;
  184.     WriteLn;
  185. END ErrorMessage;
  186.  
  187. (* Read an input line from the keyboard *)
  188. PROCEDURE ReadLine;
  189. VAR
  190.     ch: CHAR;
  191.     index, position, index1: CARDINAL;
  192.  
  193. PROCEDURE BackupCursor;
  194. BEGIN
  195.     Write(ASCII.BS);
  196.     Write(Space);
  197.     Write(ASCII.BS);
  198.     position := position-1;
  199. END BackupCursor;
  200.  
  201. PROCEDURE BackupOverTab;
  202. BEGIN
  203.     (* NOTE: first "tab" character already backed up over *)
  204.     WHILE ((position MOD 8) # 0)
  205.     DO
  206.     BackupCursor;
  207.     END;
  208. END BackupOverTab;
  209.  
  210. PROCEDURE EchoCharacter(c: CHAR);
  211. BEGIN
  212.     Write(c);
  213.     position := position+1;
  214. END EchoCharacter;
  215.  
  216. PROCEDURE EchoTab;
  217. BEGIN
  218.     (* NOTE: first "tab" character already written *)
  219.     WHILE ((position MOD 8) # 0)
  220.     DO
  221.     EchoCharacter(Space);
  222.     END;
  223. END EchoTab;
  224.  
  225. BEGIN        (* ReadLine *)
  226.     index := 0;
  227.     position := 0;
  228.     LOOP
  229.     Read(ch);
  230.     IF (ASCII.CharIsPrintable(ch) OR (ch = Tab) OR (ch = ASCII.EOT))
  231.     THEN
  232.         IF (ASCII.CharIsPrintable(ch))
  233.         THEN
  234.         EchoCharacter(ch);
  235.         ELSIF (ch = Tab)
  236.         THEN
  237.         EchoCharacter(Space);
  238.         EchoTab;
  239.         END;
  240.         LineBuffer[index] := ch;
  241.         index := index+1;
  242.     END;
  243.     IF ((ch = ASCII.CR) OR (ch = ASCII.EOT))
  244.     THEN
  245.         WriteLn;
  246.         EXIT;
  247.     END;
  248.     IF (ch = ASCII.BS)
  249.     THEN
  250.         index := index-1;
  251.         BackupCursor;
  252.         IF (LineBuffer[index] = Tab)
  253.         THEN
  254.         BackupOverTab;
  255.         END;
  256.     END;
  257.     IF (ch = ASCII.NAK)
  258.     THEN
  259.         FOR index1 := index-1 TO 0 BY -1
  260.         DO
  261.         BackupCursor;
  262.         IF (LineBuffer[index1] = Tab)
  263.         THEN
  264.             BackupOverTab;
  265.         END;
  266.         END;
  267.         index := 0;
  268.         position := 0;
  269.     END;
  270.     IF (index >= TSIZE(Line)-1)
  271.     THEN
  272.         ErrorMessage(LineTooLong,0);
  273.         EXIT;
  274.     END;
  275.     END;
  276.     LineBuffer[index] := ASCII.NUL;
  277. END ReadLine;
  278.  
  279. (* List all the defined shell variables *)
  280. PROCEDURE ListVariables;
  281. VAR
  282.     ptr: POINTER TO Variable;
  283.     index: CARDINAL;
  284. BEGIN
  285.     ptr := VarTop;
  286.     WHILE (ptr # NIL)
  287.     DO
  288.     index := 0;
  289.     WHILE (ptr^.name^[index] # ASCII.NUL)
  290.     DO
  291.         Write(ptr^.name^[index]);
  292.         index := index+1;
  293.     END;
  294.     Write('=');
  295.     index := 0;
  296.     WHILE (ptr^.value^[index] # ASCII.NUL)
  297.     DO
  298.         Write(ptr^.value^[index]);
  299.         index := index+1;
  300.     END;
  301.     WriteLn;
  302.     ptr := ptr^.link;
  303.     END;
  304. END ListVariables;
  305.  
  306. (* Change the value of a shell variable that has special meaning *)
  307. PROCEDURE ChangeDefaultVariable(varPtr: VarPointer);
  308. VAR
  309.     tempName,varName: Line;
  310. BEGIN
  311.     tempName := PathVarName;
  312.     varName := varPtr^.name^;
  313.     IF (String.Compare(tempName,varName) = String.Equal)
  314.     THEN
  315.     PathPointer := varPtr^.value;
  316.     RETURN;
  317.     END;
  318.     tempName := SuffixesVarName;
  319.     IF (String.Compare(tempName,varName) = String.Equal)
  320.     THEN
  321.     SuffixesPointer := varPtr^.value;
  322.     END;
  323. END ChangeDefaultVariable;
  324.  
  325. (* Define a shell variable *)
  326. PROCEDURE DefineVariable(VAR name: ARRAY OF CHAR; VAR value: ARRAY OF CHAR);
  327. VAR
  328.     nameLen,valueLen: CARDINAL;
  329.     varPtr: VarPointer;
  330. BEGIN
  331.     nameLen := String.Length(name);
  332.     valueLen := String.Length(value);
  333.     varPtr := VarTop;
  334.     WHILE (varPtr # NIL)
  335.     DO
  336.     IF (String.Compare(name,varPtr^.name^) = String.Equal)
  337.     THEN
  338.         Storage.DEALLOCATE(varPtr^.value,String.Length(varPtr^.value^)+1);
  339.         Storage.ALLOCATE(varPtr^.value,valueLen+1);
  340.         String.Assign(varPtr^.value^,value);
  341.         ChangeDefaultVariable(varPtr);
  342.         RETURN;
  343.     END;
  344.     varPtr := varPtr^.link;
  345.     END;
  346.     Storage.ALLOCATE(varPtr,TSIZE(Variable));
  347.     Storage.ALLOCATE(varPtr^.name,nameLen+1);
  348.     Storage.ALLOCATE(varPtr^.value,valueLen+1);
  349.     varPtr^.link := NIL;
  350.     String.Assign(varPtr^.name^,name);
  351.     String.Assign(varPtr^.value^,value);
  352.     IF (VarTop = NIL)
  353.     THEN
  354.     VarTop := varPtr;
  355.     VarBottom := varPtr;
  356.     ELSE
  357.     VarBottom^.link := varPtr;
  358.     VarBottom := varPtr;
  359.     END;
  360.     ChangeDefaultVariable(varPtr);
  361. END DefineVariable;
  362.  
  363. (* Expand a shell variable *)
  364. PROCEDURE ExpandVariable;
  365. VAR
  366.     index,length,offset: CARDINAL;
  367.     varName, varValue: Line;
  368.     varPtr: VarPointer;
  369. BEGIN
  370.     length := LastIndex+1;
  371.     offset := 0;
  372.     IF (LineBuffer[length] = LBracket)
  373.     THEN
  374.     WHILE ((length < TSIZE(Line)) AND (LineBuffer[length] # RBracket))
  375.     DO
  376.         length := length+1;
  377.     END;
  378.     offset := 1;
  379.     ELSE
  380.     WHILE ((length < TSIZE(Line)) AND
  381.         (LineBuffer[length] IN CharSet{'a'..'z', 'A'..'Z', '_', '0'..'9'}))
  382.     DO
  383.         length := length+1;
  384.     END;
  385.     END;
  386.     IF (length = TSIZE(Line))
  387.     THEN
  388.     ErrorMessage(VariableNotTerminated,0);
  389.     RETURN;
  390.     END;
  391.     String.Copy(LineBuffer,LastIndex+1+offset,length-LastIndex-1-offset,varName);
  392.     String.Delete(LineBuffer,LastIndex,length-LastIndex+offset);
  393.     length := String.Length(LineBuffer);
  394.     varPtr := VarTop;
  395.     WHILE (varPtr # NIL)
  396.     DO
  397.     IF (String.Compare(varName,varPtr^.name^) = String.Equal)
  398.     THEN
  399.         index := String.Length(varPtr^.value^);
  400.         IF ((length+index) >= TSIZE(Line))
  401.         THEN
  402.         ErrorMessage(LineTooLong,0);
  403.         index := TSIZE(Line)-length-1;
  404.         END;
  405.         varValue := varPtr^.value^;
  406.         varValue[index] := ASCII.NUL;
  407.         IF (LineBuffer[LastIndex] = ASCII.NUL)
  408.         THEN
  409.         String.Concat(LineBuffer,varValue,varName);
  410.         String.Assign(LineBuffer,varName);
  411.         ELSE
  412.         String.Insert(varValue,LineBuffer,LastIndex);
  413.         END;
  414.         LastIndex := LastIndex-1;
  415.         RETURN;
  416.     END;
  417.     varPtr := varPtr^.link;
  418.     END;
  419.     LastIndex := LastIndex-1;
  420. END ExpandVariable;
  421.  
  422. (* Parse a word of a command *)
  423. PROCEDURE CommandWord;
  424. VAR
  425.     index, counter, varSave: CARDINAL;
  426.     wildCard: BOOLEAN;
  427.     newWord: Line;
  428.     varName, varValue: Line;
  429.  
  430. (* Get the possible expansions for a filename with wildcard characters *)
  431. PROCEDURE expandWildcard;
  432. VAR
  433.     index: CARDINAL;
  434.     result: INTEGER;
  435.     DTA: ARRAY[0..43] OF CHAR;
  436.     savePath: Line;
  437.     drive, newdrive: CARDINAL;
  438.     drivemap: LONGCARD;
  439.     ch: CHAR;
  440.  
  441. (* Stick an expanded wildcard filename back into the command line *)
  442. PROCEDURE gotFile(): BOOLEAN;
  443. VAR
  444.     counter, availLength, fileLength: CARDINAL;
  445.     tempLine, tempFile: Line;
  446. BEGIN
  447.     index := String.Length(CommandLine);
  448.     availLength := TSIZE(Line)-index-1;
  449.     fileLength := 0;
  450.     FOR counter := 0 TO 13
  451.     DO
  452.     IF ((DTA[30+counter] # Space) AND
  453.         ASCII.CharIsPrintable(DTA[30+counter]))
  454.     THEN
  455.         tempLine[fileLength] := DTA[30+counter];
  456.         fileLength := fileLength+1;
  457.     END;
  458.     END;
  459.     tempLine[fileLength] := ASCII.NUL;
  460.     IF (tempLine[0] = '.')
  461.     THEN
  462.     RETURN TRUE;
  463.     END;
  464.     fileLength := fileLength+1;
  465.     IF (savePath[0] # ASCII.NUL)
  466.     THEN
  467.     fileLength := fileLength+String.Length(savePath);
  468.     END;
  469.     IF (fileLength < availLength)
  470.     THEN
  471.     CommandLine[index] := Space;
  472.     CommandLine[index+1] := ASCII.NUL;
  473.     IF (savePath[0] = ASCII.NUL)
  474.     THEN
  475.         String.Assign(tempFile,tempLine);
  476.     ELSE
  477.         String.Concat(savePath,tempLine,tempFile);
  478.     END;
  479.     String.Concat(CommandLine,tempFile,tempLine);
  480.     String.Assign(CommandLine,tempLine);
  481.     RETURN TRUE;
  482.     ELSE
  483.     ErrorMessage(LineTooLong,0);
  484.     RETURN FALSE;
  485.     END;
  486. END gotFile;
  487.  
  488. (* expandWildcard *)
  489. BEGIN
  490.     GEMDOS.GetDrv(drive);
  491.     IF (newWord[1] = DriveSep)
  492.     THEN
  493.     ch := CAP(newWord[0]);
  494.     IF ((ch < 'A') OR (ch > 'P'))
  495.     THEN
  496.         ErrorMessage(DirNotFound,0);
  497.         RETURN;
  498.     END;
  499.     newdrive := ORD(ch) - ORD('A');
  500.     GEMDOS.SetDrv(newdrive,drivemap);
  501.     END;
  502.     GEMDOS.SetDTA(ADR(DTA));
  503.     index := String.Length(newWord);
  504.     WHILE ((index > 0) AND (newWord[index] # PathSep) AND
  505.     (newWord[index] # DriveSep))
  506.     DO
  507.     index := index-1;
  508.     END;
  509.     IF (index > 0)
  510.     THEN
  511.     String.Copy(newWord,0,index+1,savePath);
  512.     ELSE
  513.     savePath[0] := ASCII.NUL;
  514.     END;
  515.     FOR index := 0 TO 43
  516.     DO
  517.     DTA[index] := ASCII.NUL;
  518.     END;
  519.     GEMDOS.SFirst(newWord,22,result);
  520.     IF (result >= 0)
  521.     THEN
  522.     IF (gotFile())
  523.     THEN
  524.         LOOP
  525.         FOR index := 30 TO 43
  526.         DO
  527.             DTA[index] := ASCII.NUL;
  528.         END;
  529.         GEMDOS.SNext(result);
  530.         IF (result < 0)
  531.         THEN
  532.             EXIT;
  533.         END;
  534.         IF (gotFile() = FALSE)
  535.         THEN
  536.             EXIT;
  537.         END;
  538.         END;
  539.     ELSE
  540.         ErrorMessage(NoMatch,result);
  541.     END;
  542.     END;
  543.     GEMDOS.SetDrv(drive,drivemap);
  544. END expandWildcard;
  545.  
  546. (* CommandWord *)
  547. BEGIN
  548.     String.Copy(LineBuffer,FirstIndex,LastIndex-FirstIndex,newWord);
  549.     counter := 0;
  550.     varSave := 0;
  551.     wildCard := FALSE;
  552.     WHILE (newWord[counter] # ASCII.NUL)
  553.     DO
  554.     CASE newWord[counter]
  555.     OF
  556.         SQuote:
  557.         String.Delete(newWord,counter,1);
  558.         WHILE ((newWord[counter] # SQuote) AND
  559.             (newWord[counter] # ASCII.NUL))
  560.         DO
  561.             counter := counter+1;
  562.         END;
  563.         IF (newWord[counter] = SQuote)
  564.         THEN
  565.             String.Delete(newWord,counter,1);
  566.         END;
  567.         |
  568.         DQuote:
  569.         String.Delete(newWord,counter,1);
  570.         WHILE ((newWord[counter] # DQuote) AND
  571.             (newWord[counter] # ASCII.NUL))
  572.         DO
  573.             counter := counter+1;
  574.         END;
  575.         IF (newWord[counter] = DQuote)
  576.         THEN
  577.             String.Delete(newWord,counter,1);
  578.         END;
  579.         |
  580.         VarDefine:
  581.         varSave := counter;
  582.         |
  583.         '*', '?':
  584.         wildCard := TRUE;
  585.         |
  586.         ELSE
  587.     END;
  588.     IF (newWord[counter] # ASCII.NUL)
  589.     THEN
  590.         counter := counter+1;
  591.     END;
  592.     END;
  593.     IF (wildCard)
  594.     THEN
  595.     expandWildcard;
  596.     ELSIF (varSave # 0)
  597.     THEN
  598.     String.Copy(newWord,0,varSave,varName);
  599.     String.Copy(newWord,varSave+1,String.Length(newWord)-varSave-1,varValue);
  600.     DefineVariable(varName,varValue);
  601.     ELSE
  602.     IF (CommandName[0] = ASCII.NUL)
  603.     THEN
  604.         CommandName := newWord;
  605.     ELSE
  606.         counter := String.Length(CommandLine);
  607.         CommandLine[counter] := Space;
  608.         counter := counter+1;
  609.         index := 0;
  610.         LOOP
  611.         IF (counter >= TSIZE(Line)-1)
  612.         THEN
  613.             ErrorMessage(LineTooLong,0);
  614.             EXIT;
  615.         END;
  616.         IF (newWord[index] = ASCII.NUL)
  617.         THEN
  618.             EXIT;
  619.         END;
  620.         CommandLine[counter] := newWord[index];
  621.         counter := counter+1;
  622.         index := index+1;
  623.         END;
  624.         CommandLine[counter] := ASCII.NUL;
  625.     END;
  626.     END;
  627. END CommandWord;
  628.  
  629. (* Try to execute either a built-in or a disk command *)
  630. PROCEDURE CommandExecute;
  631. VAR
  632.     cmdFirst, cmdLast, sfxFirst, sfxLast: CARDINAL;
  633.     pathname, pathname1, testCmd, testSfx: Line;
  634.     pathSeen, suffixSeen: BOOLEAN;
  635.     result: INTEGER;
  636.  
  637. (* Sequentially put parts of search path into "testCmd" *)
  638. (* Return TRUE while that is possible, FALSE when list end reached *)
  639. (* Depends on cmdFirst,cmdLast indexes *)
  640. PROCEDURE nextPath(): BOOLEAN;
  641. BEGIN
  642.     IF (PathPointer^[cmdFirst] = ASCII.NUL)
  643.     THEN
  644.     testCmd[0] := ASCII.NUL;
  645.     RETURN FALSE;
  646.     END;
  647.     cmdLast := cmdFirst;
  648.     WHILE ((PathPointer^[cmdLast] # PartSep) AND
  649.     (PathPointer^[cmdLast] # ASCII.NUL))
  650.     DO
  651.     cmdLast := cmdLast+1;
  652.     END;
  653.     IF ((cmdLast-cmdFirst) > 0)
  654.     THEN
  655.     String.Copy(PathPointer^,cmdFirst,cmdLast-cmdFirst,testCmd);
  656.     ELSE
  657.     testCmd[0] := ASCII.NUL;
  658.     END;
  659.     IF (PathPointer^[cmdLast] = PartSep)
  660.     THEN
  661.     cmdFirst := cmdLast+1;
  662.     ELSE
  663.     cmdFirst := cmdLast;
  664.     END;
  665.     RETURN TRUE;
  666. END nextPath;
  667.  
  668. (* Sequentially put parts of suffix list into "testSfx" *)
  669. (* Return TRUE while that is possible, FALSE when list end reached *)
  670. (* Depends on sfxFirst,sfxLast indexes *)
  671. PROCEDURE nextSfx(): BOOLEAN;
  672. BEGIN
  673.     IF (SuffixesPointer^[sfxFirst] = ASCII.NUL)
  674.     THEN
  675.     testSfx[0] := ASCII.NUL;
  676.     RETURN FALSE;
  677.     END;
  678.     sfxLast := sfxFirst;
  679.     WHILE ((SuffixesPointer^[sfxLast] # PartSep) AND
  680.     (SuffixesPointer^[sfxLast] # ASCII.NUL))
  681.     DO
  682.     sfxLast := sfxLast+1;
  683.     END;
  684.     IF ((sfxLast-sfxFirst) > 0)
  685.     THEN
  686.     String.Copy(SuffixesPointer^,sfxFirst,sfxLast-sfxFirst,testSfx);
  687.     ELSE
  688.     testSfx[0] := ASCII.NUL;
  689.     END;
  690.     IF (SuffixesPointer^[sfxLast] = PartSep)
  691.     THEN
  692.     sfxFirst := sfxLast+1;
  693.     ELSE
  694.     sfxFirst := sfxLast;
  695.     END;
  696.     RETURN TRUE;
  697. END nextSfx;
  698.  
  699. (* The "cd" command *)
  700. (* Change to another disk directory *)
  701. PROCEDURE doCD;
  702. VAR
  703.     index, index1: CARDINAL;
  704.     dchar: CHAR;
  705.     drive: CARDINAL;
  706.     drivemap: LONGCARD;
  707. BEGIN
  708.     IF (CommandLine[2] = DriveSep)
  709.     THEN
  710.     dchar := CAP(CommandLine[1]);
  711.     IF ((ORD(dchar) < ORD('A')) OR (ORD(dchar) > ORD('P')))
  712.     THEN
  713.         ErrorMessage(DirNotFound,0);
  714.         RETURN;
  715.     END;
  716.     drive := ORD(dchar) - ORD('A');
  717.     GEMDOS.SetDrv(drive,drivemap);
  718.     index := 3;        (* Rest of pathname starts here *)
  719.     IF (CommandLine[index] = ASCII.NUL)
  720.     THEN
  721.         CommandLine[index] := PathSep;
  722.         CommandLine[index+1] := ASCII.NUL;
  723.     END;
  724.     ELSE
  725.     GEMDOS.GetDrv(drive);
  726.     index := 1;        (* Pathname starts here *)
  727.     END;
  728.     index1 := index;
  729.     WHILE ((CommandLine[index1] # ASCII.NUL) AND
  730.     (CommandLine[index1] # Space))
  731.     DO
  732.     index1 := index1+1;
  733.     END;
  734.     String.Copy(CommandLine,index,index1-index,pathname);
  735.     IF (GEMDOS.SetPath(pathname) = FALSE)
  736.     THEN
  737.     ErrorMessage(DirNotFound,0);
  738.     END;
  739. END doCD;
  740.  
  741. (* The "echo" command *)
  742. (* Print our arguments on the screen *)
  743. PROCEDURE doECHO;
  744. VAR
  745.     index: CARDINAL;
  746. BEGIN
  747.     index := 1;    (* Skip leading byte; reserved for GEMDOS *)
  748.     WHILE (CommandLine[index] # ASCII.NUL)
  749.     DO
  750.     Write(CommandLine[index]);
  751.     index := index+1;
  752.     END;
  753.     WriteLn;
  754. END doECHO;
  755.  
  756. (* The "pwd" command *)
  757. (* Print the name of our working directory on the screen *)
  758. PROCEDURE doPWD;
  759. VAR
  760.     index: CARDINAL;
  761.     drive: CARDINAL;
  762.     dchar: CHAR;
  763. BEGIN
  764.     GEMDOS.GetDrv(drive);
  765.     GEMDOS.GetPath(pathname,drive+1);
  766.     dchar := CHR(drive + ORD('A'));
  767.     Write(dchar);
  768.     Write(DriveSep);
  769.     index := 0;
  770.     WHILE (pathname[index] # ASCII.NUL)
  771.     DO
  772.     Write(pathname[index]);
  773.     index := index+1;
  774.     END;
  775.     WriteLn;
  776. END doPWD;
  777.  
  778. (* The "set" command *)
  779. (* List all variables, or set some flags *)
  780. PROCEDURE doSET;
  781. VAR
  782.     index: CARDINAL;
  783. BEGIN
  784.     index := 2;
  785.     CASE CommandLine[1]
  786.     OF
  787.     '-':        (* Turn on some flags *)
  788.         WHILE ((CommandLine[index] # Space) AND
  789.         (CommandLine[index] # ASCII.NUL))
  790.         DO
  791.         CASE CommandLine[index]
  792.         OF
  793.             'v':    (* Print lines as they are read *)
  794.             PrintLinesRead := TRUE;
  795.             |
  796.             'x':    (* Print lines as they are executed *)
  797.             PrintLinesExecuted := TRUE;
  798.             |
  799.             ELSE    (* Error *)
  800.             ErrorMessage(FlagNotFound,0);
  801.         END;
  802.         index := index+1;
  803.         END;
  804.         |
  805.     '+':        (* Turn off some flags *)
  806.         WHILE ((CommandLine[index] # Space) AND
  807.         (CommandLine[index] # ASCII.NUL))
  808.         DO
  809.         CASE CommandLine[index]
  810.         OF
  811.             'v':    (* Print lines as they are read *)
  812.             PrintLinesRead := FALSE;
  813.             |
  814.             'x':    (* Print lines as they are executed *)
  815.             PrintLinesExecuted := FALSE;
  816.             |
  817.             ELSE    (* Error *)
  818.             ErrorMessage(FlagNotFound,0);
  819.         END;
  820.         index := index+1;
  821.         END;
  822.         |
  823.     ELSE        (* Just list variables *)
  824.         ListVariables;
  825.     END;
  826. END doSET;
  827.  
  828. (* The "." command *)
  829. (* Temporarily take input from a file *)
  830. PROCEDURE doSOURCE;
  831. VAR
  832.     index: CARDINAL;
  833. BEGIN
  834.     index := 1;
  835.     WHILE ((CommandLine[index] # ASCII.NUL) AND
  836.     (CommandLine[index] # Space))
  837.     DO
  838.     index := index+1;
  839.     END;
  840.     String.Copy(CommandLine,1,index-1,pathname);
  841.     OpenStream(FileStream,pathname,READ,result);
  842.     IF (result >= 0)
  843.     THEN
  844.     ReadFromFile := TRUE;
  845.     ELSE
  846.     ErrorMessage(FileNotFound,result);
  847.     END;
  848. END doSOURCE;
  849.  
  850. (* The "version" command *)
  851. (* Print the version of this program and the OS *)
  852. PROCEDURE doVERSION;
  853. VAR
  854.     ver: CARDINAL;
  855.     buffer: Line;
  856. BEGIN
  857.     WriteString(CLIVersion);
  858.     WriteLn;
  859.     WriteString(GEMDOSVersion);
  860.     GEMDOS.Version(ver);
  861.     ConvertInteger(ver MOD 256,1,buffer);
  862.     WriteString(buffer);
  863.     Write('.');
  864.     ConvertInteger(ver DIV 256,1,buffer);
  865.     WriteString(buffer);
  866.     WriteLn;
  867. END doVERSION;
  868.  
  869. (* Try to execute the passed command, assuming that the global *)
  870. (* CommandLine is correctly setup *)
  871. (* Status of execution try returned as result *)
  872. PROCEDURE tryEXEC(VAR command: ARRAY OF CHAR): INTEGER;
  873. VAR
  874.     result: INTEGER;
  875.     envstr: ARRAY[0..0] OF CHAR;
  876. BEGIN
  877.     envstr[0] := ASCII.NUL;
  878.     GEMDOS.Exec(GEMDOS.loadExecute,command,CommandLine,envstr,result);
  879.     RETURN result;
  880. END tryEXEC;
  881.  
  882. (* CommandExecute *)
  883. BEGIN
  884.     IF (PrintLinesExecuted)
  885.     THEN
  886.     WriteString("> ");
  887.     cmdLast := 0;
  888.     WHILE (CommandName[cmdLast] # ASCII.NUL)
  889.     DO
  890.         Write(CommandName[cmdLast]);
  891.         cmdLast := cmdLast+1;
  892.     END;
  893.     cmdLast := 0;
  894.     WHILE (CommandLine[cmdLast] # ASCII.NUL)
  895.     DO
  896.         Write(CommandLine[cmdLast]);
  897.         cmdLast := cmdLast+1;
  898.     END;
  899.     WriteLn;
  900.     END;
  901.  
  902.     (* Try builtin commands first *)
  903.     (* The set command; list variables, set options, etc.; "set" *)
  904.     testCmd := SetCommand;
  905.     IF (String.Compare(testCmd,CommandName) = String.Equal)
  906.     THEN
  907.     doSET;
  908.     RETURN;
  909.     END;
  910.     (* The source command; "." *)
  911.     testCmd := SourceCommand;
  912.     IF (String.Compare(testCmd,CommandName) = String.Equal)
  913.     THEN
  914.         doSOURCE;
  915.     RETURN;
  916.     END;
  917.     (* The echo command; "echo" *)
  918.     testCmd := EchoCommand;
  919.     IF (String.Compare(testCmd,CommandName) = String.Equal)
  920.     THEN
  921.     doECHO;
  922.     RETURN;
  923.     END;
  924.     (* The change directory command; "cd" *)
  925.     testCmd := ChangeDirectory;
  926.     IF (String.Compare(testCmd,CommandName) = String.Equal)
  927.     THEN
  928.     doCD;
  929.     RETURN;
  930.     END;
  931.     (* The print directory name command; "pwd" *)
  932.     testCmd := PrintDirectory;
  933.     IF (String.Compare(testCmd,CommandName) = String.Equal)
  934.     THEN
  935.     doPWD;
  936.     RETURN;
  937.     END;
  938.     testCmd := VersionCommand;
  939.     IF (String.Compare(testCmd,CommandName) = String.Equal)
  940.     THEN
  941.     doVERSION;
  942.     RETURN;
  943.     END;
  944.  
  945.     (* Now try to execute a disk file *)
  946.     cmdLast := 1;
  947.     WHILE (CommandLine[cmdLast] # ASCII.NUL)
  948.     DO
  949.     cmdLast := cmdLast+1;
  950.     END;
  951.     IF (cmdLast > 127)
  952.     THEN
  953.     ErrorMessage(CmdArgsTooLong,0);
  954.     RETURN;
  955.     END;
  956.     CommandLine[0] := CHR(cmdLast-1);
  957.     pathSeen := FALSE;
  958.     suffixSeen := FALSE;
  959.     cmdLast := 0;
  960.     WHILE (CommandName[cmdLast] # ASCII.NUL)
  961.     DO
  962.     IF (CommandName[cmdLast] = DriveSep)
  963.     THEN
  964.         pathSeen := TRUE;
  965.     ELSIF (CommandName[cmdLast] = PathSep)
  966.     THEN
  967.         pathSeen := TRUE;
  968.     ELSIF (CommandName[cmdLast] = SuffixSep)
  969.     THEN
  970.         suffixSeen := TRUE;
  971.     END;
  972.     cmdLast := cmdLast+1;
  973.     END;
  974.     cmdFirst := 0;
  975.     cmdLast := 0;
  976.     sfxFirst := 0;
  977.     sfxLast := 0;
  978.     IF (pathSeen AND suffixSeen)
  979.     THEN
  980.     result := tryEXEC(CommandName);
  981.         IF (result < 0)
  982.     THEN
  983.         ErrorMessage(FileNotExecuted,result);
  984.     END;
  985.     RETURN;
  986.     ELSIF (pathSeen)
  987.     THEN        (* Have to try different suffixes *)
  988.     WHILE (nextSfx())
  989.     DO
  990.         String.Concat(CommandName,testSfx,pathname);
  991.         result := tryEXEC(pathname);
  992.         IF ((result < 0) AND (result # GEMDOS.EFilNF))
  993.         THEN
  994.         ErrorMessage(FileNotExecuted,result);
  995.         ELSIF (result >= 0)
  996.         THEN
  997.         RETURN;        (* Everything worked??? *)
  998.         END;
  999.     END;
  1000.     ErrorMessage(FileNotExecuted,result);
  1001.     RETURN;
  1002.     ELSIF (suffixSeen)
  1003.     THEN        (* Have to use search path *)
  1004.     WHILE (nextPath())
  1005.     DO
  1006.         String.Concat(testCmd,CommandName,pathname);
  1007.         result := tryEXEC(pathname);
  1008.         IF ((result < 0) AND (result # GEMDOS.EFilNF))
  1009.         THEN
  1010.         ErrorMessage(FileNotExecuted,result);
  1011.         ELSIF (result >= 0)
  1012.         THEN
  1013.         RETURN;        (* Everything worked??? *)
  1014.         END;
  1015.     END;
  1016.     ErrorMessage(FileNotExecuted,result);
  1017.     RETURN;
  1018.     ELSE        (* Have to use search path, suffixes *)
  1019.     WHILE (nextPath())
  1020.     DO
  1021.         sfxFirst := 0;
  1022.         sfxLast := 0;
  1023.         WHILE (nextSfx())
  1024.         DO
  1025.         String.Concat(testCmd,CommandName,pathname1);
  1026.         String.Concat(pathname1,testSfx,pathname);
  1027.         result := tryEXEC(pathname);
  1028.         IF ((result < 0) AND (result # GEMDOS.EFilNF))
  1029.         THEN
  1030.             ErrorMessage(FileNotExecuted,result);
  1031.         ELSIF (result >= 0)
  1032.         THEN
  1033.             RETURN;        (* Everything worked??? *)
  1034.         END;
  1035.         END;
  1036.     END;
  1037.     ErrorMessage(FileNotExecuted,result);
  1038.     RETURN;
  1039.     END;
  1040. END CommandExecute;
  1041.  
  1042. PROCEDURE doCLI;
  1043. TYPE
  1044.     saveArea = ARRAY[0..480] OF LONGCARD;
  1045.     saveAreaPtr = POINTER TO saveArea;
  1046. VAR
  1047.     saveBar: saveArea;
  1048.     saveAddr: saveAreaPtr;
  1049.     index: CARDINAL;
  1050. BEGIN
  1051.     AESGraphics.GrafMouse(GEMAESbase.MouseOff,NIL);
  1052.  
  1053.     (* Save the top of our screen *)
  1054.     CODE(3F3CH,2,4E4EH,548FH);    (* Get the address of our screen *)
  1055.     saveAddr := saveAreaPtr(REGISTER(0));
  1056.     FOR index := 0 TO 480
  1057.     DO
  1058.     saveBar[index] := saveAddr^[index];
  1059.     END;
  1060.  
  1061.     (* General initialization *)
  1062.     ReadFromFile := FALSE;
  1063.     PrintLinesRead := FALSE;
  1064.     PrintLinesExecuted := FALSE;
  1065.     GEMDOS.GetDTA(oldDTAaddr);
  1066.  
  1067.     (* GEM Initialization *)
  1068.     VDIControls.ClearWorkstation(VDIHandle);
  1069.     VDIControls.UpdateWorkstation(VDIHandle);
  1070.     VDIEscapes.EnterAlphaMode(VDIHandle);
  1071.  
  1072.     (* Simulate a "version" command to announce ourselves *)
  1073.     CommandName := VersionCommand;
  1074.     CommandLine[0] := ASCII.NUL;
  1075.     CommandExecute;
  1076.  
  1077.     (* Main Loop *)
  1078.     KeepGoing := TRUE;
  1079.     WHILE (KeepGoing)
  1080.     DO
  1081.     IF (ReadFromFile)
  1082.     THEN
  1083.         IF (EOS(FileStream))
  1084.         THEN
  1085.         ReadFromFile := FALSE;
  1086.         CloseStream(FileStream,Result);
  1087.         WriteString(Prompt);
  1088.         ReadLine;
  1089.         ELSE
  1090.         Counter := 0;
  1091.         LOOP
  1092.             Read8Bit(FileStream,SaveCh);
  1093.             IF ((SaveCh # ASCII.CR) AND (SaveCh # ASCII.LF))
  1094.             THEN
  1095.             LineBuffer[Counter] := SaveCh;
  1096.             Counter := Counter+1;
  1097.             ELSIF (SaveCh = ASCII.CR)
  1098.             THEN
  1099.             LineBuffer[Counter] := ASCII.NUL;
  1100.             EXIT;
  1101.             END;
  1102.         END;
  1103.         END;
  1104.     ELSE
  1105.         WriteString(Prompt);
  1106.         ReadLine;
  1107.     END;
  1108.     IF (PrintLinesRead)
  1109.     THEN
  1110.         Counter := 0;
  1111.         WHILE (LineBuffer[Counter] # ASCII.NUL)
  1112.         DO
  1113.         Write(LineBuffer[Counter]);
  1114.         Counter := Counter+1;
  1115.         END;
  1116.         WriteLn;
  1117.     END;
  1118.     CommandName[0] := ASCII.NUL;
  1119.     CommandLine[0] := ASCII.NUL;
  1120.     CommandLine[1] := ASCII.NUL;
  1121.     FirstIndex := 0;
  1122.     LastIndex := 0;
  1123.     LOOP
  1124.         CASE LineBuffer[LastIndex]
  1125.         OF
  1126.         ASCII.EOT, ASCII.NUL, Tab, Space: (* End of word *)
  1127.             SaveCh := LineBuffer[LastIndex];
  1128.             IF (FirstIndex # LastIndex)
  1129.             THEN
  1130.             CommandWord;
  1131.             END;
  1132.             IF (SaveCh = ASCII.EOT)
  1133.             THEN
  1134.             KeepGoing := FALSE;
  1135.             EXIT;
  1136.             END;
  1137.             IF (SaveCh = ASCII.NUL)
  1138.             THEN
  1139.             EXIT;
  1140.             END;
  1141.             WHILE (((LineBuffer[LastIndex+1] = Tab) OR
  1142.                 (LineBuffer[LastIndex+1] = Space)) AND
  1143.             (LastIndex < TSIZE(Line)))
  1144.             DO
  1145.             LastIndex := LastIndex+1;
  1146.             END;
  1147.             FirstIndex := LastIndex+1;
  1148.             |
  1149.         VarStart:        (* Shell variable *)
  1150.             ExpandVariable;
  1151.             |
  1152.         SQuote:            (* Single quote *)
  1153.             Counter := LastIndex+1;
  1154.             LOOP
  1155.             CASE LineBuffer[Counter]
  1156.             OF
  1157.                 ASCII.NUL:
  1158.                 ErrorMessage(UnClosedString,0);
  1159.                 EXIT;
  1160.                 |
  1161.                 SQuote:
  1162.                 EXIT;
  1163.                 |
  1164.                 ELSE
  1165.             END;
  1166.             Counter := Counter+1;
  1167.             END;
  1168.             IF (LineBuffer[Counter] = ASCII.NUL)
  1169.             THEN
  1170.             EXIT;
  1171.             END;
  1172.             LastIndex := Counter;
  1173.             |
  1174.         DQuote:            (* Double quote *)
  1175.             Counter := LastIndex+1;
  1176.             LOOP
  1177.             CASE LineBuffer[Counter]
  1178.             OF
  1179.                 ASCII.NUL:
  1180.                 ErrorMessage(UnClosedString,0);
  1181.                 EXIT;
  1182.                 |
  1183.                 VarStart:
  1184.                 LastIndex := Counter;
  1185.                 ExpandVariable;
  1186.                 |
  1187.                 DQuote:
  1188.                 EXIT;
  1189.                 |
  1190.                 ELSE
  1191.             END;
  1192.             Counter := Counter+1;
  1193.             END;
  1194.             IF (LineBuffer[Counter] = ASCII.NUL)
  1195.             THEN
  1196.             EXIT;
  1197.             END;
  1198.             LastIndex := Counter;
  1199.             |
  1200.         ELSE            (* Normal character *)
  1201.         END;
  1202.         LastIndex := LastIndex+1;
  1203.         IF (LastIndex >= TSIZE(Line))
  1204.         THEN
  1205.         EXIT;
  1206.         END;
  1207.     END;
  1208.     IF (CommandName[0] # ASCII.NUL)
  1209.     THEN
  1210.         CommandExecute;
  1211.     END;
  1212.     END;
  1213.     GEMDOS.SetDTA(oldDTAaddr);    (* Old filename buffer *)
  1214.  
  1215.     (* GEM Termination *)
  1216.     VDIEscapes.ExitAlphaMode(VDIHandle);
  1217.     AESForms.FormDialogue(GEMAESbase.FormFinish,
  1218.     0,0,ScreenWidth,ScreenHeight,0,0,ScreenWidth,ScreenHeight);
  1219.     VDIControls.UpdateWorkstation(VDIHandle);
  1220.  
  1221.     (* Restore the top of our screen *)
  1222.     CODE(3F3CH,2,4E4EH,548FH);    (* Get the address of our screen *)
  1223.     saveAddr := saveAreaPtr(REGISTER(0));
  1224.     FOR index := 0 TO 480
  1225.     DO
  1226.     saveAddr^[index] := saveBar[index];
  1227.     END;
  1228.  
  1229.     AESGraphics.GrafMouse(GEMAESbase.MouseOn,NIL);
  1230. END doCLI;
  1231.  
  1232. (* CLI *)
  1233. BEGIN
  1234.     (* Try kludging around a runtime memory setup bug in the current *)
  1235.     (* version of Modula2-ST *)
  1236.     (* We'll leave "MaxStack" for stack space *)
  1237.     GEMDOS.Alloc(LONGCARD(MaxStack),BlockAddr);
  1238.     IF (LONGINT(BlockAddr) <= 0)
  1239.     THEN
  1240.     ErrorMessage(NotEnoughMemory,1);
  1241.     HALT;
  1242.     END;
  1243.     OldStackPage := PagePointer(REGISTER(14));
  1244.     (* The 72 in the next line, and the 18 words in the following loop *)
  1245.     (* are based on emperical observations of what the stack looks like *)
  1246.     (* immediately after a program starts running *)
  1247.     StackPage := PagePointer(LONGCARD(BlockAddr)+
  1248.     LONGCARD(MaxStack-72));
  1249.     FOR Counter := 0 TO 17
  1250.     DO
  1251.     StackPage^[Counter] := OldStackPage^[Counter];
  1252.     END;
  1253.     SETREG(14,ADDRESS(StackPage));
  1254.     SETREG(15,ADDRESS(StackPage));
  1255.     
  1256.     (* General initialization *)
  1257.     IF (Storage.CreateHeap(MaxHeap,FALSE) = FALSE)
  1258.     THEN
  1259.     ErrorMessage(NotEnoughMemory,0);
  1260.     HALT;
  1261.     END;
  1262.     String.InitStringModule;
  1263.     String.SetTerminator(ASCII.NUL);
  1264.  
  1265.     (* Set up shell variables *)
  1266.     VarTop := NIL;
  1267.     VarBottom := NIL;
  1268.     CommandName := PathVarName;
  1269.     CommandLine := PathDefault;
  1270.     DefineVariable(CommandName,CommandLine);
  1271.     CommandName := SuffixesVarName;
  1272.     CommandLine := SuffixesDefault;
  1273.     DefineVariable(CommandName,CommandLine);
  1274.  
  1275.     (* GEM Initialization *)
  1276.     VDIHandle := AESGraphics.GrafHandle(WidthChar,HeightChar,WidthFont,
  1277.     HeightFont) ;
  1278.     FOR Counter := 0 TO 9
  1279.     DO
  1280.     WorkIn[Counter] := 1;
  1281.     END;
  1282.     WorkIn[Counter] := 2;
  1283.     VDIControls.OpenVirtualWorkstation(WorkIn,VDIHandle,WorkOut);
  1284.     ScreenWidth := WorkOut[0];
  1285.     ScreenHeight := WorkOut[1];
  1286.  
  1287.     (* Set up as a desk accessory, or just run the shell... *)
  1288.     (* Whatever is desired. *)
  1289.     IF (DeskAccessory = 0)
  1290.     THEN
  1291.     doCLI;
  1292.     ELSE
  1293.     ApplId := AESApplications.ApplInitialise();
  1294.     MenuId := AESMenus.MenuRegister(ApplId,"  Tiny Shell");
  1295.     WHILE (TRUE)
  1296.     DO
  1297.         EventId := AESEvents.EventMultiple(GEMAESbase.MesageEvent,
  1298.         0, 0, 0,
  1299.         0, 0, 0, 0, 0,
  1300.         0, 0, 0, 0, 0,
  1301.         ADR(MsgBuf[0]), 0, 0,
  1302.         Dummy, Dummy, Dummy, Dummy, Dummy, Dummy);
  1303.         IF (EventId = GEMAESbase.MesageEvent)
  1304.         THEN
  1305.         CASE (MsgBuf[0])
  1306.         OF
  1307.             GEMAESbase.AccessoryOpen:    (* Start a shell *)
  1308.             doCLI;
  1309.             |
  1310.             GEMAESbase.AccessoryClose:    (* Really needed? *)
  1311.             |
  1312.             ELSE
  1313.         END;
  1314.         END;
  1315.     END;
  1316.     END;
  1317.  
  1318.     (* Clean up everything *)
  1319.     VDIControls.CloseVirtualWorkstation(VDIHandle);
  1320.  
  1321. END CLI.