view examples/dsl/dsl.syn @ 16:f9e4689b837d

Some minor updates for 15 years later.
author David A. Holland
date Tue, 31 May 2022 01:45:26 -0400
parents 13d2b8934445
children
line wrap: on
line source

{                                           // C Prologue
/*****

 AnaGram Programming Examples

 A Dos Script Language

 Copyright 1993 Parsifal Software. All Rights Reserved.

 This software is provided 'as-is', without any express or implied
 warranty.  In no event will the authors be held liable for any damages
 arising from the use of this software.

 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:

 1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.
 2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.
 3. This notice may not be removed or altered from any source distribution.

*****/

#include "stack.h"
#include "charsink.h"
#include "strdict.h"
#include "array.h"
#include "symbol.h"
#include "query.h"

#ifdef __BCPLUSPLUS__
extern unsigned _stklen = 0x4000;                // set stack size
#endif


// Define stacks for temporary storage

stack <action_pointer>      as(25);              // Stack actions
stack <int>                 is(100);             // Stack string indices
stack <char *>              ps(1000,20);         // Stack parameter strings
stack <query_item>          qs(23);              // Stack query items


// Define data structures for symbol table

#define N_STRINGS 2000

string_accumulator          sa(64000U,500);
string_dictionary           sd(N_STRINGS);
array <symbol_table_entry>  st(N_STRINGS);

}                              // End of C Prologue


// Character Set Definitions

digit               = '0-9'
eof                 = 0 + ^Z
letter              = 'a-z' + 'A-Z' + '_'
not double quote    = ~eof - ('"' + '\\' + '\n')
not eol             = ~(eof + '\n')
not paren           = ~(eof + '(' + ')')
not single quote    = ~eof - ('\'' + '\\' + '\n')
operator            = '#' + '=' + '<' + '>' + '|'
punctuation         = '(' + ')' + '{' + '}' + '[' + ']' + '"' + '\n'
text char           = ~(eof + operator + white + punctuation + '@')
white               = ' ' + '\t' + '\v' + '\f' + '\r'


// Configuration Section

[
  // White space control
  disregard ws
  lexeme {literal, integer constant, string literal, paren string,
          character constant, eol, literals, name}
  distinguish lexemes

  // parser configuration
  pointer input
  context type = action_pointer
  distinguish keywords {letter + digit}
  parser file name = "#.cpp"
	far tables

  // Debugging options
  line numbers
  test file mask = "*.dsl"
]


//   White Space Definitions

ws
 -> white | comment

comment
 -> comment head, "*/"

comment head
 -> "/*"
 -> comment head, ~eof

// Comment out one of the following two productions to determine whether
// comments nest or not

comment head
 -> comment head, comment                   // comments nest

/*
comment
 -> comment head, comment                   // comments do not nest
*/

eol
 -> '\n'
 -> "//", not eol?..., '\n'                 // C++ style comments



// Script File Description

script file $
 -> [execution block | declaration | eol]..., eof


// Dos Command and Parameter Recognition

word
 -> paren string
 -> string literal
 -> integer variable:v                      =++sa << sd[v], lookup();
 -> string variable:v                       =++sa << sd[v], lookup();
 -> undeclared variable:v                   =++sa << sd[v], lookup();
 -> word, '[',                              !sa << '[';,
      parameter string, ']'                 =concat(sa) << ']', lookup();

string
 -> word
 -> string, '#', word                       =concat(sa);

parameter string
 -> param word
 -> parameter string, '#',
     param word                             =concat(sa);

literal
 -> text char:c                             =++sa << c;
 -> literal, text char:c                    =sa << c;

param word
 -> word
 -> action text                             =action_string();


// Gather, but do not execute, the text of an action block

action text
 -> action text head, '}'

action text head
 -> '{'                                     =as << CONTEXT;
 -> action text head, action word
 -> action text head, eol
 -> action text head, action text           ={action_pointer a; as >> a;}
 -> action text head, operator+'@'


action word
 -> paren string                            =--sa;
 -> string literal                          =--sa;
 -> literal                                 =--sa;
 -> action word, '[', action parameter string, ']'

action parameter string
 -> action param word
 -> action parameter string, '#',
     action param word

action param word
 -> action word
 -> action text


/*****

 Parenthesized string

 May contain any characters inside balanced parentheses. If parentheses
 are included, they must balance. Outer parentheses are stripped before
 use.

*****/

paren string
 -> paren string chars, ')'

paren string chars
 -> '('                                     =++sa;
 -> paren string chars, paren string char

paren string char
 -> not paren:c                             =sa << c;
 -> !sa << '(';, paren string chars, ')'    =concat(sa) << ')';


/*****

 String Literal

 Follows the same rules as for string literals in C and C++

*****/

