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