home *** CD-ROM | disk | FTP | other *** search
/ CP/M / CPM_CDROM.iso / jsage / zsus / tcj / tcj38bmm.ws < prev    next >
Encoding:
Text File  |  1994-09-02  |  25.3 KB  |  561 lines

  1.                                 Advanced CP/M
  2.                         Batch Processing and a New ZEX
  3.                              by Bridger Mitchell
  4.  
  5.                         The Computer Journal, Issue 38
  6.                           Reproduced with permission
  7.                            of author and publisher
  8.  
  9.  
  10. .h1 More Environmental Programming
  11.  
  12. Two columns ago I went out on a limb and suggested a number of
  13. guidelines for environmentally-sensitive programming -- how to write
  14. programs that were aware of their host computer's environment, took
  15. care to avoid damaging the system, and allowed you to exploit advanced
  16. features if they were supported.  A number of you have continued the
  17. discussion by mail and on the Z-Nodes.  There are several areas for
  18. further fruitful exploration.  I'll touch on one or two this time,
  19. and I imagine we can look forward to further exchanges.
  20.  
  21. Lee Hart asks if there are ways to detect other CPU's (in addition to
  22. the HD64180/Z180 and Z280) that support the Z80 instruction set,
  23. including the Ferranti, SGS and NEC chips.  It would be useful for some
  24. programs to know, for example, that they are running on a pc.  If you
  25. can shed some light on this, please do!
  26.  
  27. .h2 Preserving the Z80 Registers
  28.  
  29. I (and others) have argued that an environmentally-conscious BIOS will
  30. preserve any non-8080 registers that it uses, and restore their values
  31. before returning from any BIOS call.  Al Hawley and other CP/M
  32. veterans recalled that Zilog's early data sheets for the Z80 suggested
  33. using the alternate registers to switch contexts in servicing an
  34. interrupt.
  35.  
  36. In an embedded application, using the alternate registers in a service
  37. routine is entirely appropriate and efficient, because the
  38. designer knows exactly what tasks will use which registers.  But it's
  39. another matter altogether to use the registers (without preserving
  40. them) in an operating system, which is intended to run an _arbitrary_
  41. task that may very well itself actively use the same registers.
  42.  
  43. Unfortunately, several BIOSes were written along the Zilog guidelines,
  44. and some authors used the register-swap instructions to save a few
  45. bytes.  As a result, erratic bugs continue to pop up.  Recently, a few
  46. users have encountered them when installing ZSDOS, which itself
  47. preserves and uses the index registers.
  48.  
  49. Cam Cotrill has come up with a portable "fix" for part of this
  50. defect.  It takes the form of a special NZ-COM BIOS segment that saves
  51. all of the Z80 registers before every BIOS call and then restores them
  52. just before returning.  Because NZ-COM allows the user to load a
  53. customized BIOS module -- in additon to command-processor,
  54. named-directory, and other segments -- and to adjust its size, it is
  55. èpossible to provide such a band-aid without knowing anything about the
  56. hardware features of the BIOS itself!  You can find this file in
  57. ZSNZBI12.LBR on one of the major Z-Nodes.
  58.  
  59. As ingenious as this solution is, it would be better still if it were
  60. unneeded.  And while it handles the BIOS that consumes registers in
  61. normal services, it cannot rectify the BIOS service routine that
  62. consumes a register for handling interrupts.  If you're writing a new
  63. BIOS, or have the source to your existing system, take care to
  64. preserve the index and alternate registers!
  65.  
  66. .h2 Interrupts
  67.  
  68. How should the environmentally-conscious programmer deal with
  69. interrupts?  First, a portable program can't use Z80 or 8080/8085
  70. interrupts, because it can't readily determine the availability of
  71. interrupt vectors for its own use, and the possible conflicts that
  72. could exist with other interrupts used by the system.  Therefore,
  73. programming interrupt-service routines falls in the province of
  74. writing hardware-specific BIOS extensions.
  75.  
  76. The relevant general-purpose guidelines must be limited to
  77. procedures for disabling and enabling interrupts.  It's rarely
  78. necessary to turn off interrupts, and the rule is: keep it short!
  79. Interrupts must be disabled whenever your code leaves the system in a
  80. state in which an arbitrary interrupt-service routine cannot execute
  81. correctly.  Keep the stack pointer and system addresses clearly in
  82. mind.
  83.  
  84. Why would you ever use SP for anything but a stack, anyway?  Well,
  85. it's sometimes a handy way to load a table of word into registers.
  86. Repeatedly pushing a constant can be the fastest method of
  87. initializing a segment of memory.  And several issues ago I described
  88. a code-relocation algorithm that used a similar trick to fetch,
  89. relocate, and store successive words of code in PRL format.
  90.  
  91. Interrupts should be disabled (with a DI instruction) just before
  92. changing the stack pointer to use it for data operations, and
  93. re-enabled (EI) as the instruction that immediately follows restoring
  94. the stack pointer to a valid stack.  If you don't turn off interrupts,
  95. and an interrupt occurs, then when the cpu catches the interrupt it
  96. will push the current program counter value onto your "stack",
  97. clobbering part of your data area.
  98.  
  99. In some applications it's necessary to change the BIOS or page 0
  100. vectors.  It's remotely possible that an interrupt service routine
  101. would use one of these vectors (but only if the BIOS is re-entrant).
  102. So, a fastidious guideline would use a DI before any multi-instruction
  103. code that changes a vector.
  104.  
  105. This code:
  106.  
  107.     ld (vector_address),de
  108. è
  109. is _atomic_ -- it changes everything necessary in a single executable
  110. instruction, one that cannot itself be interrupted.  However, using
  111. several instructions to storing the low and high bytes of the address,
  112. for example:
  113.  
  114.     ld    hl,vector_address
  115.     di
  116.     ld    (hl),e
  117.     inc    hl
  118.     ld    (hl),d
  119.     ei
  120.  
  121. is not atomic. While that sequence of instructions is executing, the
  122. state of the system BIOS vector is not well defined.  Without the DI
  123. instruction, if an interrupt occurs, its service routine could get an
  124. invalid address.
  125.  
  126. I have used the DI/EI instructions without apparent problems.  But
  127. when I wrote BackGrounder ii I wanted to ensure wide portability, and
  128. I worried about a BIOS that did _not_ use interrupts and might
  129. behave strangely if they were suddenly enabled.  This might seem
  130. paranoid, and it's probably the case that a number of other programs
  131. would not run on such a system.  But I was recalling an early
  132. experience of trying to boot an 8085-based S-100 Compupro system in
  133. which the interrupt lines had been left floating.  When the first EI
  134. was executed in the cold-boot code, one of the devices triggered an
  135. interrupt, before the BIOS's service routine had been installed.
  136.  
  137. The routines in Figure 1 can be called in place of inline DI and EI
  138. instructions to disable interrupts and conditionally re-enable them.
  139. As far as I have been able to determine, this test of the Z80's
  140. interrupt status works correctly.  However, I have heard reports that
  141. some "Z80" cpu's do not report this status correctly.  I would welcome
  142. any reliable information on this point.
  143.  
  144.           Figure 1. Disable and Re-enable Interrupts
  145. ;
  146. ; Save interrupt status and disable interrupts
  147. ;
  148. disable_int:
  149.     push    af        ; save registers
  150.     push    bc
  151.     ld    a,i        ; get interrupt status to A
  152.     push    af
  153.     pop    bc        ; and into C
  154.     ld    a,c        ; and save it
  155.     ld    (intflag),a
  156.     pop    bc
  157.     pop    af
  158.     di            ; disable non-maskable interrupts
  159.     ret
  160. è;
  161. ; If interrupts were previously enabled, 
  162. ; re-enable them.
  163. ;
  164. enable_int:
  165.     push    af        ; save register
  166.     ld    a,(intflag)    ; if interrupts
  167.     bit    2,a        ; .. were previously enabled
  168.     jr    z,1$
  169.     ei            ; ..re-enable them
  170. 1$:    pop    af
  171.     ret
  172.  
  173.     
  174.  
  175. .h1 Batch Processing
  176.  
  177. Batch processing is running a sequence of commands by submitting a
  178. single command to the operating system.  In the good old days, the
  179. computer operator submitted programs, on 80-column punched cards, to a
  180. desk-sized card reader.  Programs were batched together by stacking
  181. the card decks in a long metal tray.  You (or the operator) lugged the
  182. tray across the room, crossing your fingers that you didn't trip and
  183. spill everything on the floor.  Eventually, your job ran and after a
  184. seemingly endless wait, the printer disgorged interminible pages of
  185. digits, and you went back to debugging yet another core dump.  Then
  186. the cycle repeated....
  187.  
  188. CP/M's standard batch processor is the SUBMIT utility.  It takes a
  189. file of command lines, stored in a file of type SUB, and writes them
  190. to a temporary file.  The command processor detects this file and gets
  191. its commands from it, a line at a time, until it has completed the
  192. batch.  Then it once again gets its commands from the keyboard.
  193.  
  194. A submit file, or script, called TEST.SUB might look like this:
  195.  
  196.     cmd1 command_tail1
  197.     cmd2
  198.     cmd3 command_tail3
  199.  
  200. Your command
  201.  
  202.     SUBMIT TEST
  203.  
  204. would then cause the three commands to run in sequence.
  205.  
  206. This basic system works well for programs that require only
  207. command-line parameters for their input.  But when a program, say
  208. CMD1.COM, needs console input, the process stops in its tracks and
  209. waits for the user to type in the input.  Many times this is just what
  210. you want to occur -- the user needs to make a real-time decision, and
  211. enter data or a response.  Often, however, we want the _program input_
  212. to also be automated, so that it can be provided from the same script,
  213. èand the entire batch of jobs will run to completition unattended.
  214.  
  215. Digital Research, the authors of CP/M, attempted to provide this
  216. capability with the XSUB utility.  But it was an early attempt to
  217. write an RSX (resident system extension), it was buggy, and it proved
  218. incompatible with any other RSX.
  219.  
  220. A major step forward was the development of utilities that combined
  221. SUBMIT and XSUB processing, kept the script in memory inside the RSX
  222. for faster performance, and supplied a line editor so that short
  223. scripts could be typed in on-the-fly when needed.  EX.COM was one.
  224. Another was the In Memory Submit capability included in Morrow
  225. computers stored the script in banked memory on their CP/M computers.
  226.  
  227. .h2 ZEX
  228.  
  229. For the Z-System the batch processor has been ZEX -- the Z-System
  230. EXecutive input processor.  It evolved from EX, and has grown like
  231. topsy, with significant contributions from Rick Conn, Joe Wright, Jay
  232. Sage and others.  These increasingly elaborate versions provided for
  233. greater control over input, the ability to print messages while the
  234. script was running, simple looping, testing of command flow control,
  235. etc.
  236.  
  237. Yet ZEX never quite seemed housebroken, and the tireless Rick Charnes
  238. was always coming up with some new batch process that he couldn't
  239. quite get ZEX to perform.  Moreover, there was no ZEX for Z3PLUS
  240. systems.  And the hieroglyphics required to write a ZEX script always
  241. required relearning just when you needed a quick, automated process.
  242.  
  243. These warts, and conversations with Joe Wright and Jay Sage, the most
  244. recent revisors of ZEX, finally led me to take a fundamental look at
  245. this utility.  Although the code contained many notable advances, this
  246. was truly a "topsy" program, something that had been bandaged and
  247. remodeled many times.  So, in discussions with Jay, I decided that we
  248. need to rethink our objectives and design the program from the outside
  249. in.  This issue's column focuses on that design, leaving its
  250. implementation for another time.
  251.  
  252. .h2 What Should ZEX Be?
  253.  
  254. The easy part was how it should run.  The new ZEX should run on both
  255. CP/M 2.2 and CP/M Plus systems.  It should be compatible with existing
  256. RSX's.  It should be able to load and use RSX's as part of a script.
  257. And, perhaps, it should be able to invoke a second ZEX script.
  258.  
  259. These requirements would give us a single batch processor for all
  260. Z-Systems, and scripts that could be used on both CP/M 2.2 and CP/M
  261. Plus machines without change.  A script could be executed while an
  262. RSX, such as BackGrounder ii or DosDisk, is already in memory.  If
  263. needed, the script could load an RSX, for example one to filter
  264. printer output.
  265.  
  266. Preliminary goals for the script language seemed straightforward.  The
  267. èlanguage should allow a standard SUBMIT script to run identically.  It
  268. should use English-like directives, provide convenient, easily
  269. readable comments, and clearly distinguish between input for the
  270. command processor, input for programs, and messages and directives.
  271. Programs should run identically when the same commands appear in a
  272. script, or are typed in at the console.
  273.  
  274. This is starting to sound like the textbook-prescribed top-down design
  275. exercise.  As any real programmer knows, that would be a fairy tale,
  276. because it seems that all of us just _have to_ write some code, if
  277. only to check out an idea.
  278.  
  279. Well, writing code before the design is completed can indeed be
  280. productive -- the key thing is to avoid getting emeshed in the thicket
  281. of small details before the major skeleton of the project, and
  282. possible alternatives, have been sketched and evaluated.  So, while
  283. drafting and redrafting these preliminary specifications, I also found
  284. myself experimenting experimenting with the parsing code, rewriting,
  285. modularizing and consolidating several existing ZEX versions, and
  286. developing and testing the CP/M Plus interface.
  287.  
  288. What follows, then, is a still-in-process description of the evolving
  289. new ZEX, version 4.0.  Your comments and suggestions will be welcome
  290. and will surely improve it.  I expect ZEX to continue to evolve -- it
  291. will be easier to add features to the code now that it is more
  292. modular.  What will require effort is the systematic thought and
  293. testing of extensions to the language, to avoid unintended side
  294. effects and anomalous cases.
  295.  
  296.  
  297. .h1 The ZEX Script
  298.  
  299. ZEX is the Z-System batch-processing language.  ZEX.COM is the
  300. system tool that implements it.  Its purpose is to automate complex
  301. and repetitive tasks that require running a series of programs or
  302. entering keyboard input.
  303.  
  304. A ZEX _script_ is a text (ascii) file, or series of text lines entered
  305. interactively when ZEX is run.  The script file is conventionally
  306. given the filetype .ZEX, or sometimes .SUB, for convenience in
  307. identifying scripts in a directory.
  308.  
  309. A script typically consists of a series of _commands_ and their
  310. _command-tails_ that form the input to the ZCPR command processor.  In
  311. this form the script is equivalent to a CP/M SUBMIT script.  In
  312. addition, the script may contain _data_ for programs that would
  313. otherwise be entered from the console keyboard.  This feature is
  314. similar to, but more advanced than, the CP/M XSUB.
  315.  
  316. In addition, the ZEX script may contain a number of ZEX _directives_
  317. that provide for console messages, waiting for a keypress, ringing
  318. the bell, testing command flow control, and so forth.
  319.  
  320. èZEX explicitly distinguishes between _command-processor_ input
  321. and _program_ input.  Normally, ZEX gets all command-line input from
  322. the _script_ and all program input from the _console_.  (This is
  323. exactly what SUBMIT does; a SUBMIT script will run identically under
  324. ZEX.)  But the input sources can be switched by directives.  For
  325. example, all program input can also be obtained from the script, so
  326. that the complete script will run unattended from start to finish.
  327.  
  328. In reading this, keep clearly in mind the difference between a script
  329. file, typed input, and console output.  A file is a stream of bytes,
  330. broken into lines by a _pair_ of bytes: <CR> followed by <LF>.
  331. Similarly, when a line of text is output to the screen, it ends with a
  332. <CR> (which moves the cursor to the first column of the current line),
  333. and a <LF> (which moves it down one line).  However, when a line is
  334. entered from the keyboard it is terminated by a <CR> only.  Thus,
  335. in a script you should designate the end of a line of program _input_
  336. with a |CR|.  For a multi-line message to the screen, terminate each
  337. message line with |CRLF|.
  338.  
  339. .h1 The ZEX Language
  340.  
  341. The ZEX script consists of lines of ascii text, each terminated by a
  342. <CR><LF> pair.  (Create the script with a text editor in ascii
  343. (non-document) mode, or just type it into ZEX when prompted.)  A
  344. number of reserved words, called _directives_, control the various
  345. features.  Each directive begins and ends with the verticule character
  346. '|'.  The directives may be entered in upper, lower, or mixed case; we
  347. use uppercase here to make them stand out.  All script input that is
  348. to be sent to a program begins with a '<' character in the first
  349. column; all other lines are sent to the command processor or, when
  350. specifically directed, are messages sent directly to the console
  351. output.
  352.  
  353. .h2 Command-processor input:
  354.  
  355.  - is any line of the script that doesn't begin with '<'
  356.  - is case-independent.
  357.  - spaces and tabs at the beginning of a line are ignored
  358.  - is <CR><LF> sensitive.  The end of a script line is the end of
  359.    one command line.  Use the |JOIN| directive at the end of a script
  360.    line to continue the same command line on a second script line.
  361.    (The <LF> is always discarded).
  362.  - use "|NUL| " or |SPACE| to insert a space preceeding a command, or
  363.    after a command and before a comment.
  364.  - begin each command (or set of multiple commands, separated by
  365.    semicolons) on a new script line, optionally preceeded or followed by
  366.    whitespace.
  367.  - all whitespace immediately preceding a |JOIN|, and all characters
  368.    on the line following |JOIN| are discarded.
  369.  
  370. .h2 Program input:
  371.  
  372.  - is normally obtained from the console.
  373.  - begin each line of program input with a '<' in the first column.
  374.  - input is case-sensitive.
  375. è - data from the script ignores the <CR><LF> at the end of
  376.    a script line.  A single line of program input may spread over
  377.    several script lines.
  378.  - use |CR| to supply a carriage-return.
  379.  - use |LF| for linefeed and |CRLF| for carriage-return-linefeed.
  380.  - if the program requests more input than is supplied in the script,
  381.    the remaining input is obtained from the console
  382.  - use |WATCHFOR string| to take further input from the console, until
  383.    the program sends "string" to the console output, then resume
  384.    input from the script
  385.  
  386.  
  387. .h2 Both:
  388.  
  389.  - use |SAY| to begin text to be printed on the console.
  390.  - use |END SAY| to terminate that text
  391.  - use |UNTIL ~| to take further input from the console,
  392.    until a keyboard ~ is entered.  The '~' character may be any
  393.    character; pick one that won't be needed in entering console input.
  394.  - use |UNTIL| to take further input from the console,
  395.    until a keyboard <CR> is entered.
  396.  
  397. .h2 Comments
  398.  
  399. A double semicolon ";;" designates the beginning of a comment.  The
  400. two semicolons, any immediately-preceding whitespace, and all text up to
  401. the end of that line of script are ignored.
  402.  
  403. A left brace '{ in the first column designates the beginning of a
  404. comment field; all text, on any number of lines, is ignored up to the
  405. first right brace '}'.
  406.  
  407.  
  408. .h2 Other Directives
  409.  
  410. Within a directive, a SPACE character is optional.  Thus, |IF TRUE|
  411. and |IFTRUE| have the identical effect.
  412.  
  413. |IF TRUE|    begin conditional script; do if command flow state is true
  414. |END IF|    end conditional script
  415. |IF FALSE|    begin conditional script; do if command flow state is false
  416. |RING|        ring console bell
  417. |WAIT|        wait until a <CR> is pressed
  418. |AGAIN|        repeat the entire ZEX script
  419. |ABORT|        terminate the script if the flow state is true
  420. |QUIET ON|    turn on the ZCPR quiet flag
  421. |QUIET OFF|    turn off the ZCPR quiet flag
  422. |CCPCMD ON|    turn on ZCPR (CCP) command prompt
  423. |CCPCMD OFF|    turn off ZCPR (CCP) command prompt
  424. |ZEXCMD ON|    turn on ZEX command prompt
  425. |ZEXCMD OFF|    turn off ZEX command prompt
  426. |NUL|        use to make following whitespace significant
  427. ||        same as |NUL|
  428. |SPACE|        one space character
  429.  
  430. è
  431. .h2 Parameters
  432.  
  433. ZEX (like SUBMIT) provides for formal parameters designated $0 $1 ...
  434. $9.  When ZEX is started with a command line such as:
  435.  
  436.     A> ZEX SCRIPT1 ARG1 ARG2 ARG3
  437.  
  438. then ZEX reads and compiles the SCRIPT1.ZEX file.  In the script,
  439. any "$0" will be replaced by "SCRIPT1", any "$1" is replaced by
  440. the "first" argument "ARG1", etc.
  441.  
  442. The script may define "default parameters" for the values $1 ... $9.
  443. To do so, enter the three characters "^$n" followed (with no space) by
  444. the nth default parameter.  When ZEX encounters a formal parameter in
  445. the script, it substitutes the command-line parameter, if there was one
  446. on the command line, and otherwise the corresponding default
  447. parameter, if it was defined.
  448.  
  449. Alternatively, you can define default parameters by entering
  450. "|n=param|", where 'n' is '1' to '9' and "param" is the default string
  451. (containing no whitespace).
  452.  
  453. .h2 Control characters
  454.  
  455. You enter a control character into the script by entering a caret '^'
  456. followed by the control-character letter/symbol.  For example, "^A"
  457. will enter a Control-A (01 hex).  Control-characters may be entered in
  458. upper or lower case.
  459.  
  460. .h2 Quotation
  461.  
  462. ZEX uses a number of characters in special ways: dollar-sign, caret,
  463. verticule, left and right curley braces, less-than sign, semicolon,
  464. (space, and carriage-return).  Sometimes we might want to include
  465. these characters as ordinary input, or as output in a screen message.
  466. For this, ZEX uses '$' as the _quotation character_.  (This is also
  467. called the _escape_ character, because it allows one to escape from
  468. the meaning reserved for a special character.)  "Quotation" means that
  469. the next character is to be taken literally; I use this term to avoid
  470. confusion with the control code 1B hex generated by the _escape key_.
  471.  
  472. If '$' is followed by any character other than the digits from '0' to
  473. '9', that character is taken literally.  Thus, if we want a caret in
  474. the text and not a control character, we use '$^'.  If we want a '<'
  475. in the first column of a line that is for the command processor and
  476. not for program input, then we use '$<' there instead.  And don't
  477. forget that if we want a '$' in our script, we must use '$$'.  There
  478. are some cases, like '$a', where the '$' is not necessary, but it can
  479. always be used.
  480.  
  481. To pass a ZEX directive to a program, or the command processor, use
  482. the quotation character with the verticule.  For example, to echo the
  483. string "|RING|", the zex script should be:
  484.  
  485. è        echo $|RING$|
  486.  
  487. .h2 Some examples
  488.  
  489. Figure 2 provides several examples of how the new script language
  490. should work.  You will note a number of differences from the current
  491. dialect used, for example, in Rick Charnes' article in this issue.
  492. And, no doubt, further improvements will emerge from your suggestions
  493. and the actual implementation of the new batch processor.
  494.  
  495.  
  496.             Figure 2.  ZEX Script Examples
  497.  
  498.  
  499. ZEX SCRIPT        INPUT SOURCE/EXECUTION SEQUENCE
  500.  
  501. cmd1    ;;comment    The CCP receives "cmd1<cr>".  The spaces before
  502.                 the comment are stripped, and the <cr> at the
  503.             end of the line is passed to the CCP.
  504.             The cmd1 program gets its input from the console.
  505.  
  506. cmd2 |UNTIL|        The CCP receives "cmd2 " and then gets additional
  507.             input from the console, including a <cr>.
  508.             The cmd2 program gets its input from the console.
  509. |SAY|ccp msg|ENDSAY|cmd3
  510.             When the CCP prompts for the next command,
  511.             "ccp msg" is printed on the console.  The CCP
  512.             then receives "cmd3<cr>"
  513. <text            The cmd3 program gets "textmore text<cr>new
  514. <more text|CR|        line of text"
  515. <new line of text    If the program requests more input, it comes from
  516.             the console.
  517.  
  518. cmd4 cmd4tail        The CCP receives "cmd4 cmd4tail<cr>"
  519. <|UNTIL~|        The cmd4 program receives console input until
  520. <text            the user types a '~'.  Then the program receives
  521.             "text"
  522.             If the program requests more input, it comes
  523.             from the console.  If the program doesn't use all
  524.             of the input, it is discarded.
  525.  
  526. cmd5 |UNTIL~| tail    The CCP receives "cmd5 " and then gets additional
  527.             input from the console, until the user types '~'.
  528.             The CCP then receives " tail<cr>".
  529.             The program receives input from the console.
  530.  
  531. |UNTIL|            The CCP receives a command line of input from the console.
  532.             The program receives input from the console.
  533.  
  534. |UNTIL|            The CCP receives a command line of input from the console.
  535. <|SAY|message|ENDSAY|    When the program first calls for console input,
  536. <text            "message" is printed on the console.  Then the
  537.             program receives "text".
  538. è            Additional program input is received from the console.
  539.  
  540.  
  541. cmd6            The CCP gets "cmd6<cr>"
  542. <|WATCHFORstring|    The cmd6 program gets input from the console, until
  543. <|SAY|message|ENDSAY|    the characters "string" appear at the console output.
  544. <text            Then "message" appears on the console output, and
  545.             the program gets "text".  Further input comes
  546.             from the console.
  547.             If "string" never appears, all of this is
  548.             discarded.
  549.  
  550. alias1            The CCP gets "alias1<cr>".  That program, a Z-System
  551.             alias, puts "cmd1;cmd2" into the multiple
  552.             command line buffer.  The CCP then obtains "cmd1" from mcl
  553. <|UNTIL~|        The cmd1 program gets any input from the
  554. <cmd2text        console.  If a '~' is typed, it gets "cmd2text".
  555.             If cmd1 does not request console input, or if
  556.             no '~' is typed, cmd1 finishes and the CCP then
  557.             obtains "cmd2" from mcl.  Assume this case.
  558.             The cmd2 program obtains input from the
  559.             console, until a '~' is typed.  Then it gets
  560.             "cmd2text".  Further input comes from the console
  561. cmd3            The CCP gets "cmd3<cr>".
  562. <text            The cmd3 program gets "text".  Further input
  563.             comes from the console.
  564.  
  565.  
  566. [This article was originally published in issue 38 of The Computer Journal,
  567. P.O. Box 12, South Plainfield, NJ 07080-0012 and is reproduced with the
  568. permission of the author and the publisher. Further reproduction for non-
  569. commercial purposes is authorized. This copyright notice must be retained.
  570. (c) Copyright 1989, 1991 Socrates Press and respective authors]
  571.