string literal
  -> string chars, '"'

string chars
  -> '"'                                    =++sa;
  -> string chars, string char

string char
 -> not double quote:c                      =sa << c;
 -> escape sequence:c                       =sa << c;

(int) escape sequence
 -> "\\a"   ='\a';
 -> "\\b"   ='\b';
 -> "\\f"   ='\f';
 -> "\\n"   ='\n';
 -> "\\r"   ='\r';
 -> "\\t"   ='\t';
 -> "\\v"   ='\v';
 -> "\\\\"  ='\\';
 -> "\\?"   = '\?';
 -> "\\'"   ='\'';
 -> "\\\""  ='"';
 -> octal escape
 -> hex escape

(int) octal escape
 -> one octal | two octal | three octal

(int) one octal
 -> '\\', '0-7':d                           =d-'0';

(int) two octal
 -> one octal:n, '0-7':d                    =8*n + d-'0';

(int) three octal
 -> two octal:n, '0-7':d                    =8*n + d-'0';

(int) hex escape
 -> "\\x", hex number:n                     =(int) n;

(long) hex number
 -> hex digit
 -> hex number:n, hex digit:d               =16*n + d;

[
  sticky {one octal, two octal, hex number}
]


/*****

 Command Line Interpretation

 The identifier may be the name of a DOS command, internal or external,
 a path name of an arbitrary executable, or an internal commmand of the
 scripting language. It may appear literally, or may be the result of
 string concatenation and substitution.

 command is used in the program logic section, below.

*****/

command
 -> identifier, parameters?                 =exec();
 -> identifier, parameters?,
      '<', parameter string                 =exec_redirect_in();
 -> piped command:file, '|',
      identifier, parameters?               =exec_pipe_in(file);
 -> piped command:file, '>',
      parameter string                      =grab_output(file);
 -> piped command:file, ">>",
      parameter string                      =append_output(file);

(char *) piped command
 -> identifier, parameters?                 =exec_pipe_out();
 -> identifier, parameters?,
      '<', parameter string                 =exec_redirect_in_pipe_out();
 -> piped command:file, '|',
      identifier, parameters?               =exec_pipe_in_pipe_out(file);

identifier
 -> string                                  =sa << 0, ++ps << (sa.top());

parameters
 -> parameter string                        =ps << (sa.top()), sa << 0;
 -> parameters, parameter string            =ps << (sa.top()), sa << 0;


/*****

 Program logic.

 This section of syntax controls the interpretation of a sequence
 of commands enclosed within braces.

*****/

execution block
 -> '{', eol?...,[command sequence, eol?... | if sequence, eol?...], '}'


/*****

 A command sequence is any sequence of statements and
 if statements that ends with a statement.

*****/

command sequence
 -> statement
 -> command sequence, eol..., statement
 -> if sequence, eol..., statement
 -> if sequence:pc, eol?...,
    "else", action text                   =do_if(pc,1);

/*****

 An if sequence is any direct sequence of statements and if statements
 that ends with an if statement. The difference between an "if sequence" and
 a "command sequence" is that an else clause may follow the "if sequence".

*****/

(int) if sequence
 -> if statement
 -> if sequence, eol..., if statement:cc      =cc;
 -> command sequence, eol..., if statement:cc =cc;
 -> if sequence:pc, eol?..., "else", eol?...,
     if condition:cc, action text         =do_if(pc,cc!=0);

(int) if condition
 -> "if",
    '(', conditional exp:cc, ')'            =(int) cc;

(int) if statement
 -> if condition:cc, action text          =do_if(0,cc != 0);

/*****

 A statement is any command that isn't an if or else statement.
 The iteration on the while statement is a ruse.

*****/

statement
 -> command
 -> assignment
 -> for statement
 -> declaration
 -> screen description
 -> while statement...


/*****

 Assignment statements

 There are four varieties of assignment statement depending on whether
 or how the variable on the left hand side has been previously declared.

*****/

assignment
 -> undeclared variable:v, '=',
     parameter string                       =assign_value(v);
 -> integer variable:v, '=',
     conditional exp:x, ';'?                =st[v].data.integer = (int) x;
 -> string variable:v, '=',
    string exp, ';'?                        =st[v].data.text = copy(sa--);
 -> string variable:v, '@',
     primary exp:n, '=',
     conditional exp:x, ';'?                =st[v].data.text[(unsigned)n] = (char) x;


/*****

 Semantically determined production to determine treatment of variable
 on left side of assignment statement.

*****/

(unsigned) integer variable,
           string variable,
           undeclared variable
 -> literal                                 =check_integer();


