home *** CD-ROM | disk | FTP | other *** search
/ kermit.columbia.edu / kermit.columbia.edu.tar / kermit.columbia.edu / ucsdmagiscan2 / kermit.text < prev    next >
Text File  |  2011-08-11  |  22KB  |  662 lines

  1.  
  2. program kermit;
  3. UCSD Pascal KERMIT for the Terak p-System, from Kate MacGregor, Cornell U
  4. Adapted to Pascal Microengine by Tim Shimeall, UCI
  5. {Changes:
  6. - Added device declarations copied from Microengine hardware documentation
  7. - Replaced external assembly language routines with Pascal versions
  8. - Modified debug messages to be label values printed
  9. - Changed format of packetwrite display to show header fields
  10. - Implemented machine-dependent packet timeout
  11. - Added debug packetwrites in recsw
  12. - Added wrap-around debug info region
  13. - Added legality check in showparms
  14. - Removed lf elimination check in echo procedure
  15. - Unitwrite calls replaced by calls to device driving routines
  16. - Most uses of char_int_rec replaced by ord and chr
  17. - Removed queue (no interrupts)
  18. - Used sets for integer ops to getaround Microengine bug
  19. - Changed parser from a unit to a segment procedure to allow swapping
  20. - Split utility procs into separate files for editing and transfer convinience
  21. }
  22.  
  23. {Adapted to Joyce Loebl's Magiscan 2 Image processing computer,
  24.  by Henry Balen, Lancaster University }
  25. {Changes:
  26. - added ability for the parser to recognize digits,
  27.   this enabled a Baudrate command to be implemented
  28. - added a command to set a work disk, set disk #.
  29. - The IO subroutines were put into an unit RS232 and
  30.   changed to suit the Magiscan.
  31. - put the parser back into an unit since the Magiscan has 128K
  32.   available.
  33. - modified the constants for the screen because the Magiscan only
  34.   has 64 columns.
  35. - Added a unit SysUnit to enable the user to interogate the
  36.   current work disk and delete files if so wishes.
  37. - Added a unit FileHandle which gives routines for accessing
  38.   files for reading and writing, the old version of this didn't
  39.   close a file if there was an unsuccessful receive/send this
  40.   is now fixed.
  41. - Modified the Buffer empty and fill routines to use these.
  42. - Added the ability to do eight bit prefixing and the necessary
  43.   routines for this.
  44. - Have added a new command called TRANSFER ( do a TRANSFER
  45.   TYPE <type> ), which enables transfers of image,data,code and
  46.   text 'types'.
  47. - There is also image LOAD routine implemented, this allows
  48.   the images to be loaded from disk and transfered to the Host
  49.   straight from image memory.
  50. }
  51.  Futher changes by H Balen, now of Joyce Loebl, March 1986 
  52. {
  53. - The receive packet routine has been put in the magiscan's
  54.   microcode, data can now be succesfully received and transmitted
  55.   at 9600 baud (except images ! max =4800 ), though the screen
  56.   cannot scroll fast enough for incoming characters greater
  57.   than 1200.
  58. - Two new options have been included - they are the MUX delay
  59.   which tells the Magiscan how many cycles the wait when
  60.   sending characters, and the option of using the winchester
  61.   on #9.
  62. }
  63.  
  64. (*$R-*) (* turn range checking off *)
  65. (*$S+*) (* turn swapping on *)
  66. (* $L PRINTER: *) (* no listing *)
  67.  
  68. Uses
  69.   M2Types,M2IpRoot,M2Sys,
  70.   (*$U DISK.CODE*)DiskUnit,
  71.   (*$U RS232.Code*)RS232,
  72.   (*$U SysUnit.Code*)SysUnit,
  73.   (*$U ParUnit.Code*)ParseUnit,
  74.   (*$U FileUnit.Code*)FileHandle,
  75.   (*$U HANDLE.CODE*)HANDLER; { the microcode }
  76.  
  77. const blksize = 512;
  78.       oport = 8;          (* output port # *)
  79.       (* clearscreen = 12;   charcter which erases screen *)
  80.       { bell = 7; }           (* ASCII bell *)
  81.       esc = 27;           (* ASCII escape *)
  82.       maxpack = 93;       (* maximum packet size minus 1 *)
  83.       soh = 1;            (* start of header *)
  84.       sp = 32;            (* ASCII space *)
  85.       cr = 13;            (* ASCII CR *)
  86.       lf = 10;            (* ASCII line feed *)
  87.       dle = 16;           (* ASCII DLE (space compression prefix for psystem) *)
  88.       del = 127;          (* delete *)
  89.       my_esc = 29;        (* default esc char for connect (^]) *)
  90.       maxtry = 5;         (* number of times to retry sending packet *)
  91.       my_quote = '#';     (* quote character I'll use *)
  92.       my_bquote = '&';    { binary quate character I'll use }
  93.       my_pad = 0;         (* number of padding chars I need *)
  94.       my_pchar = 0;       (* padding character I need *)
  95.       my_eol = 13;        (* end of line character i need *)
  96.       my_time = 5;        (* seconds after which I should be timed out *)
  97.       maxtim = 20;        (* maximum timeout interval *)
  98.       mintim = 2;         (* minimum time out interval *)
  99.       at_eof = -1;        (* value to return if at eof *)
  100.       eoln_sym = 13;      (* pascal eoln sym *)
  101.       back_space = 8;     (* pascal backspace sym *)
  102.  
  103.  
  104. (* screen control information *)
  105.   (* console line on which to put specified info *)
  106.       title_line = 1;
  107.       statusline = 2;
  108.       packet_line = 3;
  109.       retry_line = 4;
  110.       file_line = 5;
  111.       error_line = 6;
  112.       prompt_line = 7;
  113.       debug_line = 9;
  114.       debug_max = 12; (* Max lines of debug to show at once *)
  115.   (* position on line to put info *)
  116.       statuspos = 54;
  117.       packet_pos = 19;
  118.       retry_pos = 17;
  119.       file_pos = 11;
  120.  
  121.       Intsize = 15;
  122.  
  123. type packettype = packed array[0..maxpack] of char;
  124.      parity_type = (evenpar, oddpar, markpar, spacepar, nopar);
  125.  
  126.      char_int_rec = record (* allows character to be treated as integer... *)
  127.                            (* is system dependent *)
  128.                       case boolean of
  129.                           true: (i: integer);
  130.                           false: (ch: char)
  131.                     end; (* record *)
  132.  
  133.      int_bool_rec = record (* allows integer to be treated as boolean... *)
  134.                            (* used for numeric AND,OR,XOR...system dependent *)
  135.                            (* replaced by set version to escape microengine
  136.                               bug *)
  137.                       case boolean of
  138.                           true: (i: integer);
  139.                           false: (b: set of 0..intsize);
  140.                     end; (* record *)
  141.  
  142.      Port = (Terminal,Modem);
  143.  
  144.  
  145. var state: char; (* current state *)
  146.     s: string;
  147.     eol, bquote, quote, esc_char: char;
  148.     fwarn, ibm, half_duplex, debug: boolean;
  149.     delay, i, size, rpsiz, spsiz, pad, n, num_try, oldtry, timint: integer;
  150.     recpkt, packet: packettype;
  151.     padchar, ch: char;
  152.     debf: text; (* file for debug output *)
  153.     debnext:0..7; (* offset for next debug message *)
  154.     parity: parity_type;
  155.     xon: char;
  156.     vol, Baud: integer;
  157.     parity_array: packed array[char] of char;
  158.     ctlset: set of char;
  159.     rec_ok, send_ok: boolean;
  160.  
  161.  
  162. function read_ch(p: port; var ch: char): boolean;
  163. forward;
  164.  
  165. function aand(x,y: integer): integer;
  166. forward;
  167.  
  168. function aor(x,y: integer): integer;
  169. forward;
  170.  
  171. function xor(x,y: integer): integer;
  172. forward;
  173.  
  174. procedure error(p: packettype; len: integer);
  175. forward;
  176.  
  177. procedure ino_error(i: integer);
  178. forward;
  179.  
  180. procedure debugwrite(s: string);
  181. forward;
  182.  
  183. procedure debugint(s: string; i: integer);
  184. forward;
  185.  
  186. procedure writescreen(s: string);
  187. forward;
  188.  
  189. procedure refresh_screen(numtry, num: integer);
  190. forward;
  191.  
  192. function min(x,y: integer): integer;
  193. forward;
  194.  
  195. function tochar(ch: char): char;
  196. forward;
  197.  
  198. function unchar(ch: char): char;
  199. forward;
  200.  
  201. function ctl(ch: char): char;
  202. forward;
  203.  
  204. function getfil(filename: string): boolean;
  205. forward;
  206.  
  207. procedure Bbufemp(buffer: packettype; len: integer);
  208. forward;
  209.  
  210. function Bbufill(var buffer: packettype): integer;
  211. forward;
  212.  
  213. procedure bufemp(buffer: packettype; var f: text; len: integer);
  214. forward;
  215.  
  216. function bufill(var buffer: packettype): integer;
  217. forward;
  218.  
  219. procedure spar(var packet: packettype);
  220. forward;
  221.  
  222. procedure rpar(var packet: packettype);
  223. forward;
  224.  
  225. procedure spack(ptype: char; num:integer; len: integer; data: packettype);
  226. forward;
  227.  
  228. function getch(var r: char; p: port): boolean;
  229. forward;
  230.  
  231. function getsoh(p: port): boolean;
  232. forward;
  233.  
  234. function rpack(var len, num: integer; var data: packettype): char;
  235. forward;
  236.  
  237. procedure read_str(p: port; var s: string);
  238. forward;
  239.  
  240. procedure packetwrite(p: packettype; len: integer);
  241. forward;
  242.  
  243. procedure show_parms;
  244. forward;
  245.  
  246.  
  247. (*$I HELP.TEXT*) (* Segment Procedure Help *)
  248. (*$I SENDSW.TEXT*) (* Segment Procedure Sendsw *)
  249. (*$I RECSW.TEXT*) (* Segment Procedure Recsw *)
  250. (*$I UTILS.TEXT *) (* General Utility procedures *)
  251. (*$I BINUTILS.TEXT*) { Routines for Binary transfer }
  252. (*$I RSUTILS.TEXT *) (* Utility procedures for send and receive *)
  253.  
  254. procedure connect;
  255.  
  256. (* connect to remote host (terminal emulation *)
  257.  
  258. var ch: char;
  259.     close: boolean;
  260.  
  261.   procedure read_esc;
  262.  
  263.   (* read charcter after esc char and interpret it *)
  264.  
  265.     begin
  266.       repeat
  267.       until read_ch(terminal,ch);       (* wait until they've typed something in
  268.  *)
  269.       if (ch in ['a'..'z']) then  (* uppercase it *)
  270.           ch := chr(ord(ch) - ord('a') + ord('A'));
  271.       if ch in [{'B',}'C','S','D','?'] then
  272.           begin
  273.           writeln;
  274.           case ch of
  275.               (*'B': sendbrk;        B: send a break to the IBM *)
  276.               'C': close := true; (* C: end connection *)
  277.               'S': begin          (* S: show status *)
  278.                    noun := allsym;
  279.                    showparms
  280.                    end; (* S *)
  281.               'D':begin
  282.                   vol := ord(disk[2]) - ord('0');
  283.                   if vol in [9,10] then
  284.                     writeln('Cannot DIR a Winchester')
  285.                    else
  286.                      PrintNames(vol,value)
  287.                   end; (* D *)
  288.               '?': begin          (* ?: show options *)
  289.                   (* writeln('B    Send a BREAK signal.'); *)
  290.                   writeln('C    Close Connection, return to ');
  291.                   writeln('     KERMIT-UCSD command level.');
  292.                   writeln('S    Show Status of connection');
  293.                   writeln('D    displays the current directory');
  294.                   writeln('?    Print this list');
  295.                   write('^',ctl(esc_char),'   send the escape ');
  296.                   writeln('character itself to the');
  297.                   writeln('     remote host.');
  298.                 end; (* ? *)
  299.             end (* case *)
  300.            end
  301.       else if ch = esc_char then  (* ESC-char: send it out *)
  302.         begin
  303.           if half_duplex then
  304.             begin
  305.               echo(ch);
  306.               while not istbtr do;
  307.               sndbbt(ch);
  308.             end (* if *)
  309.         end (* else if *)
  310.       else                        (* anything else: ignore *)
  311.           write(chr(bell))
  312.     end; (* read_esc *)
  313.  
  314.   begin (* connect *)
  315.     writeln('Connecting to host...type CTRL-',ctl(esc_char),' C to exit');
  316.     close := false;
  317.     repeat
  318.         if read_ch(modem,ch) then        (* if char from host then *)
  319.             echo(ch);                   (* echo it *)
  320.  
  321.         if read_ch(terminal,ch) then        (* if char from keyboard then *)
  322.             if ch <> esc_char then      (* if not ESC-char then *)
  323.               begin
  324.                 if half_duplex then       (* echo it if half-duplex *)
  325.                     echo(ch);
  326.                 while not istbtr do;
  327.                 sndbbt(ch)               (* send it out the port *)
  328.               end (* if *)
  329.             else (* ch = esc_char *)    (* else is ESC-char so *)
  330.               read_esc;                   (* interpret next char *)
  331.     until close;                      (* if still connected, get more *)
  332.     writeln('Disconnected')
  333.   end; (* connect *)
  334.  
  335. procedure fill_parity_array;
  336.  
  337. (* parity value table for even parity...not(entry) = odd parity *)
  338.  
  339. const min = 0;
  340.       max = 126;
  341.  
  342. var i, shifter, counter: integer;
  343.     minch, maxch, ch: char;
  344.     r: char_int_rec;
  345.  
  346.   begin
  347.     minch := chr(min);
  348.     maxch := chr(max);
  349.     case parity of
  350.       evenpar:
  351.         begin
  352.           for ch := minch to maxch do
  353.             begin
  354.               r.ch := ch;               (* put char into variant record *)
  355.               shifter := aand(r.i,255); (* mask off parity bit *)
  356.               counter := 0;
  357.               for i := 1 to 7 do        (* count the 1's *)
  358.                 begin
  359.                   if odd(shifter) then
  360.                       counter := counter + 1;
  361.                   shifter := shifter div 2
  362.                 end; (* for i *)
  363.               if odd(counter) then       (* stick a 1 on if necessary *)
  364.                   parity_array[ch] := chr(aor(ord(ch),128))
  365.               else
  366.                   parity_array[ch] := chr(aand(ord(ch),127))
  367.             end; (* for ch *)
  368.         end; (* case even *)
  369.       oddpar:
  370.         begin
  371.           for ch := minch to maxch do
  372.             begin
  373.               r.ch := ch;                (* put char into variant record *)
  374.               shifter := aand(r.i,255);  (* mask off parity bit *)
  375.               counter := 0;
  376.               for i := 1 to 7 do         (* count the 1's *)
  377.                 begin
  378.                   if odd(shifter) then
  379.                       counter := counter + 1;
  380.                   shifter := shifter div 2
  381.                 end; (* for i *)
  382.               if odd(counter) then        (* stick a 1 on if necessary *)
  383.                   parity_array[ch] := chr(aand(ord(ch),127))
  384.               else
  385.                   parity_array[ch] := chr(aor(ord(ch),128))
  386.             end; (* for ch *)
  387.         end; (* case odd *)
  388.       markpar:
  389.           for ch := minch to maxch do     (* stick a 1 on all chars *)
  390.               parity_array[ch] := chr(aor(ord(ch),128));
  391.       spacepar:
  392.           for ch := minch to maxch do     (* mask off parity on all chars *)
  393.               parity_array[ch] := chr(aand(ord(ch),127));
  394.       nopar:
  395.           for ch := minch to maxch do     (* don't mess w/parity bit at all *)
  396.               parity_array[ch] := ch;
  397.     end; (* case *)
  398.   end; (* fill_parity_array *)
  399.  
  400. procedure write_bool(s: string; b: boolean);
  401.  
  402. (* writes message & 'on' if b, 'off' if not b *)
  403.   begin
  404.     write(s);
  405.     case b of
  406.         true: writeln('on');
  407.         false: writeln('off');
  408.       end; (* case *)
  409.   end; (* write_bool *)
  410.  
  411. procedure writeTrans;
  412.  writes the transfer state 
  413.  
  414. begin
  415. write('Transfer Type : ');
  416. case TranState of
  417.   CodeFile : writeln('BINARY');
  418.   ImgFile : writeln('IMAGE');
  419.   TxtFile : writeln('TEXT');
  420. "BinFile : writeln('DATA')
  421.   end
  422. end{writeTrans};
  423.  
  424. procedure show_parms;
  425.  
  426. (* shows the various settable parameters *)
  427.  
  428.   begin
  429.     writeln;
  430.     if noun in [allsym, debugsym, ibmsym, escsym, filewarnsym,
  431.                 muxsym, transym, disksym, localsym, baudsym, paritysym] then
  432.     case noun of
  433.         allsym:
  434.           begin
  435.             write_bool('Debugging is ',debug);
  436.             writeln('Escape character is ^',ctl(esc_char));
  437.             write_bool('File warning is ',fwarn);
  438.             write_bool('IBM is ',ibm);
  439.             write_bool('Local echo is ',halfduplex);
  440.             case parity of
  441.                 evenpar: write('Even');
  442.                 markpar: write('Mark');
  443.                 nopar: write('No');
  444.                 oddpar: write('Odd');
  445.                 spacepar: write('Space');
  446.               end; (* case *)
  447.             writeln(' parity');
  448.             writeln('Baudrate is ',Baud);
  449.             writeln('Drive is ',disk);
  450.             writeln('MUX is ',MUXDelay);
  451.             writetrans
  452.           end; (* allsym *)
  453.         debugsym: write_bool('Debugging is ',debug);
  454.         escsym: writeln('Escape character is ^',ctl(esc_char));
  455.         filewarnsym: write_bool('File warning is ',fwarn);
  456.         ibmsym: write_bool('IBM is ',ibm);
  457.         localsym: write_bool('Local echo is ',halfduplex);
  458.         baudsym : writeln('Baudrate is ',Baud);
  459.         disksym : writeln('Drive is ',disk);
  460.         transym : writetrans;
  461.         muxsym : writeln('MUX is ',MUXDelay);
  462.         paritysym: begin
  463.             case parity of
  464.                 evenpar: write('Even');
  465.                 markpar: write('Mark');
  466.                 nopar: write('No');
  467.                 oddpar: write('Odd');
  468.                 end;
  469.             writeln(' parity');
  470.            end; (* paritysym *)
  471.         typesym : writetrans
  472.       end (* case *)
  473.       else write(chr(bell));
  474.   end; (* show_sym *)
  475.  
  476. procedure set_parms;
  477.  
  478. (* sets the parameters *)
  479.  
  480.   begin
  481.     case noun of
  482.         debugsym: case adj of
  483.                       onsym: begin
  484.                           debug := true;
  485.                           (*$I-*)
  486.                           rewrite(debf,'CONSOLE:')
  487.                           (*I+*)
  488.                         end; (* onsym *)
  489.                       offsym: debug := false
  490.                     end; (* case adj *)
  491.         escsym: escchar := newescchar;
  492.         filewarnsym: fwarn := (adj = onsym);
  493.         ibmsym: case adj of
  494.                     onsym: begin
  495.                           ibm := true;
  496.                           parity := markpar;
  497.                           half_duplex := true;
  498.                           fillparityarray
  499.                           end; (* onsym *)
  500.                     offsym: begin
  501.                             ibm := false;
  502.                             parity := nopar;
  503.                             half_duplex := false;
  504.                             fillparityarray
  505.                             end; (* onsym *)
  506.                   end; (* case adj *)
  507.         localsym: halfduplex := (adj = onsym);
  508.         paritysym: begin
  509.                    case adj of
  510.                        evensym: parity := evenpar;
  511.                        marksym: parity := markpar;
  512.                        nonesym: parity := nopar;
  513.                        oddsym: parity := oddpar;
  514.                        spacesym: parity := spacepar;
  515.                      end; (* case *)
  516.                    fill_parity_array;
  517.                   end; (* paritysym *)
  518.         MUXsym  : begin
  519.                   MUXDelay := value
  520.                   end (* baudsym *);
  521.         baudsym : begin
  522.                   Baud := value;
  523.                   BaudRate(Baud)
  524.                   end (* baudsym *);
  525.         disksym : begin
  526.                   if value in [4,5,9] then
  527.                     begin
  528.                     disk := ' ';
  529.                     disk[1] := chr(ord('0')+value);
  530.                     disk := concat('#',disk);
  531.                     disk := concat(disk,':')
  532.                     end
  533.                    else
  534.                      writeln('Drive does not exist ')
  535.                   end (* disksym *)
  536.  
  537.       end; (* case *)
  538.   end; (* set_parms *)
  539.  
  540. procedure initialize;
  541.  
  542. var ch: char;
  543.  
  544.   begin
  545.     pad := mypad;
  546.     padchar := chr(mypchar);
  547.     eol := chr(my_eol);
  548.     esc_char := chr(my_esc);
  549.  
  550.     quote := my_quote;
  551.     bquote := my_bquote;
  552.     ctlset := [chr(1)..chr(31),chr(del),quote,bquote];
  553.     TranState := TxtFile;
  554.     TimInt := My_Time;
  555.  
  556.     half_duplex := false;
  557.     debug := false;
  558.     debnext:=0;
  559.     fwarn := false;
  560.     spsiz := max_pack;
  561.     rpsiz := max_pack;
  562.     n := 0;
  563.     parity := nopar;
  564.     initvocab;
  565.     fill_parity_array;
  566.     ibm := false;
  567.     xon := chr(17);
  568.     {bufpos := 1;}
  569.     initM;
  570.     Baud := 1200;
  571.  
  572.     FileInit;
  573.     value := 0;
  574.     disk  := '#5:'
  575.   end; (* initialize *)
  576.  
  577. procedure closeup;
  578.  
  579.   begin
  580.     writeln(chr(ff){clearscreen});
  581.   end; (* closeup *)
  582.  
  583.   begin (* kermit *)
  584.     initialize;
  585.     { Load in the microcode }
  586.     OVLYLOAD('HANDLE');
  587.  
  588.     repeat
  589.         write('Kermit-UCSD> ');
  590.         readstr(terminal,line);
  591.         case parse of
  592.             unconfirmed: writeln('Unconfirmed');
  593.             parm_expected: writeln('Parameter expected');
  594.             ambiguous: writeln('Ambiguous');
  595.             unrec: writeln('Unrecognized command');
  596.             fn_expected: writeln('File name expected');
  597.             ch_expected: writeln('Single character expected');
  598.             null: case verb of
  599.                       consym: connect;
  600.                       helpsym: help;
  601.                       Loadsym: begin
  602.                                uppercase(filename);
  603.                                LoadIm(filename)
  604.                                end;
  605.                       recsym: begin
  606.                               recsw(rec_ok);
  607.                               gotoxy(0,debugline);
  608.                               write(chr(bell));
  609.                               if rec_ok then
  610.                                   writeln('successful receive')
  611.                               else
  612.                                   writeln('unsuccessful receive');
  613.                               gotoxy(0,promptline);
  614.                               end; (* recsym *)
  615.                       sendsym: begin
  616.                                uppercase(filename);
  617.                                sendsw(send_ok);
  618.                                gotoxy(0,debugline);
  619.                                write(chr(bell));
  620.                                if send_ok then
  621.                                    writeln('successful send')
  622.                                else
  623.                                    writeln('unsuccessful send');
  624.                                (*$I-*) (* set i/o checking off *)
  625.                                closeF(filename,False);
  626.                                (*$I+*) (* set i/o checking back on *)
  627.                                gotoxy(0,promptline);
  628.                                end; (* sendsym *)
  629.                       delsym: begin
  630.                               uppercase(filename);
  631.                               vol := ord(disk[2]) - ord('0');
  632.                               Delfile(filename,vol)
  633.                               end; (* delsym *)
  634.                       setsym: set_parms;
  635.                      transym: begin
  636.                               if noun = Typesym then
  637.                                 case adj of
  638.                                   binsym   : TranState := CodeFile;
  639.                                   datasym  : TranState := BinFile;
  640.                                   textsym  : TranState := TxtFile;
  641.                                   imagesym : TranState := ImgFile;
  642.                                   end
  643.                                 else
  644.                                   write(Bell)
  645.                               end;
  646.                       show_sym: show_parms;
  647.                       dirsym : begin
  648.                                vol := ord(disk[2]) - ord('0');
  649.                                if vol in [9,10] then
  650.                                  writeln('Cannot DIR a Winchester')
  651.                                 else
  652.                                   PrintNames(vol,value)
  653.                                end (* dirsym *)
  654.                   end; (* case verb *)
  655.         end; (* case parse *)
  656.         { unitclear(1); }(* clear any trash in input *)
  657.         { unitclear(2); } (* Don't clear the screen ! *)
  658.      until (verb = exitsym) or (verb = quitsym);
  659.      closeup
  660.    end.(* kermit *)
  661.  
  662.