$title('File Concatenation')
concat:
do;

$include (copyrt.lit)

  declare true literally '0FFFFH';
  declare false literally '0';
  declare forever literally 'while true';
  declare boolean literally 'byte';

  declare start label;
  declare jmp$to$start structure (
    jmp$instr byte,
    jmp$location address ) data  (
    0C3H,
    .start-3);

  mon1:
    procedure (func,info) external;
      declare func byte;
      declare info address;
    end mon1;

  mon2:
    procedure (func,info) byte external;
      declare func byte;
      declare info address;
    end mon2;

  declare fcb (1) byte external;
  declare fcb16 (1) byte external;
  declare tbuff (1) byte external;

  /**************************************
   *                                    *
   *       B D O S   Externals          *
   *                                    *
   **************************************/

  system$reset:
    procedure;
      call mon1 (0,0);
    end system$reset;

  read$console:
    procedure byte;
      return mon2 (1,0);
    end read$console;

  write$console:
    procedure (char);
      declare char byte;
      call mon1 (2,char);
    end write$console;

  print$console$buffer:
    procedure (buffer$address);
      declare buffer$address address;
      call mon1 (9,buffer$address);
    end print$console$buffer;

  open$file:
    procedure (fcb$address) byte;
      declare fcb$address address;
      return mon2 (15,fcb$address);
    end open$file;

  close$file:
    procedure (fcb$address) byte;
      declare fcb$address address;
      return mon2 (16,fcb$address);
    end close$file;

  delete$file:
    procedure (fcb$address);
      declare fcb$address address;
      call mon1 (19,fcb$address);
    end delete$file;

  read$record:
    procedure (fcb$address) byte;
      declare fcb$address address;
      return mon2 (20,fcb$address);
    end read$record;

  write$record:
    procedure (fcb$address) byte;
      declare fcb$address address;
      return mon2 (21,fcb$address);
    end write$record;

  create$file:
    procedure (fcb$address) byte;
      declare fcb$address address;
      return mon2 (22,fcb$address);
    end create$file;

  set$DMA$address:
    procedure (DMA$address);
      declare DMA$address address;
      call mon1 (26,DMA$address);
    end set$DMA$address;
  
  crlf:
    procedure;
      call write$console (0dh);
      call write$console (0ah);
    end crlf;


  parse:
    procedure (pcb$adr) address;
    declare pcb$adr address;
    declare pcb based pcb$adr structure (
      filename$adr address,
      fcb$adr address      );
    declare pcb$filename$adr address;
    declare pcb$fcb$adr address;
    declare filename based pcb$filename$adr (1) byte;
    declare fcb based pcb$fcb$adr (1) byte;
  
    declare
      /* return conditions */
      endline literally '00000H',
      badfile literally '0FFFFH',
      /* useful literals */
      disk    literally 'fcb(0)',
      fcbname literally '8',  /* end of name */
      fcbtype literally '11', /* end of type field */
      fcbsize literally '16'; /* partial size of fcb */
  
    declare char byte; /* global temp for current char */
    declare fnp  byte; /* index into file name buffer */
    declare fnlen byte;
  
    gnctran:
      procedure(b) byte;
        declare b byte;
  
        if b < ' ' then return 0dh; /* all non-graphics */
        /* translate alpha to upper case */
        if b >= 'a' and b <= 'z' then
          b = b and 101$1111b; /* upper case */
        return b;
      end gnctran;
  
    gnc:
      procedure;
  
        char = gnctran(filename(fnp := fnp + 1));
      end gnc;
  
    delimiter:
      procedure byte;
        declare i byte;
        declare del(*) byte data
          (0dh,' =.:<>_[],');
  
        do i = 0 to last(del);
          if char = del(i) then return true;
        end;
        return false;
      end delimiter;
  
    putchar:
      procedure;
  
        fcb(fnlen:=fnlen+1) = char;
        /* can check here for ambig ref's "char = '?'" */
      end putchar;
  
    fillq:
      procedure(len);
      /* fill current name or type with question marks */
  
        declare len byte;
  
        char = '?'; /* question mark */
        do while fnlen < len; 
          call putchar;
        end;
      end fillq;
  
    /* initialize local bases */
    pcb$filename$adr = pcb.filename$adr;
    pcb$fcb$adr      = pcb.fcb$adr;
    /* initialize file control block to empty */
    char  = ' ';
    fnlen  = 0;
    fnp   = -1;
    do while fnlen < fcbsize-1;
      if fnlen = fcbtype then char = 0;
      call putchar;
    end;
    disk = 0;
    /* scan next name */
    do forever;
    /* deblank command buffer */
      call gnc;
      do while char = ' ';
        call gnc;
      end;
      if delimiter then return badfile;
      fnlen = 0;
      do while not delimiter;
        if fnlen >= fcbname then /* error, file name too long */
          return badfile;
        if char = '*' then call fillq(fcbname); else call putchar;
        call gnc;
      end;
  
      /* check for disk name */
      if char = ':' then
      do;
        if not (disk = 0 and fnlen = 1) then
          return badfile;
        /* must be a disk name */
        if (disk := fcb(1) - 'A' + 1) > 26
          /* invalid disk name */
          then return badfile;
          /* valid disk name replace space in name */
          else fcb(fnlen) = ' ';
      end;
      else
      do;
        /* char is not ':', so file name is set. scan remainder */
        /* at least one char scanned */
        fnlen = fcbname;
        if char = '.' then /* scan file type */
        do;
          call gnc;
          do while not delimiter;
            if fnlen >= fcbtype then
              /* error, type field too long */
              return badfile;
            if char = '*'
              then call fillq(fcbtype);
              else call putchar;
              call gnc;
          end;
        end;
  
        if char = 0dh
          then return endline;
          else return .filename(fnp);
      end;
    end; /* of forever */
  end parse;

  declare pcb structure (
    filename$adr address,
    fcb$adr address      );
  declare nxt$chr$adr address;
  declare old$nxt$chr$adr address at (.pcb.filename$adr);
  declare char byte;
  declare ret byte at (.char);
  declare delim based nxt$chr$adr byte;
  declare fcbin (33) byte;
  declare fcbout (33) byte initial (
    0,'        ','   ',0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0);
  declare default$fcb (33) byte data (
    0,'        ','   ',0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0);
  declare nmb$sect literally '32';
  declare buffer (nmb$sect) structure (
    record (128) byte);


  setup$output$file:
    procedure;
      pcb.filename$adr = .tbuff(1);
      pcb.fcb$adr = .fcbout;
      nxt$chr$adr = parse (.pcb);
      if delim <> '=' then
      do;
        if nxt$chr$adr = 0 then
        do;
          call print$console$buffer (.(
            'No input files specified','$'));
          go to error;
        end;
        if nxt$chr$adr = 0ffffh then
        do;
          call print$console$buffer (.(
            'Bad output file name','$'));
          go to error;
        end;
        call print$console$buffer (.(
          'A ''='' delimeter expected after output file name','$'));
        go to error;
      end;
      call set$DMA$address (.buffer);
      if open$file (.fcbout) <> 255 then
      do;
        call print$console$buffer (.(
          'Destination file exists, delete (Y/N) ? ','$'));
        char = read$console;
        if (char <> 'y') and (char <> 'Y')
          then call system$reset;
        call crlf;
        call delete$file (.fcbout);
      end;
      if create$file (.fcbout) = 255 then
      do;
        call print$console$buffer (.(
          'Directory full','$'));
        call system$reset;
      end;
    end setup$output$file;

  setup$input$file:
    procedure;
      pcb.filename$adr = nxt$chr$adr + 1;
      pcb.fcb$adr = .fcbin;
      call move (33,.default$fcb,.fcbin);
      if (nxt$chr$adr := parse (.pcb)) = 0ffffh then
      do;
        call print$console$buffer (.(
          'Bad input file name','$'));
        go to error;
      end;
      call set$DMA$address (.buffer);
      if open$file (.fcbin) = 255 then
      do;
        call print$console$buffer (.(
          'No such input file','$'));
        go to error;
      end;
    end setup$input$file;

  copy$file:
    procedure;
      declare (i,cnt) byte;
      declare (ok,more$input) boolean;

      more$input = true;
      do while more$input;
        cnt = 0;
        ok = true;
        do while ok;
          call set$DMA$address (.buffer((cnt := cnt+1)-1));
          ok = (read$record (.fcbin) = 0) and
               (cnt <> nmb$sect);
        end;
        if (more$input := (cnt = nmb$sect))
          then cnt = cnt - 1;
          else cnt = cnt - 2;
        do i = 0 to cnt;
          call set$DMA$address (.buffer(i));
          if write$record (.fcbout) <> 0 then
          do;
            call print$console$buffer (.(
              'Disk write error','$'));
            call system$reset;
          end;
        end;
      end;
    end copy$file;

  /*
      F i l e   C o n c a t e n a t i o n
  */

start:
  call setup$output$file;
  do forever;
    call setup$input$file;
    call copy$file;
    if nxt$chr$adr = 0 then
    do;
      ret = close$file (.fcbout);
      call system$reset;
    end;
  end;
  
error:
  call crlf;
  tbuff(tbuff(0)+1) = '$';
  call print$console$buffer (old$nxt$chr$adr);
  call system$reset;

end concat;