/*****

 While Statement

 The While statement loops by simply resetting the input pointer for
 the parser back to the beginning of the while loop. This is the reason
 for the iteration of the while statement in the production for "simple
 command".

*****/

while statement
 -> "while", '(', conditional exp:cc, ')',
     action text                          =do_while(cc != 0);


/*****



 For Statement

 This for statement corresponds to the for statement in the DOS batch
 programming language, not the for statement in C.

*****/

for statement
 -> "for", name,                           // !++sa << '(';,
    "in", parameter string,
    "do"?, action text                    =do_for_loop();


/*****

 Declaration statements

*****/

declaration
 -> "action", literals:n,
        action text                         =define_action(n);
 -> "int",
       name,  '=', conditional exp:x, ';'?  =define_integer((sa--).top(), x);
 -> "string",
       name, '=', string exp, ';'?          =define_string();

(int) literals
 -> literal                                 =0;
 -> literals:n, ws..., literal              =n+1;


name
 -> letter:c                                =++sa << c;
 -> name, letter:c                          =sa << c;
 -> name, digit:c                           =sa << c;


/*****

 Integer and String Expression Logic

 The syntax for expressions is essentially that of C, with the addition
 of string comparison. The only missing operators are ++, --, and comma.

*****/


(long) conditional exp
 -> logical or exp
 -> logical or exp:c, '?', conditional exp:x, ':',
    conditional exp:y                       = c != 0 ? x : y;

(long) logical or exp
 -> logical and exp
 -> logical or exp:x, "||",
      logical and exp:y                     =x != 0 || y!=0;

(long) logical and exp
 -> inclusive or exp
 -> logical and exp:x, "&&",
      inclusive or exp:y                    =x != 0 && y !=0;

(long) inclusive or exp
 -> exclusive or exp
 -> inclusive or exp:x, '|',
      exclusive or exp:y                    =x | y;

(long) exclusive or exp
 -> and exp
 -> exclusive or exp:x, '^', and exp:y      =x ^ y;

(long) and exp
 -> equality exp
 -> and exp:x, '&', equality exp:y          =x & y;

(long) equality exp
 -> relational exp
 -> equality exp:x, "==", relational exp:y  =x == y;
 -> equality exp:x, "!=", relational exp:y  =x != y;
 -> string exp, "==", string exp            =string_comp() == 0;
 -> string exp, "!=", string exp            =string_comp() != 0;


(long) relational exp
 -> shift exp
 -> relational exp:x, '<', shift exp:y      =x < y;
 -> relational exp:x, '>', shift exp:y      =x > y;
 -> relational exp:x, "<=", shift exp:y     =x <= y;
 -> relational exp:x, ">=", shift exp:y     =x >= y;
 -> string exp, '<', string exp             =string_comp() < 0;
 -> string exp, '>', string exp             =string_comp() > 0;
 -> string exp, "<=", string exp            =string_comp() <= 0;
 -> string exp, ">=", string exp            =string_comp() >= 0;

(long) shift exp
 -> additive exp
 -> shift exp:x, "<<", additive exp:y       =x << (int) y;
 -> shift exp:x, ">>", additive exp:y       =x >> (int) y;

(long) additive exp
 -> multiplicative exp
 -> additive exp:x, '+',
      multiplicative exp:y                  =x + y;
 -> additive exp:x, '-',
      multiplicative exp:y                  =x - y;

(long) multiplicative exp
 -> unary exp
 -> multiplicative exp:x, '*', unary exp:y  =x * y;
 -> multiplicative exp:x, '/', nonzero:y    =x / y;
 -> multiplicative exp:x, '%', nonzero:y    =x % y;

(long) nonzero
  -> unary exp: x ={
      assert(x);
      return x;
    }

(long) unary exp
 -> primary exp
 -> '+', unary exp:x                        =x;
 -> '-', unary exp:x                        =-x;
 -> '~', unary exp:x                        =~x;
 -> '!', unary exp:x                        =!x;

(long) primary exp
 -> integer constant:x                      =x;
 -> character constant:x                    =x;
 -> string term,'@', primary exp:n =((unsigned char *) (sa--).top())[(int) n];
 -> '#', string element    ={
                              long temp;
                              sscanf((sa--).top(), "%ld", &temp);
                              return temp;
                            }
 -> numeric name
 -> '(', conditional exp:x, ')'             =x;
 -> built_in name:x, built_in argument  =(*st[(unsigned)x].data.func)();

built_in argument
 -> '(', parameter string, ')'

(long) numeric name,
       string name,
       built_in name,
       undefined name
 -> name                                    =name_type();


/*****

 String Expressions

*****/

string exp
 -> string term
 -> string exp, '#', string term           =concat(sa);

