Subversion Repositories SvarDOS

Rev

Rev 962 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/* This file is part of the SvarCOM project and is published under the terms
 * of the MIT license.
 *
 * Copyright (C) 2021-2022 Mateusz Viste
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/*
 * goto label
 *
 * if label does not exist in the currently processed batch file, then a error
 * "Label not found" is displayed and the batch file is aborted. Any parent
 * batches are ALSO aborted. Same behavior if called without any parameter.
 *
 * if called outside of a batch file, this command has no effect (and does not
 * display any error, even when called without any argument).
 *
 * it reacts to /? by outputing its help screen
 *
 * only 1st argument is processed, any extra arguments are ignored (even /?).
 *
 * Labels can be written as:
 * :LABEL
 *    :LABEL
 *   :  LABEL
 *
 * A label can also be followed by one or more space or tabs, followed by
 * anything. The label is parsed only to the first space, tab, end of line or
 * end of file. Hence this would be perfectly ok:
 *
 * :LABEL this is a comment
 *
 * Labels are searched in the batch file from top to bottom and first match
 * is jumped to. Matching labels is case-insensitive (ie. LABEL == LaBeL)
 */


static void goto_close_dos_handle(unsigned short fhandle) {
  _asm {
    push bx

    mov ah, 0x3e
    mov bx, fhandle
    int 0x21

    pop bx
  }
}


static enum cmd_result cmd_goto(struct cmd_funcparam *p) {
  char *buff = NULL;
  const char *label;
  unsigned short bufflen = 0;
  unsigned short fhandle = 0;
  unsigned short doserr = 0;
  unsigned short batname_seg;
  unsigned short batname_off;
  unsigned char eof_reached = 0;
  unsigned short i;

  /* help? reacts only to /? being passed as FIRST argument */
  if ((p->argc > 0) && imatch(p->argv[0], "/?")) {
    nls_outputnl(17,0); /* "Directs batch processing to a labelled line in the batch program." */
    outputnl("");
    nls_outputnl(17,1); /* "GOTO LABEL" */
    outputnl("");
    nls_outputnl(17,2); /* "LABEL specifies a text string used in the batch program as a label." */
    outputnl("");
    nls_outputnl(17,3); /* "A label is on a line by itself and must be preceded by a colon." */
    return(CMD_OK);
  }

  /* not inside a batch file? not given any argument? then do nothing */
  if ((p->rmod->bat == NULL) || (p->argc == 0)) return(CMD_OK);

  /* label is in first arg */
  label = p->argv[0];

  /* open batch file (read-only) */
  batname_seg = FP_SEG(p->rmod->bat->fname);
  batname_off = FP_OFF(p->rmod->bat->fname);
  _asm {
    push bx
    push dx

    mov ax, batname_seg
    push ds /* save ds */
    mov ds, ax
    mov dx, batname_off
    mov ax, 0x3d00
    int 0x21    /* handle in ax on success */
    pop ds
    jnc OPEN_SUCCESS
    mov doserr, ax

    OPEN_SUCCESS:
    mov fhandle, ax /* save file handle */

    pop dx
    pop bx
  }

  /* file open failed? */
  if (doserr != 0) {
    nls_outputnl_doserr(doserr);
    return(CMD_FAIL);
  }

  /* reset the rmod bat counter since I will scan all lines from top to bottom */
  p->rmod->bat->nextline = 0; /* remember this is a byte offset, not a line number */

  /* read bat file line by line until label is found or EOF */
  for (;;) {

    /* move any existing data to the start of the buffer */
    if (bufflen > 0) memmove(p->BUFFER, buff, bufflen);

    buff = p->BUFFER; /* assumption: must be big enough to hold 2 sectors (2 * 512) */

    /* if buffer has less than 512b then load it with another sector (unless eof) */
    if ((eof_reached == 0) && (bufflen < 512)) {
      /* load 512b of data into buffer */
      _asm {
        push ax
        push bx
        push cx
        push dx
        pushf

        mov ah, 0x3f    /* read from file handle */
        mov bx, fhandle /* file handle where to read from */
        mov cx, 512     /* read 512 bytes (one sector) */
        mov dx, buff    /* target buffer */
        add dx, bufflen /* data must follow existing pending data */
        int 0x21        /* CF clear on success and AX=number of bytes read */
        /* error? */
        jnc READ_OK
        mov doserr, ax
        READ_OK:
        add bufflen, ax
        /* set eof if amount of bytes read is shorter than cx */
        cmp ax, cx
        je EOF_NOT_REACHED
        mov eof_reached, byte ptr 1
        EOF_NOT_REACHED:

        popf
        pop dx
        pop cx
        pop bx
        pop ax
      }

      /* on error close the file and quit */
      if (doserr != 0) {
        goto_close_dos_handle(fhandle);
        nls_outputnl_doserr(doserr);
        return(CMD_FAIL);
      }
    }

    /* advance buffer to first non-space/non-tab/non-CR/non-LF */
    while (bufflen > 0) {
      if ((*buff != ' ') && (*buff != '\t') && (*buff != '\r') && (*buff != '\n')) break;
      bufflen--;
      buff++;
      p->rmod->bat->nextline++;
    }

    /* if the line does not start with a colon, then jump to next line */
    if ((bufflen > 0) && (*buff != ':')) {
      while ((bufflen > 0) && (*buff != '\n')) {
        bufflen--;
        buff++;
        p->rmod->bat->nextline++;
      }
    }

    /* refill buffer if needed */
    if ((bufflen < 512) && (eof_reached == 0)) continue;

    /* eof? */
    if (bufflen == 0) break;

    /* skip the colon */
    if (*buff == ':') {
      bufflen--;
      buff++;
      p->rmod->bat->nextline++;
    }

    /* skip any leading white spaces (space or tab) */
    while (bufflen > 0) {
      if ((*buff != ' ') && (*buff != '\t')) break;
      bufflen--;
      buff++;
      p->rmod->bat->nextline++;
    }

    /* read the in-file label and compare it with what's in the label buff */
    for (i = 0;; i++) {
      /* if end of label then check if it is also end of in-file label (ends with space, tab, \r or \n) */
      if ((i == bufflen) || (buff[i] == ' ') || (buff[i] == '\t') || (buff[i] == '\r') || (buff[i] == '\n')) {
        if (label[i] == 0) {
          /* match found -> close file, skip to end of line and quit */
          while (bufflen > 0) {
            bufflen--;
            buff++;
            p->rmod->bat->nextline++;
            if (*buff == '\n') break;
          }
          goto_close_dos_handle(fhandle);
          return(CMD_OK);
        }
        break;
      }
      /* end of label = mismatch */
      if (label[i] == 0) break;
      /* case-insensitive comparison */
      if ((label[i] & 0xDF) != (buff[i] & 0xDF)) break;
    }

    /* no match, move forward to end of line and repeat */
    while ((bufflen > 0) && (*buff != '\n')) {
      bufflen--;
      buff++;
      p->rmod->bat->nextline++;
    }
  }

  /* close the batch file handle */
  goto_close_dos_handle(fhandle);

  /* label not found, display error message and abort all batch scripts */
  nls_outputnl(17, 10); /* "Label not found" */
  rmod_free_bat_llist(p->rmod);

  /* restore echo flag as it was before running the (first) bat file */
  p->rmod->flags &= ~FLAG_ECHOFLAG;
  if (p->rmod->flags & FLAG_ECHO_BEFORE_BAT) p->rmod->flags |= FLAG_ECHOFLAG;

  return(CMD_FAIL);
}