MaxZ80 - Chapter 9

A program that copies a file needs to know the name of the source file
and optionally the name to give the copy and whether to overwrite an
existing target. CP/M uses the 128 bytes of memory starting at hex
location 80 for several purposes. This "Transient Buffer Area" (TBA)
provides a place to store what is typed after a command. CP/M puts the
tail of your command into the TBA.

MOTYL.BAS and CMD.MAC (Chapter 3 and 4) rely on the TBA. Here are two
lines of code from CMD.MAC.

 LD A,(0080H)     ;COMMAND TAIL LENGTH IN A
 LD HL,0081H

CP/M figures out the number of characters in your command tail and
puts this number into location hex 80. From hex 81 up to hex FF, CP/M
puts a copy of the command tail itself. The Z80 microprocessor's
registers are addressed in assembly language with the letters A, F,
B, C, D, E, H and L. Certain pairs of registers can be used together,
which is useful when you need to track 16 bit addresses. HL is the H
and L register pair. The above two lines are storing the length in
register A and the address that holds the tail itself in the HL
register pair.

Joe Wright, who was mentioned in Chapter 7 when we talked about
NZ-TOOL.DEF, NZ-TOOL.BOX, CHICKEN.PAS and the speed field in the
environment record, wrote CPY.PAS, a file copy program. He used Turbo
Pascal 3.0, which has a builtin ParamStr() function. ParamStr(0)
returns the name of the command, ParamStr(1) returns the first element
in the tail of the command, ParamStr(2) returns the second, etc. The
builtin ParamStr() parses what it finds in the Transient Buffer Area.

Turbo Pascal 2.0, which is what we use here, does not have a builtin
ParamStr() function, so I wrote one. When I issued the command

CPY EBC:PARAMSTB.INC RAMDISK:PTB.INC

which should copy the file PARAMSTB.INC to the RAMDISK and name it
PTB.INC, it announced

 Copying A6:EBC PARAMSTB.INC to D1:RAMDISK PTB.IC:

This was due to the fact that the above command tail is longer than 32
characters. Turbo Pascal uses the bytes in the TBA from offset 32 on
for its own purposes and this was causing the filename corruption.

Z-System supports a "Multiple Command Line Buffer." Commands are
separated with a semicolon. The Multiple Command Line Buffer is like
the TBA, but it's about twice as long and is used only for command
line tracking.

I rewrote ParamStr() to use the Z-System segment that supports the
Multiple Command Line Buffer (see PARAMSTR.INC). It uses the ENVREC
record structure and the variable Z3EADR Absolute $0109 which is a
pointer to this structure. Although ParamStr() was later revised to
use a different method than what we show below, it is still worth
mentioning the older approach.

MCLPTR = ^MCLREC;
MCLREC =
  Record
     NXTCHR  : Integer;
     MCLMAX  : Byte;
     MCL     : String[203];
  End;

The first three bytes of this structure are overhead to track the
address of the first character in the next command to run (NXTCHR) and
to hold the size of the maximum number of characters allowed.

Function GETMCL:Integer;   { get multiple command line }
Begin getmcl := Ord(z3eadr^.z3cl) End;

Function ParamStr(i:integer):str80;
Var
  j,j1,lcmd,lc,p      : Integer;
  whatsleft           : str80;
  cl                  : mclptr;  { from nz-tool.box }
Begin
  cl := Ptr(getmcl);  { point to multiple command line, }
  { using nz-tool.box }
  { determine end of command and its offset }
  { in multiple command line } 
  lc := cl^.nxtchr - 1; j := lc - Ord(cl) - 3; lcmd := 0;
  While (cl^.mcl[j]<>';') And (j<>0) Do Begin { back up to start of command }
   j:=j-1;lcmd:=lcmd+1;End;
  { load work string with it }
  For j1:=j+1 To j+1+lcmd-1 Do
    whatsleft[j1-(j+1)+1]:=cl^.mcl[j1];
  whatsleft[0]:=Chr(lcmd);
  p:=Pos(' ',whatsleft);                   { locate first space }
  If (i>0) And (p=0) Then whatsleft:='';
  While (i<>0) And (p<>0) Do Begin         { grab the ith parm }
    i:=i-1;                                { decrement parm counter }
    Delete(whatsleft,1,p);                 { lop off the leading parm }
    p:=Pos(' ',whatsleft);                 { locate next space }
  End;
  If (i>0) And (p=0) Then current := '';
  If p=0 Then {if at end, use what's left}
    ParamStr := current
  Else {what's at the beginning of what's left}
    ParamStr := Copy(current,1,p-1);
End;

CPY was written to demonstrate how to write programs in a high level
language and take advantage of Z-System. It makes no attempt to be
both CP/M and Z-System compatible, unlike other programs which we'll
encounter later. It exits gracefully if Z-System is not detected by
invoking z3exist (see Chapter 7).

Begin {ning of CPY.PAS}
 If not z3exist Then Begin Write('Z3 not found.',cr,lf); Goto 100; End;

To test the behaviour of CPY under CP/M, you may issue the command

NZCPM

which will exit from Z-System and put you in a CP/M environment, or,
more accurately, a "ZCPR1" environment. To get to drive A user area 1,
type

A1:

Then type

CPY

To get back to Z-System, type Ctrl-Break and then select the B option
(reBoot).