string term
 -> string element
 -> string term, '@', '(',
    conditional exp:first, "..",
    conditional exp:last, ')'  =extract((unsigned)first, (unsigned) last);
 -> string term, '[',                       !sa << '[';,
      parameter string, ']'                 =concat(sa) << ']', lookup();

string element
 -> string literal
 -> string name:x                     =++sa << st[(unsigned)x].data.text;
 -> undefined name:x                  =++sa << sd[(unsigned)x];
 -> action text                       =action_string();
 -> '=', primary exp:x                =++sa,sa.printf("%ld",x);
 -> '(', string exp, ')'

/*****

 Integer constants

 The syntax for integer constants is identical to that in C.

*****/

(long) integer constant
 -> hex constant
 -> octal constant
 -> decimal constant

(long) hex constant
 -> {"0x" | "0X"}                           =0;
 -> hex constant:x, hex digit:d             =16*x + d;

(long) hex digit
 -> '0-9':d                                 =d - '0';
 -> 'a-f' + 'A-F':d                         =(d&7) + 9;

(long) octal constant
  -> '0'                                    =0;
  -> octal constant:n, '0-7':d              =8*n + d-'0';

(long) decimal constant
 -> '1-9':d                                 =d-'0';
 -> decimal constant:n, '0-9':d             =10*n + d-'0';


/*****

 Character Constant

 The rules for character constant are the same as in C.

*****/

(int) character constant
 -> '\'', char constant element:c, '\''     =c;

(int) char constant element
 -> not single quote
 -> escape sequence


/*****

 Screen Display

*****/

screen description
 -> screen items:scd, '}'                   =display_queries(scd);

(screen_descriptor *) screen items
 -> "screen", '{'                           =reset(qs), new screen_descriptor;
 -> screen items, eol
 -> screen items:scd,
      "title", '=',
      formula, eol                          =scd->title = formula(), scd;
 -> screen items:scd,
      color spec:c, eol                     =scd->color = (char) c, scd;
 -> screen items:scd,
      "entry", color spec:c, eol            =scd->entry_color = (char) c, scd;
 -> screen items:scd,
       "highlight", color spec:c, eol       =scd->highlight_color = (char) c, scd;
 -> screen items:scd,
       "size", '=', conditional exp:w,
         ',', conditional exp:h   =scd->width = (unsigned)w, scd->height = (unsigned) h, scd;
 -> screen items:scd,
       "location", '=', conditional exp:px,
         ',', conditional exp:py        =scd->pos.x = (unsigned) px,scd->pos.y = (unsigned) py, scd;
 -> screen items:scd,
      query line:q, '}',  eol               =qs << *q, delete q, scd;
 -> screen items:scd,
      button line:q, '}', eol               =qs << *q, delete q, scd;

(int) color spec
 -> "color", '=', conditional exp:fg,
       ',', conditional exp:bg              =COLOR((unsigned)fg,(unsigned)bg);


(query_item *) query line
 -> "field", '{'                            =clear(new query_item);
 -> query line, eol
 -> query line:q,
      "variable", '=',
       literal, eol                         =q->id = sd << (sa--).top(), q;
 -> query line:q,
      "default", '=',
       formula, eol                         =q->value = formula(), q;
 -> query line:q,
      "prompt", '=',
       formula, eol                         =q->prompt = formula(), q;
 -> query line:q,
      "explanation", '=',
       formula, eol                         =q->explanation = formula(),q;

(query_item *) button line
 -> "button", '{'                           =clear(new query_item);
 -> button line,  eol
 -> button line:q,
      "prompt", '=', formula, eol           =q->prompt = formula(), q;
 -> button line:q,
      "explanation", '=',
       formula, eol                         =q->explanation = formula(),q;
 -> button line:q,
      action text, eol                      =q->action = copy_action(), q;

formula
 -> formula element                         =reset(is) << (sd << (sa--).top());
 -> formula, '#', formula element           =is << (sd << (sa--).top());

formula element
 -> paren string
 -> string literal
 -> literal
 -> formula element, '[',                   !sa << '[';,
      parameter string, ']'                 =concat(sa) << ']';

{

#include <stdlib.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <assert.h>
#include <errno.h>

#if defined(__MSDOS__) || defined(__WINDOWS__)
#include <io.h>
#include <conio.h>
#include <process.h>
#else
#include <unistd.h>

/* This is only meant to compile, not run. (Unix) */
static int kbhit(void) { return 0; }
static int getch(void) { return '?'; }
static void ungetch(int) { }
static void strupr(char *) { }
static int spawnvp(int, const char *, char *const *) { return -1; }
#define O_TEXT 0
#define O_BINARY 0
#define P_WAIT 0
#define _MAX_PATH 128
#define _MAX_DRIVE 4
#define _MAX_DIR  128
#define _MAX_FNAME 16
#define _MAX_EXT   4
static void _splitpath(const char *, char *, char *, char *, char *) {}
static void _makepath(char *, const char *, const char *, const char *,
	const char *) {}

#endif

#ifdef __MSDOS__
#include <dos.h>
#else

/* This is only meant to compile, not run. (Windows, Unix) */

#define far
struct find_t { int attrib; char name[16]; };
int _dos_findfirst(const char *, int, struct find_t *) { return -1; }
int _dos_findnext(struct find_t *) { return -1; }
#define _A_SUBDIR 1
struct diskfree_t {
       unsigned avail_clusters, bytes_per_sector, sectors_per_cluster;
};
void _dos_getdiskfree(int, struct diskfree_t *f) {
   f->avail_clusters = 0;
   f->bytes_per_sector = 512;
   f->sectors_per_cluster = 4;
}
void _dos_getftime(int, unsigned short *d, unsigned short *t) { *d=*t=0; }

#endif /* not MSDOS */

#include "edit.h"


#include "screen.h"
#include "util.h"
#include "redirect.h"

#define GET_CONTEXT CONTEXT.pointer = PCB.pointer;\
  CONTEXT.line=PCB.line;\
  CONTEXT.column = PCB.column;


int       debug_switch = 0;
char     *error_msg = NULL;
unsigned  errorlevel_index;
int       errorlevel;
int       exitcode = 0;
int       exitflag = 0;
int       first_line = 1;
int       first_column = 1;
unsigned  stderr_index;

void      display_queries(screen_descriptor *);

#define FIRST_LINE first_line
#define FIRST_COLUMN first_column


/*****

 Internal Functions

*****/

long file_exists(void) {
  FILE *f = fopen((sa--).top(),"r");
  if (f != NULL) fclose(f);
  return f != NULL;
}

long directory_exists(void) {
  struct find_t ff;
  int result;

  sa << "\\*.*";
  result = _dos_findfirst((sa--).top(),_A_SUBDIR,&ff);
  return result == 0;
}

long string_length(void) {
  return size(sa--);
}

long get_file_length(void) {
  int handle = open((sa--).top(), O_RDONLY);
  long length;
  if (handle < 0) return 0;
  length = filelength(handle);
  close(handle);
  return length;
}

long disk_space(void) {
  struct diskfree_t free;
  int drive = toupper(*(sa--).top()) - 64;
  long avail;

  _dos_getdiskfree(drive, &free);
  avail = (long) free.avail_clusters
        * (long) free.bytes_per_sector
        * (long) free.sectors_per_cluster;
  return avail;
}

long file_time(void) {
  int handle = open((sa--).top(), O_RDONLY);
#ifdef __BCPLUSPLUS__
  unsigned date, time;
#else
  unsigned short date, time;
#endif
  struct tm t;

  if (handle < 0) return 0;
  _dos_getftime(handle, &date, &time);
  close(handle);
  t.tm_year = ((date & 0xfe00) >> 9) + 80;
  t.tm_mon  = ((date & 0x1e00) >> 5) - 1;
  t.tm_mday = date & 0x001f;
  ;
  t.tm_hour = (time & 0xf800) >> 11;
  t.tm_min  = (time & 0x07e0) >> 5;
  t.tm_sec  = (time & 0x001f) << 1;
  return mktime(&t);
}


// Support for reduction procecures

// Compare top strings on string accumulator

/*
  pops top two strings from string accumulator using strcmp
  and returns
    -1 if first string is less than top string
     0 if strings match
    +1 if top string is greater than first string
*/

int string_comp(void) {
  int n = size(sa);
  array<char> right_string((sa--).top(), n+1);
  return strcmp((sa--).top(),right_string);
}

/*
  replace the top string on the stack, with a substring where the index
  of the first character in the substring is given by "first" and the index
  of the last character is given by "last"
*/

void extract(unsigned first, unsigned last) {
  int n = last - first + 1;
  assert (last >= first);
  array <char> x( (sa--).top() + first, n+1 );
  x[n] = 0;
  ++sa << x;
}

/*
 Look up the top string on the accumulator stack in the string dictionary.
 If it has a value in the symbol table, replace it with the symbol table
 value. If the value is numeric, convert it to integer. Otherwise, leave the
 string untouched on the stack.
*/

void lookup(void) {
  unsigned index = sd[sa.top()];
  if (index == 0) return;
  switch (st[index].type) {
    case string_type:
    case value_type: {
      --sa;                                       // discard name
      ++sa << st[index].data.text;                // stack value
      break;
    }
    case integer_type: {
      --sa;                                       // discard name
      (++sa).printf("%ld", st[index].data.integer); // convert to ascii
      break;
    }
    default:
      /* not supposed to happen? */
      break;
  }
}

/*
 Find the data type of a symbol and change the reduction accordingly.
 Return the dictionary index for strings, and the value itself for integers.
*/

long name_type(void) {
  unsigned index = sd << (sa--).top();
  switch (st[index].type) {
    case value_type:
    case string_type: {
      CHANGE_REDUCTION(string_name);
      return index;
    }
    case built_in_function_type: {
      CHANGE_REDUCTION(built_in_name);
      return index;
    }
    case undefined_type: {
      CHANGE_REDUCTION(undefined_name);
      return index;
    }
    case integer_type: return st[index].data.integer;
    default:
      /* not supposed to happen? */
      break;
  }
  return 0;
}

/*
 Store a string formula. A string formula is a sequence of string identifiers
 the values of which are to be concatenated. The parser has accumulated the
 identifiers on the integer_stack, is. The formula is terminated by a zero
 entry.
*/

int *formula(void) {
  int n = size(is << 0);
  int *f = new int[n];
  while (n--) is >> f[n];
  return f;
}

/*
 Make a copy of an action that has been identified in the text stream.
 An action pointer was stacked at the beginning of the action text on the
 action stack, as.
*/

action_pointer copy_action(void) {
  action_pointer ap;
  as >> ap;                               // pop action descriptor
  unsigned length = (unsigned) (PCB.pointer - ap.pointer);
  unsigned char *action = memdup(ap.pointer,length + 1);
  action[length] = 0;
  ap.pointer = action;
  return ap;
}


// Internal Commands

int echo(int, char *args[]) {
  int i;
  const char *cs = "";
  for (i = 1; args[i]; i++) printf("%s%s", cs, args[i]), cs = " ";
  printf("\n");
  fflush(stdout);
  return 0;
}

int pause(int, char *[]) {
  int c;
  while (kbhit()) getch();                  // Empty buffer
  printf("Press any key to continue . . .\n");
  c = getch();
  if (c == 3) exit(1);
  return c;
}

int exit_script(int n_args, char *args[]) {
  if (n_args > 1) sscanf(args[1], "%d", &exitcode);
  exit(exitcode);
  return exitcode;
}

/*
int return_script(int n_args, char *args[]) {
  if (n_args > 1) sscanf(args[1], "%d", &exitcode);
  PCB.exit_flag = AG_SUCCESS_CODE;
  return exitcode;
}
*/

int subdirs(int, char *args[]) {
  struct find_t file_block;
  int flag;
  int length = strlen(args[1]);
  array <char> name(args[1],length + 5);

  strcat(name, "\\*.*");
  for (flag = _dos_findfirst(name, _A_SUBDIR, &file_block);
       flag == 0; flag = _dos_findnext(&file_block)) {
    if ((file_block.attrib & _A_SUBDIR) == 0) continue;
    if (strcmp(file_block.name, ".") == 0) continue;
    if (strcmp(file_block.name, "..") == 0) continue;
    puts(file_block.name);
  }
  return 0;
}

int files(int, char *args[]) {
  struct find_t file_block;
  int flag;
  int length = strlen(args[1]);
  array<char> name(args[1],length + 5);

  strcat(name, "\\*.*");
  for (flag = _dos_findfirst(name, 0, &file_block);
       flag == 0; flag = _dos_findnext(&file_block)) {
    puts(file_block.name);
  }
  return 0;
}


/*****

 Execute Command Line

*****/


void perform_action(action_pointer ap) {
  dsl_pcb_type save_pcb = PCB;

  PCB.pointer = ap.pointer;
  first_line = ap.line;
  first_column = ap.column;
  dsl();
  exitflag = PCB.exit_flag != AG_SUCCESS_CODE;
  PCB = save_pcb;
  if (exitflag) PCB.exit_flag = AG_SEMANTIC_ERROR_CODE;
}

void exec(void) {
  int n = size(ps << (char *) NULL);
  int n_args = n - 1;
  unsigned index;
  unsigned uc_index;
  int i;

  array <char *> args(n);
  while (n--) ps >> args[n];

  index = sd[args[0]];
  while (index && st[index].type == string_type) {
     args[0] = st[index].data.text;                // stack value
     index = sd[args[0]];
  }
  if (debug_switch) {
    for (i = 0; args[i]; i++) fprintf(stderr, "%s ", args[i]);
    fprintf(stderr,"\nPress any key to continue\n");
    while (!kbhit());
    getch();
  }
  strupr(args[0]);
  uc_index = sd[args[0]];
  if (n_args == 1 && strlen(args[0]) == 2 && args[0][1] == ':') {
    errorlevel = system(args[0]);
  }
  else if ( *args[0] && uc_index) switch (st[uc_index].type) {
    case internal_type: {
      errorlevel = (*st[uc_index].data.proc)(n_args, args);
      break;
    }
    case dos_type: {
      int i;
      for (i = 1; args[i]; i++) args[i][-1] = ' ';
      errorlevel = system(args[0]);
      if (errorlevel == -1) {
        fprintf(stderr,"Error invoking %s: %s\n", args[0], strerror(errno));
        exit(1);
      }
      break;
    }
    default:
      /* not supposed to happen? */
      break;
  }
  else if ( *args[0] && index) switch (st[index].type) {
    case action_type: {
      action_descriptor d = *st[index].data.action;
      stack <symbol_table_entry> old_entries(d.n_args);
			for (i = 0; i < d.n_args && args[i+1]; i++) {
        old_entries << st[d.args[i]];
        st[d.args[i]].type = value_type;
        st[d.args[i]].data.text = memdup(args[i+1], 1 + strlen(args[i+1]));
      }
      perform_action(d.ap);
      for (i = d.n_args; i--;) {
        release(st[d.args[i]]);
        old_entries >> st[d.args[i]];
      }
      break;
    }
    default: {
      char **tmpargs = args;
      errorlevel = spawnvp(P_WAIT,args[0], tmpargs);
      if (errorlevel == -1) {
        fprintf(stderr,"Error invoking %s: %s\n", args[0], strerror(errno));
        exit(1);
      }
    }
  }
  else {
  }
  st[errorlevel_index].data.integer = errorlevel;
  while (n_args--) --sa;
  --ps;
  if (kbhit()) {
    int c = getch();
    if (c == 3) exit(1);
    ungetch(c);
  }
}

void discard_temp_file(char *file_name) {
  unlink(file_name);                        // Delete file
  delete [] file_name;                      // Free storage for name
}


/*****

 Execute Command with piped input

*****/


void exec_pipe_in(char *file_name) {
  {
    redirect sin(STDIN, file_name);
    exec();
  }
  discard_temp_file(file_name);
}


/*****

 Execute Command with redirected I/O

*****/

void exec_redirect_in(void) {
  redirect sin(STDIN, (sa--).top());
  exec();
}

char *exec_pipe_out(void) {
  fflush(stdout);
  redirect sout(STDOUT);
  exec();
  fflush(stdout);
  return save_file(sout);
}

char *exec_pipe_in_pipe_out(char *file_name) {
  char *result;
  {
    redirect sin(STDIN, file_name);
    fflush(stdout);
    redirect sout(STDOUT);
    exec();
    fflush(stdout);
    result = save_file(sout);
  }
  discard_temp_file(file_name);
  return result;
}

char *exec_redirect_in_pipe_out(void) {
  fflush(stdout);
  redirect sout(STDOUT);
  exec_redirect_in();
  fflush(stdout);
  return save_file(sout);
}

unsigned check_integer(void) {
  unsigned index = sd << (sa--).top();
  if (st[index].type == integer_type) return index;
  CHANGE_REDUCTION(undeclared_variable);
  if (st[index].type == string_type) CHANGE_REDUCTION(string_variable);
  return index;
}

void assign_value(unsigned index) {
  char *text = copy(sa--);
  release(st[index]);
  st[index].type = value_type;
  st[index].data.text = text;
}

void grab_output(char *temp_name) {
  unlink(sa.top());                         // delete old file
  rename(temp_name, (sa--).top());          // rename temp file
  delete [] temp_name;                      // discard name string
}

void append_output(char *temp_name) {
  fflush(stdout);
  redirect sout(STDOUT, (sa--).top(), 1);   // append to file named on sa
  redirect sin(STDIN, temp_name);
  char buf[2000];
  int n;
  while (1) {
    n = read(STDIN, buf, 2000);
    if (n == 0) break;
    write(STDOUT, buf, n);
  }
  fflush(stdout);
  unlink(temp_name);
  delete [] temp_name;
}

void action_string(void) {
  action_pointer ap;
  as >> ap;
  unsigned length = (unsigned)(PCB.pointer - ap.pointer);
  array <unsigned char> action(ap.pointer,length + 1);
  action[length] = 0;
  fflush(stdout);
  redirect sout(STDOUT);
  char *result;

  ap.pointer = action;
  perform_action(ap);
  fflush(stdout);
  result = content(sout);
  ++sa << result;
  delete [] result;
}


// Program Control functions

// If/else statement

int do_if(int pc, int cc) {
  action_pointer ap;
  as >> ap;
  if (!pc && cc && exitflag == 0) {
    unsigned length = (unsigned) (PCB.pointer - ap.pointer);
    array<unsigned char> q(ap.pointer, length+1);
    q[length] = 0;
    ap.pointer = q;
    perform_action(ap);
  }
  return pc || cc;
}

// While statement

void do_while(int cc) {
  unsigned length;
  action_pointer ap;
  as >> ap;
  if (cc == 0) return;
  length = (unsigned) (PCB.pointer - ap.pointer);
  array<unsigned char> q(ap.pointer, length+1);
  q[length] = 0;
  ap.pointer = q;
  perform_action(ap);
  if (exitflag) return;
  PCB.pointer = CONTEXT.pointer;
  PCB.line = CONTEXT.line;
  PCB.column = CONTEXT.column;
}


// For Statement
// Note that this is the for statement in the DOS batch languange for, not C

void do_for_loop(void) {
  int n,k;
  char *q;
  const char *seps = " \t\v\f\r\n";
  action_pointer ap;
  as >> ap;
  unsigned length = (unsigned)(PCB.pointer - ap.pointer);
  array <unsigned char> action(ap.pointer, length + 1);
  action[length] = 0;

  ap.pointer = action;
  n = size(sa);
  array<char> text((sa--).top(), n + 1);


  unsigned index = sd << (sa--).top();

  ++ps;
  for (q = strtok(text, seps); q != NULL; q = strtok(NULL,seps)) {
    if (*q == '(') {
      int k = strlen(q) - 1;
      assert(q[k] == ')');
      q[k] = 0;
      q++;
    }
    else if (*q == '"') {
      int k = strlen(q) - 1;
      assert(q[k] == '"');
      q[k] = 0;
      q++;
    }
    ps << q;
  }
  k = n = size(ps);
  array<char *> args(n);
  while (k--) ps >> args[k];
  --ps;
  symbol_table_entry save_table_entry = st[index];
  st[index].type = value_type;

  for (k = 0; k < n && exitflag == 0; k++) {
    st[index].data.text = args[k];
    perform_action(ap);
  }
  st[index] = save_table_entry;
}

void invoke_script(void) {
  int handle = open(sa.top(), O_TEXT | O_RDONLY);
  long size;
  unsigned n;
  action_pointer ap;

  if (handle < 0) {
    fprintf(stderr,"Cannot open %s\n", (sa--).top());
    exit(1);
  }
  --sa;
  size = filelength(handle);
  assert(size < 65536L);
  array <unsigned char> data((unsigned) size+1);
  n = (unsigned) read(handle,data,(unsigned) size);
  data[n] = 0;
  close(handle);
  exitflag = 0;
  ap.pointer = data;
  ap.line = ap.column = 1;
  perform_action(ap);
  st[errorlevel_index].data.integer = exitcode;
  exitflag = exitcode = 0;
  return;
}

internal_commands_descriptor internal_commands[] = {
  {"ECHO", echo},
  {"EXIT", exit_script},
  {"FILES", files},
  {"PAUSE", pause},
//  {"RETURN", return_script},
  {"SUBDIRS", subdirs},
  {NULL, NULL}
};

struct built_ins_descriptor built_ins[] = {
  {"file_exists", file_exists},
  {"directory_exists", directory_exists},
  {"string_length", string_length},
  {"file_length", get_file_length},
  {"disk_space", disk_space},
  {"file_time", file_time},
  {NULL, NULL}
};

void set_extension(char *path, const char *e) {
  char s[_MAX_PATH];
  char drive[_MAX_DRIVE];
  char dir[_MAX_DIR];
  char file[_MAX_FNAME];
  char ext[_MAX_EXT];

  _splitpath(path,drive,dir,file,ext);
  _makepath(s, drive, dir, file, e);
  ++sa << s;
}

/*
 Note that if this program is called without any arguments, it looks for a
 script with the same name as the executable. Thus, to make an install
 program that picks up the install script without any arguments, you simply
 rename DSL.EXE to INSTALL.EXE. Then when you run it without any arguments
 it will run the INSTALL.DSL script.
*/

int main(int argc, char *argv[]) {
  int arg_number = 0;
  int i = 1;
  int j = 0;

  init_dos_internals();
  set_arg(j++, argv[0]);
  if (argc > i && (argv[i][0] == '/' || argv[i][0] == '-')) {
    if (toupper(argv[i][1]) != 'D') {
      printf("Unrecognized switch -- /%c\n", argv[i][1]);
      return 1;
    }
    debug_switch = 1;
    i++;
  }
  if (argc > i) arg_number = i++;
  set_extension(argv[arg_number], "DSL");
  set_arg(j++,copy(sa));
  while (i < argc) set_arg(j++, argv[i++]);
  define_integer("argc", j);
  invoke_script();                          // Takes file name from sa
  return exitcode;
}
}                                           // End Embedded C