/****************************************************************************
 * apps/interpreters/minibasic/basic.c
 *
 *   Copyright (C) 2016 Gregory Nutt. All rights reserved.
 *
 * This file was taken from Mini Basic, versino 1.0 developed by Malcolm
 * McLean, Leeds University.  Mini Basic version 1.0 was released the
 * Creative Commons Attribution license which, from my reading, appears to
 * be compatible with the NuttX BSD-style license:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <limits.h>
#include <ctype.h>
#include <assert.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Configuration */

#ifndef CONFIG_INTERPRETER_MINIBASIC_IOBUFSIZE
#  define CONFIG_INTERPRETER_MINIBASIC_IOBUFSIZE 1024
#endif

#define IOBUFSIZE CONFIG_INTERPRETER_MINIBASIC_IOBUFSIZE

/* Tokens defined */

#define EOS 0
#define VALUE 1
#define PI 2
#define E 3

#define DIV 10
#define MULT 11
#define OPAREN 12
#define CPAREN 13
#define PLUS 14
#define MINUS 15
#define SHRIEK 16
#define COMMA 17
#define MOD 200

#define SYNTAX_ERROR 20
#define EOL 21
#define EQUALS 22
#define STRID 23
#define FLTID 24
#define DIMFLTID 25
#define DIMSTRID 26
#define QUOTE 27
#define GREATER 28
#define LESS 29
#define SEMICOLON 30

#define PRINT 100
#define LET 101
#define DIM 102
#define IF 103
#define THEN 104
#define AND 105
#define OR 106
#define GOTO 107
#define INPUT 108
#define REM 109
#define FOR 110
#define TO 111
#define NEXT 112
#define STEP 113

#define SIN 5
#define COS 6
#define TAN 7
#define LN 8
#define POW 9
#define SQRT 18
#define ABS 201
#define LEN 202
#define ASCII 203
#define ASIN 204
#define ACOS 205
#define ATAN 206
#define INT 207
#define RND 208
#define VAL 209
#define VALLEN 210
#define INSTR 211

#define CHRSTRING 300
#define STRSTRING 301
#define LEFTSTRING 302
#define RIGHTSTRING 303
#define MIDSTRING 304
#define STRINGSTRING 305

/* Relational operators defined */

#define ROP_EQ 1                /* equals */
#define ROP_NEQ 2               /* doesn't equal */
#define ROP_LT 3                /* less than */
#define ROP_LTE 4               /* less than or equals */
#define ROP_GT 5                /* greater than */
#define ROP_GTE 6               /* greater than or equals */

/* Error codes (in BASIC script) defined */

#define ERR_CLEAR 0
#define ERR_SYNTAX 1
#define ERR_OUTOFMEMORY 2
#define ERR_IDTOOLONG 3
#define ERR_NOSUCHVARIABLE 4
#define ERR_BADSUBSCRIPT 5
#define ERR_TOOMANYDIMS 6
#define ERR_TOOMANYINITS 7
#define ERR_BADTYPE 8
#define ERR_TOOMANYFORS 9
#define ERR_NONEXT 10
#define ERR_NOFOR 11
#define ERR_DIVIDEBYZERO 12
#define ERR_NEGLOG 13
#define ERR_NEGSQRT 14
#define ERR_BADSINCOS 15
#define ERR_EOF 16
#define ERR_ILLEGALOFFSET 17
#define ERR_TYPEMISMATCH 18
#define ERR_INPUTTOOLONG 19
#define ERR_BADVALUE 20
#define ERR_NOTINT 21

#define MAXFORS 32              /* Maximum number of nested fors */

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct mb_line_s
{
  int no;                       /* Line number */
  FAR const char *str;          /* Points to start of line */
};

struct mb_variable_s
{
  char id[32];                  /* Id of variable */
  double dval;                  /* Its value if a real */
  FAR char *sval;               /* Its value if a string (malloced) */
};

struct mb_dimvar_s
{
  char id[32];                  /* Id of dimensioned variable */
  int type;                     /* Its type, STRID or FLTID */
  int ndims;                    /* Number of dimensions */
  int dim[5];                   /* Dimensions in x y order */
  FAR char **str;               /* Pointer to string data */
  FAR double *dval;             /* Pointer to real data */
};

struct mb_lvalue_s
{
  int type;                     /* Type of variable (STRID or FLTID or SYNTAX_ERROR) */
  FAR char **sval;              /* Pointer to string data */
  FAR double *dval;             /* Pointer to real data */
};

struct mb_forloop_s
{
  char id[32];                  /* Id of control variable */
  int nextline;                 /* Line below FOR to which control passes */
  double toval;                 /* Terminal value */
  double step;                  /* Step size */
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* NOTE: The use of these globals precludes the use of Mini Basic on
 * multiple threads (at least in a flat address environment).  If you
 * want multiple copies of Mini Basic to run, you would need to:
 * (1) Create a new struct mb_state_s that contains all of the following
 *     as fields.
 * (2) Allocate an instance of struct mb_state_s in basic() as part of the
 *     initialization logic. And,
 * (3) Pass the instance of struct mb_state_s to every Mini Basic function.
 */

static struct mb_forloop_s g_forstack[MAXFORS]; /* Stack for for loop conrol */
static int nfors;                               /* Number of fors on stack */

static FAR struct mb_variable_s *g_variables;   /* The script's variables */
static int g_nvariables;                        /* Number of variables */

static FAR struct mb_dimvar_s *g_dimvariables;  /* Dimensioned arrays */
static int g_ndimvariables;                     /* Number of dimensioned arrays */

static FAR struct mb_line_s *g_lines;           /* List of line starts */
static int nlines;                              /* Number of BASIC g_lines in program */

static FILE *g_fpin;                            /* Input stream */
static FILE *g_fpout;                           /* Output strem */
static FILE *g_fperr;                           /* Error stream */

static FAR const char *g_string;                /* String we are parsing */
static int g_token;                             /* Current token (lookahead) */
static int g_errorflag;                         /* Set when error in input encountered */
static char g_iobuffer[IOBUFSIZE];              /* I/O buffer */

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int setup(FAR const char *script);
static void cleanup(void);

static void reporterror(int lineno);
static int findline(int no);

static int line(void);
static void doprint(void);
static void dolet(void);
static void dodim(void);
static int doif(void);
static int dogoto(void);
static void doinput(void);
static void dorem(void);
static int dofor(void);
static int donext(void);

static void lvalue(FAR struct mb_lvalue_s *lv);

static int boolexpr(void);
static int boolfactor(void);
static int relop(void);

static double expr(void);
static double term(void);
static double factor(void);
static double instr(void);
static double variable(void);
static double dimvariable(void);

static FAR struct mb_variable_s *findvariable(FAR const char *id);
static FAR struct mb_dimvar_s *finddimvar(FAR const char *id);
static FAR struct mb_dimvar_s *dimension(FAR const char *id, int ndims, ...);
static FAR void *getdimvar(FAR struct mb_dimvar_s *dv, ...);
static FAR struct mb_variable_s *addfloat(FAR const char *id);
static FAR struct mb_variable_s *addstring(FAR const char *id);
static FAR struct mb_dimvar_s *adddimvar(FAR const char *id);

static FAR char *stringexpr(void);
static FAR char *chrstring(void);
static FAR char *strstring(void);
static FAR char *leftstring(void);
static FAR char *rightstring(void);
static FAR char *midstring(void);
static FAR char *stringstring(void);
static FAR char *stringdimvar(void);
static FAR char *stringvar(void);
static FAR char *stringliteral(void);

static int integer(double x);

static void match(int tok);
static void seterror(int errorcode);
static int getnextline(FAR const char *str);
static int gettoken(FAR const char *str);
static int tokenlen(FAR const char *str, int tokenid);

static int isstring(int tokenid);
static double getvalue(FAR const char *str, FAR int *len);
static void getid(FAR const char *str, FAR char *out, FAR int *len);

static void mystrgrablit(FAR char *dest, FAR const char *src);
static FAR char *mystrend(FAR const char *str, char quote);
static int mystrcount(FAR const char *str, char ch);
static FAR char *mystrdup(FAR const char *str);
static FAR char *mystrconcat(FAR const char *str, FAR const char *cat);
static double factorial(double x);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: setup
 *
 * Description:
 *   Sets up all our globals, including the list of lines.
 *   Params: script - the script passed by the user
 *   Returns: 0 on success, -1 on failure
 *
 *
 ****************************************************************************/

static int setup(FAR const char *script)
{
  int i;

  nlines = mystrcount(script, '\n');
  g_lines = malloc(nlines * sizeof(struct mb_line_s));
  if (!g_lines)
    {
      if (g_fperr)
        {
          fprintf(g_fperr, "Out of memory\n");
        }

      return -1;
    }

  for (i = 0; i < nlines; i++)
    {
      if (isdigit(*script))
        {
          g_lines[i].str = script;
          g_lines[i].no = strtol(script, 0, 10);
        }
      else
        {
          i--;
          nlines--;
        }

      script = strchr(script, '\n');
      script++;
    }

  if (!nlines)
    {
      if (g_fperr)
        {
          fprintf(g_fperr, "Can't read program\n");
        }

      free(g_lines);
      return -1;
    }

  for (i = 1; i < nlines; i++)
    if (g_lines[i].no <= g_lines[i - 1].no)
      {
        if (g_fperr)
          {
            fprintf(g_fperr, "program lines %d and %d not in order\n",
                    g_lines[i - 1].no, g_lines[i].no);
          }

        free(g_lines);
        return -1;
      }

  g_nvariables = 0;
  g_variables = 0;

  g_dimvariables = 0;
  g_ndimvariables = 0;

  return 0;
}

/****************************************************************************
 * Name: cleanup
 *
 * Description:
 *   Frees all the memory we have allocated
 *
 ****************************************************************************/

static void cleanup(void)
{
  int i;
  int ii;
  int size;

  for (i = 0; i < g_nvariables; i++)
    {
      if (g_variables[i].sval)
        {
          free(g_variables[i].sval);
        }
    }

  if (g_variables)
    {
      free(g_variables);
    }

  g_variables = 0;
  g_nvariables = 0;

  for (i = 0; i < g_ndimvariables; i++)
    {
      if (g_dimvariables[i].type == STRID)
        {
          if (g_dimvariables[i].str)
            {
              size = 1;
              for (ii = 0; ii < g_dimvariables[i].ndims; ii++)
                {
                  size *= g_dimvariables[i].dim[ii];
                }

              for (ii = 0; ii < size; ii++)
                {
                  if (g_dimvariables[i].str[ii])
                    {
                      free(g_dimvariables[i].str[ii]);
                    }
                }

              free(g_dimvariables[i].str);
            }
        }
      else if (g_dimvariables[i].dval)
        {

          free(g_dimvariables[i].dval);
        }
    }

  if (g_dimvariables)
    {
      free(g_dimvariables);
    }

  g_dimvariables = 0;
  g_ndimvariables = 0;

  if (g_lines)
    {
      free(g_lines);
    }

  g_lines = 0;
  nlines = 0;
}

/****************************************************************************
 * Name: reporterror
 *
 * Description:
 *   Frror report function.
 *   For reporting errors in the user's script.
 *   Checks the global g_errorflag.
 *   Writes to g_fperr.
 *   Params: lineno - the line on which the error occurred
 *
 ****************************************************************************/

static void reporterror(int lineno)
{
  if (!g_fperr)
    {
      return;
    }

  switch (g_errorflag)
    {
    case ERR_CLEAR:
      assert(0);
      break;

    case ERR_SYNTAX:
      fprintf(g_fperr, "Syntax error line %d\n", lineno);
      break;

    case ERR_OUTOFMEMORY:
      fprintf(g_fperr, "Out of memory line %d\n", lineno);
      break;

    case ERR_IDTOOLONG:
      fprintf(g_fperr, "Identifier too long line %d\n", lineno);
      break;

    case ERR_NOSUCHVARIABLE:
      fprintf(g_fperr, "No such variable line %d\n", lineno);
      break;

    case ERR_BADSUBSCRIPT:
      fprintf(g_fperr, "Bad subscript line %d\n", lineno);
      break;

    case ERR_TOOMANYDIMS:
      fprintf(g_fperr, "Too many dimensions line %d\n", lineno);
      break;

    case ERR_TOOMANYINITS:
      fprintf(g_fperr, "Too many initialisers line %d\n", lineno);
      break;

    case ERR_BADTYPE:
      fprintf(g_fperr, "Illegal type line %d\n", lineno);
      break;

    case ERR_TOOMANYFORS:
      fprintf(g_fperr, "Too many nested fors line %d\n", lineno);
      break;

    case ERR_NONEXT:
      fprintf(g_fperr, "For without matching next line %d\n", lineno);
      break;

    case ERR_NOFOR:
      fprintf(g_fperr, "Next without matching for line %d\n", lineno);
      break;

    case ERR_DIVIDEBYZERO:
      fprintf(g_fperr, "Divide by zero lne %d\n", lineno);
      break;

    case ERR_NEGLOG:
      fprintf(g_fperr, "Negative logarithm line %d\n", lineno);
      break;

    case ERR_NEGSQRT:
      fprintf(g_fperr, "Negative square root line %d\n", lineno);
      break;

    case ERR_BADSINCOS:
      fprintf(g_fperr, "Sine or cosine out of range line %d\n", lineno);
      break;

    case ERR_EOF:
      fprintf(g_fperr, "End of input file %d\n", lineno);
      break;

    case ERR_ILLEGALOFFSET:
      fprintf(g_fperr, "Illegal offset line %d\n", lineno);
      break;

    case ERR_TYPEMISMATCH:
      fprintf(g_fperr, "Type mismatch line %d\n", lineno);
      break;

    case ERR_INPUTTOOLONG:
      fprintf(g_fperr, "Input too long line %d\n", lineno);
      break;

    case ERR_BADVALUE:
      fprintf(g_fperr, "Bad value at line %d\n", lineno);
      break;

    case ERR_NOTINT:
      fprintf(g_fperr, "Not an integer at line %d\n", lineno);
      break;

    default:
      fprintf(g_fperr, "ERROR line %d\n", lineno);
      break;
    }
}

/****************************************************************************
 * Name: findline
 *
 * Description:
 *   Binary search for a line
 *   Params: no - line number to find
 *   Returns: index of the line, or -1 on fail.
 *
 ****************************************************************************/

static int findline(int no)
{
  int high;
  int low;
  int mid;

  low = 0;
  high = nlines - 1;
  while (high > low + 1)
    {
      mid = (high + low) / 2;
      if (g_lines[mid].no == no)
        {
          return mid;
        }

      if (g_lines[mid].no > no)
        {
          high = mid;
        }
      else
        {
          low = mid;
        }
    }

  if (g_lines[low].no == no)
    {
      mid = low;
    }
  else if (g_lines[high].no == no)
    {
      mid = high;
    }
  else
    {
      mid = -1;
    }

  return mid;
}

/****************************************************************************
 * Name: line
 *
 * Description:
 *   Parse a line. High level parse function
 *
 ****************************************************************************/

static int line(void)
{
  int answer = 0;
  FAR const char *str;

  match(VALUE);

  switch (g_token)
    {
    case PRINT:
      doprint();
      break;

    case LET:
      dolet();
      break;

    case DIM:
      dodim();
      break;

    case IF:
      answer = doif();
      break;

    case GOTO:
      answer = dogoto();
      break;

    case INPUT:
      doinput();
      break;

    case REM:
      dorem();
      return 0;

    case FOR:
      answer = dofor();
      break;

    case NEXT:
      answer = donext();
      break;

    default:
      seterror(ERR_SYNTAX);
      break;
    }

  if (g_token != EOS)
    {
      /* match(VALUE); */
      /* check for a newline */

      str = g_string;
      while (isspace(*str))
        {
          if (*str == '\n')
            {
              break;
            }

          str++;
        }

      if (*str != '\n')
        {
          seterror(ERR_SYNTAX);
        }
    }

  return answer;
}

/****************************************************************************
 * Name: doprint
 *
 * Description:
 *   The PRINT statement
 *
 ****************************************************************************/

static void doprint(void)
{
  FAR char *str;
  double x;

  match(PRINT);

  while (1)
    {
      if (isstring(g_token))
        {
          str = stringexpr();
          if (str)
            {
              fprintf(g_fpout, "%s", str);
              free(str);
            }
        }
      else
        {
          x = expr();
          fprintf(g_fpout, "%g", x);
        }

      if (g_token == COMMA)
        {
          fprintf(g_fpout, " ");
          match(COMMA);
        }
      else
        {
          break;
        }
    }

  if (g_token == SEMICOLON)
    {
      match(SEMICOLON);
      fflush(g_fpout);
    }
  else
    {
      fprintf(g_fpout, "\n");
    }
}

/****************************************************************************
 * Name: dolet
 *
 * Description:
 *   The LET statement
 *
 ****************************************************************************/

static void dolet(void)
{
  struct mb_lvalue_s lv;
  FAR char *temp;

  match(LET);
  lvalue(&lv);
  match(EQUALS);
  switch (lv.type)
    {
    case FLTID:
      *lv.dval = expr();
      break;

    case STRID:
      temp = *lv.sval;
      *lv.sval = stringexpr();
      if (temp)
        {
          free(temp);
        }

      break;

    default:
      break;
    }
}

/****************************************************************************
 * Name: dodim
 *
 * Description:
 *   The DIM statement
 *
 ****************************************************************************/

static void dodim(void)
{
  int ndims = 0;
  double dims[6];
  char name[32];
  int len;
  FAR struct mb_dimvar_s *dimvar;
  int i;
  int size = 1;

  match(DIM);

  switch (g_token)
    {
    case DIMFLTID:
    case DIMSTRID:
      getid(g_string, name, &len);
      match(g_token);
      dims[ndims++] = expr();
      while (g_token == COMMA)
        {
          match(COMMA);
          dims[ndims++] = expr();
          if (ndims > 5)
            {
              seterror(ERR_TOOMANYDIMS);
              return;
            }
        }

      match(CPAREN);

      for (i = 0; i < ndims; i++)
        {
          if (dims[i] < 0 || dims[i] != (int)dims[i])
            {
              seterror(ERR_BADSUBSCRIPT);
              return;
            }
        }

      switch (ndims)
        {
        case 1:
          dimvar = dimension(name, 1, (int)dims[0]);
          break;

        case 2:
          dimvar = dimension(name, 2, (int)dims[0], (int)dims[1]);
          break;

        case 3:
          dimvar = dimension(name, 3, (int)dims[0], (int)dims[1], (int)dims[2]);
          break;

        case 4:
          dimvar =
            dimension(name, 4, (int)dims[0], (int)dims[1], (int)dims[2],
                      (int)dims[3]);
          break;

        case 5:
          dimvar =
            dimension(name, 5, (int)dims[0], (int)dims[1], (int)dims[2],
                      (int)dims[3], (int)dims[4]);
          break;
        }
      break;

    default:
      seterror(ERR_SYNTAX);
      return;
    }

  if (dimvar == 0)
    {
      /* Out of memory */

      seterror(ERR_OUTOFMEMORY);
      return;
    }

  if (g_token == EQUALS)
    {
      match(EQUALS);

      for (i = 0; i < dimvar->ndims; i++)
        {
          size *= dimvar->dim[i];
        }

      switch (dimvar->type)
        {
        case FLTID:
          i = 0;
          dimvar->dval[i++] = expr();
          while (g_token == COMMA && i < size)
            {
              match(COMMA);
              dimvar->dval[i++] = expr();
              if (g_errorflag)
                {
                  break;
                }
            }
          break;

        case STRID:
          i = 0;
          if (dimvar->str[i])
            {
              free(dimvar->str[i]);
            }

          dimvar->str[i++] = stringexpr();

          while (g_token == COMMA && i < size)
            {
              match(COMMA);
              if (dimvar->str[i])
                {
                  free(dimvar->str[i]);
                }

              dimvar->str[i++] = stringexpr();
              if (g_errorflag)
                {
                  break;
                }
            }
          break;
        }

      if (g_token == COMMA)
        {
          seterror(ERR_TOOMANYINITS);
        }
    }
}

/****************************************************************************
 * Name: doif
 *
 * Description:
 *   The IF statement.
 *   If jump taken, returns new line no, else returns 0
 *
 ****************************************************************************/

static int doif(void)
{
  int condition;
  int jump;

  match(IF);
  condition = boolexpr();
  match(THEN);
  jump = integer(expr());
  if (condition)
    {
      return jump;
    }
  else
    {
      return 0;
    }
}

/****************************************************************************
 * Name: dogoto
 *
 * Description:
 *   The GOTO satement
 *   Returns new line number
 *
 ****************************************************************************/

static int dogoto(void)
{
  match(GOTO);
  return integer(expr());
}

/****************************************************************************
 * Name: dofor
 *
 * Description:
 *   The FOR statement.
 *
 *   Pushes the for stack.
 *   Returns line to jump to, or -1 to end program
 *
 ****************************************************************************/

static int dofor(void)
{
  struct mb_lvalue_s lv;
  char id[32];
  char nextid[32];
  int len;
  double initval;
  double toval;
  double stepval;
  FAR const char *savestring;
  int answer;

  match(FOR);
  getid(g_string, id, &len);

  lvalue(&lv);
  if (lv.type != FLTID)
    {
      seterror(ERR_BADTYPE);
      return -1;
    }

  match(EQUALS);
  initval = expr();
  match(TO);
  toval = expr();

  if (g_token == STEP)
    {
      match(STEP);
      stepval = expr();
    }
  else
    {
      stepval = 1.0;
    }

  *lv.dval = initval;

  if (nfors > MAXFORS - 1)
    {
      seterror(ERR_TOOMANYFORS);
      return -1;
    }

  if ((stepval < 0 && initval < toval) ||
      (stepval > 0 && initval > toval))
    {
      savestring = g_string;
      while ((g_string = strchr(g_string, '\n')) != NULL)
        {
          g_errorflag = 0;
          g_token = gettoken(g_string);
          match(VALUE);
          if (g_token == NEXT)
            {
              match(NEXT);
              if (g_token == FLTID || g_token == DIMFLTID)
                {
                  getid(g_string, nextid, &len);
                  if (!strcmp(id, nextid))
                    {
                      answer = getnextline(g_string);
                      g_string = savestring;
                      g_token = gettoken(g_string);
                      return answer ? answer : -1;
                    }
                }
            }
        }

      seterror(ERR_NONEXT);
      return -1;
    }
  else
    {
      strcpy(g_forstack[nfors].id, id);
      g_forstack[nfors].nextline = getnextline(g_string);
      g_forstack[nfors].step = stepval;
      g_forstack[nfors].toval = toval;
      nfors++;
      return 0;
    }
}

/****************************************************************************
 * Name: donext
 *
 * Description:
 *   The NEXT statement
 *   Updates the counting index, and returns line to jump to
 *
 ****************************************************************************/

static int donext(void)
{
  char id[32];
  int len;
  struct mb_lvalue_s lv;

  match(NEXT);

  if (nfors)
    {
      getid(g_string, id, &len);
      lvalue(&lv);
      if (lv.type != FLTID)
        {
          seterror(ERR_BADTYPE);
          return -1;
        }

      *lv.dval += g_forstack[nfors - 1].step;
      if ((g_forstack[nfors - 1].step < 0 &&
           *lv.dval < g_forstack[nfors - 1].toval) ||
          (g_forstack[nfors - 1].step > 0 &&
           *lv.dval > g_forstack[nfors - 1].toval))
        {
          nfors--;
          return 0;
        }
      else
        {
          return g_forstack[nfors - 1].nextline;
        }
    }
  else
    {
      seterror(ERR_NOFOR);
      return -1;
    }
}

/****************************************************************************
 * Name: doinput
 *
 * Description:
 *   The INPUT statement
 *
 ****************************************************************************/

static void doinput(void)
{
  struct mb_lvalue_s lv;
  FAR char *end;

  match(INPUT);
  lvalue(&lv);

  switch (lv.type)
    {
    case FLTID:
      {
        FAR char *ptr;
        int nch;

        /* Copy floating point number to a g_iobuffer.  Skip over leading
         * spaces and terminate with a NUL when a space, tab, newline, EOF,
         * or comma is detected.
         */

        for (nch = 0, ptr = g_iobuffer; nch < (IOBUFSIZE-1); nch++)
          {
            int ch = fgetc(g_fpin);
            if (ch == EOF)
              {
                seterror(ERR_EOF);
                return;
              }

            if (ch == ' ' || ch == '\t' || ch == ',' || ch == '\n')
              {
                ungetc(ch, g_fpin);
                break;
              }
          }

        *ptr = '\0';

        /* Use strtod() to get the floating point value from the substring
         * in g_iobuffer.
         */

        *lv.dval = strtod(g_iobuffer, &ptr);
        if (ptr == g_iobuffer)
          {
            seterror(ERR_SYNTAX);
            return;
          }
      }
      break;

    case STRID:
      {
        if (*lv.sval)
          {
            free(*lv.sval);
            *lv.sval = NULL;
          }

        if (fgets(g_iobuffer, IOBUFSIZE, g_fpin) == 0)
          {
            seterror(ERR_EOF);
            return;
          }

        end = strchr(g_iobuffer, '\n');
        if (!end)
          {
            seterror(ERR_INPUTTOOLONG);
            return;
          }

        *end = 0;
        *lv.sval = mystrdup(g_iobuffer);
        if (!*lv.sval)
          {
            seterror(ERR_OUTOFMEMORY);
            return;
          }
      }
      break;

    default:
      break;
    }
}

/****************************************************************************
 * Name: dorem
 *
 * Description:
 *   The REM statement.
 *   Note is unique as the rest of the line is not parsed
 *
 ****************************************************************************/

static void dorem(void)
{
  match(REM);
  return;
}

/****************************************************************************
 * Name: lvalue
 *
 * Description:
 *   Get an lvalue from the environment
 *   Params: lv - structure to fill.
 *   Notes: missing variables (but not out of range subscripts)
 *          are added to the variable list.
 *
 ****************************************************************************/

static void lvalue(FAR struct mb_lvalue_s *lv)
{
  char name[32];
  int len;
  FAR struct mb_variable_s *var;
  FAR struct mb_dimvar_s *dimvar;
  int index[5];
  FAR void *valptr = 0;
  int type;

  lv->type = SYNTAX_ERROR;
  lv->dval = NULL;
  lv->sval = NULL;

  switch (g_token)
    {
    case FLTID:
      {
        getid(g_string, name, &len);
        match(FLTID);
        var = findvariable(name);
        if (!var)
          {
            var = addfloat(name);
          }

        if (!var)
          {
            seterror(ERR_OUTOFMEMORY);
            return;
          }

        lv->type = FLTID;
        lv->dval = &var->dval;
        lv->sval = NULL;
      }
      break;

    case STRID:
      {
        getid(g_string, name, &len);
        match(STRID);
        var = findvariable(name);
        if (!var)
          {
            var = addstring(name);
          }

        if (!var)
          {
            seterror(ERR_OUTOFMEMORY);
            return;
          }

        lv->type = STRID;
        lv->sval = &var->sval;
        lv->dval = NULL;
      }
      break;

    case DIMFLTID:
    case DIMSTRID:
      {
        type = (g_token == DIMFLTID) ? FLTID : STRID;
        getid(g_string, name, &len);
        match(g_token);
        dimvar = finddimvar(name);
        if (dimvar)
          {
            switch (dimvar->ndims)
              {
              case 1:
                {
                  index[0] = integer(expr());
                  if (g_errorflag == 0)
                    {
                      valptr = getdimvar(dimvar, index[0]);
                    }
                }
                break;

              case 2:
                {
                  index[0] = integer(expr());
                  match(COMMA);
                  index[1] = integer(expr());
                  if (g_errorflag == 0)
                    {
                      valptr = getdimvar(dimvar, index[0], index[1]);
                    }
                }
                break;

              case 3:
                {
                  index[0] = integer(expr());
                  match(COMMA);
                  index[1] = integer(expr());
                  match(COMMA);
                  index[2] = integer(expr());
                  if (g_errorflag == 0)
                    {
                      valptr = getdimvar(dimvar, index[0], index[1], index[2]);
                    }
                }
                break;

              case 4:
                {
                  index[0] = integer(expr());
                  match(COMMA);
                  index[1] = integer(expr());
                  match(COMMA);
                  index[2] = integer(expr());
                  match(COMMA);
                  index[3] = integer(expr());
                  if (g_errorflag == 0)
                    {
                      valptr =
                        getdimvar(dimvar, index[0], index[1], index[2], index[3]);
                    }
                }
                break;

              case 5:
                {
                  index[0] = integer(expr());
                  match(COMMA);
                  index[1] = integer(expr());
                  match(COMMA);
                  index[2] = integer(expr());
                  match(COMMA);
                  index[3] = integer(expr());
                  match(COMMA);
                  index[4] = integer(expr());
                  if (g_errorflag == 0)
                    {
                      valptr =
                        getdimvar(dimvar, index[0], index[1], index[2], index[3]);
                    }
                }
                break;
             }

           match(CPAREN);
         }
      else
        {
          seterror(ERR_NOSUCHVARIABLE);
          return;
        }

      if (valptr)
        {
          lv->type = type;
          if (type == FLTID)
            {
              lv->dval = valptr;
            }
          else if (type == STRID)
            {
              lv->sval = valptr;
            }
          else
            {
              assert(0);
            }
        }
      }
      break;

    default:
      seterror(ERR_SYNTAX);
    }
}

/****************************************************************************
 * Name: boolexpr
 *
 * Description:
 *   Parse a boolean expression
 *   Consists of expressions or strings and relational operators,
 *   and parentheses
 *
 ****************************************************************************/

static int boolexpr(void)
{
  int left;
  int right;

  left = boolfactor();

  while (1)
    {
      switch (g_token)
        {
        case AND:
          match(AND);
          right = boolexpr();
          return (left && right) ? 1 : 0;

        case OR:
          match(OR);
          right = boolexpr();
          return (left || right) ? 1 : 0;

        default:
          return left;
        }
    }
}

/****************************************************************************
 * Name: boolfactor
 *
 * Description:
 *   Boolean factor, consists of expression relop expression
 *   or string relop string, or ( boolexpr() )
 *
 ****************************************************************************/

static int boolfactor(void)
{
  int answer;
  double left;
  double right;
  int op;
  FAR char *strleft;
  FAR char *strright;
  int cmp;

  switch (g_token)
    {
    case OPAREN:
      match(OPAREN);
      answer = boolexpr();
      match(CPAREN);
      break;

    default:
      if (isstring(g_token))
        {
          strleft = stringexpr();
          op = relop();
          strright = stringexpr();
          if (!strleft || !strright)
            {
              if (strleft)
                {
                  free(strleft);
                }

              if (strright)
                {
                  free(strright);
                }

              return 0;
            }
          cmp = strcmp(strleft, strright);
          switch (op)
            {
            case ROP_EQ:
              answer = cmp == 0 ? 1 : 0;
              break;

            case ROP_NEQ:
              answer = cmp == 0 ? 0 : 1;
              break;

            case ROP_LT:
              answer = cmp < 0 ? 1 : 0;
              break;

            case ROP_LTE:
              answer = cmp <= 0 ? 1 : 0;
              break;

            case ROP_GT:
              answer = cmp > 0 ? 1 : 0;
              break;

            case ROP_GTE:
              answer = cmp >= 0 ? 1 : 0;
              break;

            default:
              answer = 0;
            }

          free(strleft);
          free(strright);
        }
      else
        {
          left = expr();
          op = relop();
          right = expr();
          switch (op)
            {
            case ROP_EQ:
              answer = (left == right) ? 1 : 0;
              break;

            case ROP_NEQ:
              answer = (left != right) ? 1 : 0;
              break;

            case ROP_LT:
              answer = (left < right) ? 1 : 0;
              break;

            case ROP_LTE:
              answer = (left <= right) ? 1 : 0;
              break;

            case ROP_GT:
              answer = (left > right) ? 1 : 0;
              break;

            case ROP_GTE:
              answer = (left >= right) ? 1 : 0;
              break;

            default:
              g_errorflag = 1;
              return 0;
            }
        }
    }

  return answer;
}

/****************************************************************************
 * Name: relop
 *
 * Description:
 *   Get a relational operator
 *   Returns operator parsed or SYNTAX_ERROR
 *
 ****************************************************************************/

static int relop(void)
{
  switch (g_token)
    {
    case EQUALS:
      match(EQUALS);
      return ROP_EQ;

    case GREATER:
      match(GREATER);
      if (g_token == EQUALS)
        {
          match(EQUALS);
          return ROP_GTE;
        }

      return ROP_GT;

    case LESS:
      match(LESS);
      if (g_token == EQUALS)
        {
          match(EQUALS);
          return ROP_LTE;
        }
      else if (g_token == GREATER)
        {
          match(GREATER);
          return ROP_NEQ;
        }

      return ROP_LT;

    default:
      seterror(ERR_SYNTAX);
      return SYNTAX_ERROR;
    }
}

/****************************************************************************
 * Name: expr
 *
 * Description:
 *   Parses an expression
 *
 ****************************************************************************/

static double expr(void)
{
  double left;
  double right;

  left = term();

  while (1)
    {
      switch (g_token)
        {
        case PLUS:
          match(PLUS);
          right = term();
          left += right;
          break;

        case MINUS:
          match(MINUS);
          right = term();
          left -= right;
          break;

        default:
          return left;
        }
    }
}

/****************************************************************************
 * Name: term
 *
 * Description:
 *   Parses a term
 *
 ****************************************************************************/

static double term(void)
{
  double left;
  double right;

  left = factor();

  while (1)
    {
      switch (g_token)
        {
        case MULT:
          match(MULT);
          right = factor();
          left *= right;
          break;

        case DIV:
          match(DIV);
          right = factor();
          if (right != 0.0)
            {
              left /= right;
            }
          else
            {
              seterror(ERR_DIVIDEBYZERO);
            }
          break;

        case MOD:
          match(MOD);
          right = factor();
          left = fmod(left, right);
          break;

        default:
          return left;
        }
    }
}

/****************************************************************************
 * Name: factor
 *
 * Description:
 *   Parses a factor
 *
 ****************************************************************************/

static double factor(void)
{
  double answer = 0;
  FAR char *str;
  FAR char *end;
  int len;

  switch (g_token)
    {
    case OPAREN:
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      break;

    case VALUE:
      answer = getvalue(g_string, &len);
      match(VALUE);
      break;

    case MINUS:
      match(MINUS);
      answer = -factor();
      break;

    case FLTID:
      answer = variable();
      break;

    case DIMFLTID:
      answer = dimvariable();
      break;

    case E:
      answer = exp(1.0);
      match(E);
      break;

    case PI:
      answer = acos(0.0) * 2.0;
      match(PI);
      break;

    case SIN:
      match(SIN);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      answer = sin(answer);
      break;

    case COS:
      match(COS);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      answer = cos(answer);
      break;

    case TAN:
      match(TAN);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      answer = tan(answer);
      break;

    case LN:
      match(LN);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      if (answer > 0)
        {
          answer = log(answer);
        }
      else
        {
          seterror(ERR_NEGLOG);
        }
      break;

    case POW:
      match(POW);
      match(OPAREN);
      answer = expr();
      match(COMMA);
      answer = pow(answer, expr());
      match(CPAREN);
      break;

    case SQRT:
      match(SQRT);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      if (answer >= 0.0)
        {
          answer = sqrt(answer);
        }
      else
        {
          seterror(ERR_NEGSQRT);
        }
      break;

    case ABS:
      match(ABS);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      answer = fabs(answer);
      break;

    case LEN:
      match(LEN);
      match(OPAREN);
      str = stringexpr();
      match(CPAREN);
      if (str)
        {
          answer = strlen(str);
          free(str);
        }
      else
        {
          answer = 0;
        }
      break;

    case ASCII:
      match(ASCII);
      match(OPAREN);
      str = stringexpr();
      match(CPAREN);
      if (str)
        {
          answer = *str;
          free(str);
        }
      else
        {
          answer = 0;
        }
      break;

    case ASIN:
      match(ASIN);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      if (answer >= -1 && answer <= 1)
        {
          answer = asin(answer);
        }
      else
        {
          seterror(ERR_BADSINCOS);
        }
      break;

    case ACOS:
      match(ACOS);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      if (answer >= -1 && answer <= 1)
        {
          answer = acos(answer);
        }
      else
        {
          seterror(ERR_BADSINCOS);
        }
      break;

    case ATAN:
      match(ATAN);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      answer = atan(answer);
      break;

    case INT:
      match(INT);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      answer = floor(answer);
      break;

    case RND:
      match(RND);
      match(OPAREN);
      answer = expr();
      match(CPAREN);
      answer = integer(answer);
      if (answer > 1)
        {
          answer = floor(rand() / (RAND_MAX + 1.0) * answer);
        }
      else if (answer == 1)
        {
          answer = rand() / (RAND_MAX + 1.0);
        }
      else
        {
          if (answer < 0)
            {
              srand((unsigned)-answer);
            }
          answer = 0;
        }
      break;

    case VAL:
      match(VAL);
      match(OPAREN);
      str = stringexpr();
      match(CPAREN);
      if (str)
        {
          answer = strtod(str, 0);
          free(str);
        }
      else
        {
          answer = 0;
        }
      break;

    case VALLEN:
      match(VALLEN);
      match(OPAREN);
      str = stringexpr();
      match(CPAREN);
      if (str)
        {
          strtod(str, &end);
          answer = end - str;
          free(str);
        }
      else
        {
          answer = 0.0;
        }
      break;

    case INSTR:
      answer = instr();
      break;

    default:
      if (isstring(g_token))
        {
          seterror(ERR_TYPEMISMATCH);
        }
      else
        {
          seterror(ERR_SYNTAX);
        }
      break;
    }

  while (g_token == SHRIEK)
    {
      match(SHRIEK);
      answer = factorial(answer);
    }

  return answer;
}

/****************************************************************************
 * Name: instr
 *
 * Description:
 *   Calcualte the INSTR() function.
 *
 ****************************************************************************/

static double instr(void)
{
  FAR char *str;
  FAR char *substr;
  FAR char *end;
  double answer = 0;
  int offset;

  match(INSTR);
  match(OPAREN);
  str = stringexpr();
  match(COMMA);
  substr = stringexpr();
  match(COMMA);
  offset = integer(expr());
  offset--;
  match(CPAREN);

  if (!str || !substr)
    {
      if (str)
        {
          free(str);
        }

      if (substr)
        {
          free(substr);
        }

      return 0;
    }

  if (offset >= 0 && offset < (int)strlen(str))
    {
      end = strstr(str + offset, substr);
      if (end)
        {
          answer = end - str + 1.0;
        }
    }

  free(str);
  free(substr);
  return answer;
}

/****************************************************************************
 * Name: variable
 *
 * Description:
 *   Get the value of a scalar variable from string
 *   matches FLTID
 *
 *
 ****************************************************************************/

static double variable(void)
{
  FAR struct mb_variable_s *var;
  char id[32];
  int len;

  getid(g_string, id, &len);
  match(FLTID);
  var = findvariable(id);
  if (var)
    {
      return var->dval;
    }
  else
    {
      seterror(ERR_NOSUCHVARIABLE);
      return 0.0;
    }
}

/****************************************************************************
 * Name: dimvariable
 *
 * Description:
 *   Get value of a dimensioned variable from string.
 *   matches DIMFLTID
 *
 ****************************************************************************/

static double dimvariable(void)
{
  FAR struct mb_dimvar_s *dimvar;
  char id[32];
  int len;
  int index[5];
  FAR double *answer = NULL;

  getid(g_string, id, &len);
  match(DIMFLTID);
  dimvar = finddimvar(id);
  if (!dimvar)
    {
      seterror(ERR_NOSUCHVARIABLE);
      return 0.0;
    }

  if (dimvar)
    {
      switch (dimvar->ndims)
        {
        case 1:
          index[0] = integer(expr());
          answer = getdimvar(dimvar, index[0]);
          break;

        case 2:
          index[0] = integer(expr());
          match(COMMA);
          index[1] = integer(expr());
          answer = getdimvar(dimvar, index[0], index[1]);
          break;

        case 3:
          index[0] = integer(expr());
          match(COMMA);
          index[1] = integer(expr());
          match(COMMA);
          index[2] = integer(expr());
          answer = getdimvar(dimvar, index[0], index[1], index[2]);
          break;

        case 4:
          index[0] = integer(expr());
          match(COMMA);
          index[1] = integer(expr());
          match(COMMA);
          index[2] = integer(expr());
          match(COMMA);
          index[3] = integer(expr());
          answer = getdimvar(dimvar, index[0], index[1], index[2], index[3]);
          break;

        case 5:
          index[0] = integer(expr());
          match(COMMA);
          index[1] = integer(expr());
          match(COMMA);
          index[2] = integer(expr());
          match(COMMA);
          index[3] = integer(expr());
          match(COMMA);
          index[4] = integer(expr());
          answer =
            getdimvar(dimvar, index[0], index[1], index[2], index[3], index[4]);
          break;
        }

      match(CPAREN);
    }

  if (answer != NULL)
    {
      return *answer;
    }

  return 0.0;
}

/****************************************************************************
 * Name: findvariable
 *
 * Description:
 *   Find a scalar variable invariables list
 *   Params: id - id to get
 *   Returns: pointer to that entry, 0 on fail
 *
 ****************************************************************************/

static FAR struct mb_variable_s *findvariable(FAR const char *id)
{
  int i;

  for (i = 0; i < g_nvariables; i++)
    {
      if (!strcmp(g_variables[i].id, id))
        {
          return &g_variables[i];
        }
    }

  return 0;
}

/****************************************************************************
 * Name: finddimvar
 *
 * Description:
 *   Get a dimensioned array by name
 *   Params: id (includes opening parenthesis)
 *   Returns: pointer to array entry or 0 on fail
 *
 ****************************************************************************/

static struct mb_dimvar_s *finddimvar(FAR const char *id)
{
  int i;

  for (i = 0; i < g_ndimvariables; i++)
    {
      if (!strcmp(g_dimvariables[i].id, id))
        {
          return &g_dimvariables[i];
        }
    }

  return 0;
}

/****************************************************************************
 * Name: dimension
 *
 * Description:
 *   Dimension an array.
 *   Params: id - the id of the array (include leading ()
 *           ndims - number of dimension (1-5)
 *         ... - integers giving dimension size,
 *
 ****************************************************************************/

static FAR struct mb_dimvar_s *dimension(FAR const char *id, int ndims, ...)
{
  FAR struct mb_dimvar_s *dv;
  va_list vargs;
  int size = 1;
  int oldsize = 1;
  int i;
  int dimensions[5];
  FAR double *dtemp;
  FAR char **stemp;

  assert(ndims <= 5);
  if (ndims > 5)
    {
      return 0;
    }

  dv = finddimvar(id);
  if (!dv)
    {
      dv = adddimvar(id);
    }

  if (!dv)
    {
      seterror(ERR_OUTOFMEMORY);
      return 0;
    }

  if (dv->ndims)
    {
      for (i = 0; i < dv->ndims; i++)
        {
          oldsize *= dv->dim[i];
        }
    }
  else
    {
      oldsize = 0;
    }

  va_start(vargs, ndims);
  for (i = 0; i < ndims; i++)
    {
      dimensions[i] = va_arg(vargs, int);
      size *= dimensions[i];
    }

  va_end(vargs);

  switch (dv->type)
    {
    case FLTID:
      dtemp = realloc(dv->dval, size * sizeof(double));
      if (dtemp)
        {
          dv->dval = dtemp;
        }
      else
        {
          seterror(ERR_OUTOFMEMORY);
          return 0;
        }
      break;

    case STRID:
      if (dv->str)
        {
          for (i = size; i < oldsize; i++)
            {
              if (dv->str[i])
                {
                  free(dv->str[i]);
                  dv->str[i] = 0;
                }
            }
        }

      stemp = realloc(dv->str, size * sizeof(char *));
      if (stemp)
        {
          dv->str = stemp;
          for (i = oldsize; i < size; i++)
            {
              dv->str[i] = 0;
            }
        }
      else
        {
          for (i = 0; i < oldsize; i++)
            {
              if (dv->str[i])
                {
                  free(dv->str[i]);
                  dv->str[i] = 0;
                }
            }

          seterror(ERR_OUTOFMEMORY);
          return 0;
        }
      break;

    default:
      assert(0);
    }

  for (i = 0; i < 5; i++)
    {
      dv->dim[i] = dimensions[i];
    }

  dv->ndims = ndims;

  return dv;
}

/****************************************************************************
 * Name: getdimvar
 *
 * Description:
 *   Get the address of a dimensioned array element.
 *   Works for both string and real arrays.
 *   Params: dv - the array's entry in variable list
 *           ... - integers telling which array element to get
 *   Returns: the address of that element, 0 on fail
 *
 ****************************************************************************/

static FAR void *getdimvar(FAR struct mb_dimvar_s *dv, ...)
{
  va_list vargs;
  int index[5];
  int i;
  FAR void *answer = 0;

  va_start(vargs, dv);
  for (i = 0; i < dv->ndims; i++)
    {
      index[i] = va_arg(vargs, int);
      index[i]--;
    }

  va_end(vargs);

  for (i = 0; i < dv->ndims; i++)
    {
      if (index[i] >= dv->dim[i] || index[i] < 0)
        {
          seterror(ERR_BADSUBSCRIPT);
          return 0;
        }
    }

  if (dv->type == FLTID)
    {
      switch (dv->ndims)
        {
        case 1:
          answer = &dv->dval[index[0]];
          break;

        case 2:
          answer = &dv->dval[index[1] * dv->dim[0] + index[0]];
          break;
        case 3:
          answer = &dv->dval[index[2] * (dv->dim[0] * dv->dim[1])
                             + index[1] * dv->dim[0] + index[0]];
          break;

        case 4:
          answer =
            &dv->dval[index[3] * (dv->dim[0] + dv->dim[1] + dv->dim[2]) +
                      index[2] * (dv->dim[0] * dv->dim[1]) +
                      index[1] * dv->dim[0] + index[0]];
          break;

        case 5:
          answer =
            &dv->dval[index[4] *
                      (dv->dim[0] + dv->dim[1] + dv->dim[2] +
                       dv->dim[3]) + index[3] * (dv->dim[0] + dv->dim[1] +
                                                 dv->dim[2]) +
                      index[2] * (dv->dim[0] + dv->dim[1]) +
                      index[1] * dv->dim[0] + index[0]];
          break;
        }
    }
  else if (dv->type == STRID)
    {
      switch (dv->ndims)
        {
        case 1:
          answer = &dv->str[index[0]];
          break;

        case 2:
          answer = &dv->str[index[1] * dv->dim[0] + index[0]];
          break;

        case 3:
          answer = &dv->str[index[2] * (dv->dim[0] * dv->dim[1])
                            + index[1] * dv->dim[0] + index[0]];
          break;

        case 4:
          answer =
            &dv->str[index[3] * (dv->dim[0] + dv->dim[1] + dv->dim[2]) +
                     index[2] * (dv->dim[0] * dv->dim[1]) +
                     index[1] * dv->dim[0] + index[0]];
          break;

        case 5:
          answer =
            &dv->str[index[4] *
                     (dv->dim[0] + dv->dim[1] + dv->dim[2] + dv->dim[3]) +
                     index[3] * (dv->dim[0] + dv->dim[1] + dv->dim[2]) +
                     index[2] * (dv->dim[0] + dv->dim[1]) +
                     index[1] * dv->dim[0] + index[0]];
          break;
        }
    }

  return answer;
}

/****************************************************************************
 * Name: addfloat
 *
 * Description:
 *   Add a real varaible to our variable list
 *   Params: id - id of varaible to add.
 *   Returns: pointer to new entry in table
 *
 ****************************************************************************/

static FAR struct mb_variable_s *addfloat(FAR const char *id)
{
  FAR struct mb_variable_s *vars;

  vars =
    realloc(g_variables, (g_nvariables + 1) * sizeof(struct mb_variable_s));
  if (vars)
    {
      g_variables = vars;
      strcpy(g_variables[g_nvariables].id, id);
      g_variables[g_nvariables].dval = 0.0;
      g_variables[g_nvariables].sval = NULL;
      g_nvariables++;
      return &g_variables[g_nvariables - 1];
    }
  else
    {
      seterror(ERR_OUTOFMEMORY);
    }

  return 0;
}

/****************************************************************************
 * Name: addstring
 *
 * Description:
 *   Add a string variable to table.
 *   Params: id - id of variable to get (including trailing $)
 *   Returns: pointer to new entry in table, 0 on fail.
 *
 ****************************************************************************/

static FAR struct mb_variable_s *addstring(FAR const char *id)
{
  FAR struct mb_variable_s *vars;

  vars =
    realloc(g_variables, (g_nvariables + 1) * sizeof(struct mb_variable_s));
  if (vars)
    {
      g_variables = vars;
      strcpy(g_variables[g_nvariables].id, id);
      g_variables[g_nvariables].sval = NULL;
      g_variables[g_nvariables].dval = 0.0;
      g_nvariables++;
      return &g_variables[g_nvariables - 1];
    }
  else
    {
      seterror(ERR_OUTOFMEMORY);
    }

  return 0;
}

/****************************************************************************
 * Name: adddimvar
 *
 * Description:
 *   Add a new array to our symbol table.
 *   Params: id - id of array (include leading ()
 *   Returns: pointer to new entry, 0 on fail.
 *
 ****************************************************************************/

static FAR struct mb_dimvar_s *adddimvar(FAR const char *id)
{
  FAR struct mb_dimvar_s *vars;

  vars =
    realloc(g_dimvariables, (g_ndimvariables + 1) * sizeof(struct mb_dimvar_s));
  if (vars)
    {
      g_dimvariables = vars;
      strcpy(g_dimvariables[g_ndimvariables].id, id);
      g_dimvariables[g_ndimvariables].dval  = NULL;
      g_dimvariables[g_ndimvariables].str   = NULL;
      g_dimvariables[g_ndimvariables].ndims = 0;
      g_dimvariables[g_ndimvariables].type  = strchr(id, '$') ? STRID : FLTID;
      g_ndimvariables++;
      return &g_dimvariables[g_ndimvariables - 1];
    }
  else
    {
      seterror(ERR_OUTOFMEMORY);
    }

  return 0;
}

/****************************************************************************
 * Name: stringexpr
 *
 * Description:
 *   High level string parsing function.
 *   Returns: a malloced pointer, or 0 on error condition.
 *   caller must free!
 *
 ****************************************************************************/

static FAR char *stringexpr(void)
{
  FAR char *left;
  FAR char *right;
  FAR char *temp;

  switch (g_token)
    {
    case DIMSTRID:
      left = mystrdup(stringdimvar());
      break;

    case STRID:
      left = mystrdup(stringvar());
      break;

    case QUOTE:
      left = stringliteral();
      break;

    case CHRSTRING:
      left = chrstring();
      break;

    case STRSTRING:
      left = strstring();
      break;

    case LEFTSTRING:
      left = leftstring();
      break;

    case RIGHTSTRING:
      left = rightstring();
      break;

    case MIDSTRING:
      left = midstring();
      break;

    case STRINGSTRING:
      left = stringstring();
      break;

    default:
      if (!isstring(g_token))
        {
          seterror(ERR_TYPEMISMATCH);
        }
      else
        {
          seterror(ERR_SYNTAX);
        }

      return mystrdup("");
    }

  if (!left)
    {
      seterror(ERR_OUTOFMEMORY);
      return 0;
    }

  switch (g_token)
    {
    case PLUS:
      match(PLUS);
      right = stringexpr();
      if (right)
        {
          temp = mystrconcat(left, right);
          free(right);
          if (temp)
            {
              free(left);
              left = temp;
            }
          else
            {
              seterror(ERR_OUTOFMEMORY);
            }
        }
      else
        {
          seterror(ERR_OUTOFMEMORY);
        }

      break;

    default:
      return left;
    }

  return left;
}

/****************************************************************************
 * Name: chrstring
 *
 * Description:
 *   Parse the CHR$ token
 *
 ****************************************************************************/

static FAR char *chrstring(void)
{
  double x;
  FAR char *answer;

  match(CHRSTRING);
  match(OPAREN);
  x = integer(expr());
  match(CPAREN);

  g_iobuffer[0] = (char)x;
  g_iobuffer[1] = 0;
  answer = mystrdup(g_iobuffer);

  if (!answer)
    {
      seterror(ERR_OUTOFMEMORY);
    }

  return answer;
}

/****************************************************************************
 * Name: strstring
 *
 * Description:
 *   Parse the STR$ token
 *
 ****************************************************************************/

static FAR char *strstring(void)
{
  double x;
  FAR char *answer;

  match(STRSTRING);
  match(OPAREN);
  x = expr();
  match(CPAREN);

  sprintf(g_iobuffer, "%g", x);
  answer = mystrdup(g_iobuffer);
  if (!answer)
    {
      seterror(ERR_OUTOFMEMORY);
    }

  return answer;
}

/****************************************************************************
 * Name: leftstring
 *
 * Description:
 *   Parse the LEFT$ token
 *
 ****************************************************************************/

static FAR char *leftstring(void)
{
  FAR char *str;
  int x;
  FAR char *answer;

  match(LEFTSTRING);
  match(OPAREN);
  str = stringexpr();
  if (!str)
    {
      return 0;
    }

  match(COMMA);
  x = integer(expr());
  match(CPAREN);

  if (x > (int)strlen(str))
    {
      return str;
    }

  if (x < 0)
    {
      seterror(ERR_ILLEGALOFFSET);
      return str;
    }

  str[x] = 0;
  answer = mystrdup(str);
  free(str);
  if (!answer)
    {
      seterror(ERR_OUTOFMEMORY);
    }

  return answer;
}

/****************************************************************************
 * Name: rightstring
 *
 * Description:
 *   Parse the RIGHT$ token
 *
 ****************************************************************************/

static FAR char *rightstring(void)
{
  int x;
  FAR char *str;
  FAR char *answer;

  match(RIGHTSTRING);
  match(OPAREN);
  str = stringexpr();
  if (!str)
    {
      return 0;
    }

  match(COMMA);
  x = integer(expr());
  match(CPAREN);

  if (x > (int)strlen(str))
    {
      return str;
    }

  if (x < 0)
    {
      seterror(ERR_ILLEGALOFFSET);
      return str;
    }

  answer = mystrdup(&str[strlen(str) - x]);
  free(str);
  if (!answer)
    {
      seterror(ERR_OUTOFMEMORY);
    }

  return answer;
}

/****************************************************************************
 * Name: midstring
 *
 * Description:
 *   Parse the MID$ token
 *
 ****************************************************************************/

static FAR char *midstring(void)
{
  FAR char *str;
  int x;
  int len;
  FAR char *answer;
  FAR char *temp;

  match(MIDSTRING);
  match(OPAREN);
  str = stringexpr();
  match(COMMA);
  x = integer(expr());
  match(COMMA);
  len = integer(expr());
  match(CPAREN);

  if (!str)
    {
      return 0;
    }

  if (len == -1)
    {
      len = strlen(str) - x + 1;
    }

  if (x > (int)strlen(str) || len < 1)
    {
      free(str);
      answer = mystrdup("");
      if (!answer)
        {
          seterror(ERR_OUTOFMEMORY);
        }

      return answer;
    }

  if (x < 1.0)
    {
      seterror(ERR_ILLEGALOFFSET);
      return str;
    }

  temp = &str[x - 1];

  answer = malloc(len + 1);
  if (!answer)
    {
      seterror(ERR_OUTOFMEMORY);
      return str;
    }

  strncpy(answer, temp, len);
  answer[len] = 0;
  free(str);
  return answer;
}

/****************************************************************************
 * Name: stringstring
 *
 * Description:
 *   Parse the string$ token
 *
 ****************************************************************************/

static FAR char *stringstring(void)
{
  int x;
  FAR char *str;
  FAR char *answer;
  int len;
  int N;
  int i;

  match(STRINGSTRING);
  match(OPAREN);
  x = integer(expr());
  match(COMMA);
  str = stringexpr();
  match(CPAREN);

  if (!str)
    {
      return 0;
    }

  N = x;

  if (N < 1)
    {
      free(str);
      answer = mystrdup("");
      if (!answer)
        {
          seterror(ERR_OUTOFMEMORY);
        }

      return answer;
    }

  len = strlen(str);
  answer = malloc(N * len + 1);
  if (!answer)
    {
      free(str);
      seterror(ERR_OUTOFMEMORY);
      return 0;
    }

  for (i = 0; i < N; i++)
    {
      strcpy(answer + len * i, str);
    }

  free(str);
  return answer;
}

/****************************************************************************
 * Name: stringdimvar
 *
 * Description:
 *   Read a dimensioned string variable from input.
 *   Returns: pointer to string (not malloced)
 *
 ****************************************************************************/

static FAR char *stringdimvar(void)
{
  char id[32];
  int len;
  FAR struct mb_dimvar_s *dimvar;
  FAR char **answer = NULL;
  int index[5];

  getid(g_string, id, &len);
  match(DIMSTRID);
  dimvar = finddimvar(id);

  if (dimvar)
    {
      switch (dimvar->ndims)
        {
        case 1:
          index[0] = integer(expr());
          answer = getdimvar(dimvar, index[0]);
          break;

        case 2:
          index[0] = integer(expr());
          match(COMMA);
          index[1] = integer(expr());
          answer = getdimvar(dimvar, index[0], index[1]);
          break;

        case 3:
          index[0] = integer(expr());
          match(COMMA);
          index[1] = integer(expr());
          match(COMMA);
          index[2] = integer(expr());
          answer = getdimvar(dimvar, index[0], index[1], index[2]);
          break;

        case 4:
          index[0] = integer(expr());
          match(COMMA);
          index[1] = integer(expr());
          match(COMMA);
          index[2] = integer(expr());
          match(COMMA);
          index[3] = integer(expr());
          answer = getdimvar(dimvar, index[0], index[1], index[2], index[3]);
          break;

        case 5:
          index[0] = integer(expr());
          match(COMMA);
          index[1] = integer(expr());
          match(COMMA);
          index[2] = integer(expr());
          match(COMMA);
          index[3] = integer(expr());
          match(COMMA);
          index[4] = integer(expr());
          answer =
            getdimvar(dimvar, index[0], index[1], index[2], index[3], index[4]);
          break;
        }

      match(CPAREN);
    }
  else
    {
      seterror(ERR_NOSUCHVARIABLE);
    }

  if (!g_errorflag)
    {
      if (answer != NULL && *answer != NULL)
        {
          return *answer;
        }
    }

  return "";
}

/****************************************************************************
 * Name: stringvar
 *
 * Description:
 *   Parse a string variable.
 *   Returns: pointer to string (not malloced)
 *
 ****************************************************************************/

static FAR char *stringvar(void)
{
  char id[32];
  int len;
  FAR struct mb_variable_s *var;

  getid(g_string, id, &len);
  match(STRID);
  var = findvariable(id);
  if (var)
    {
      if (var->sval)
        {
          return var->sval;
        }

      return "";
    }

  seterror(ERR_NOSUCHVARIABLE);
  return "";
}

/****************************************************************************
 * Name: stringliteral
 *
 * Description:
 *   Parse a string literal
 *   Returns: malloced string literal
 *   Notes: newlines aren't allowed in literals, but blind
 *          concatenation across newlines is.
 *
 ****************************************************************************/

static FAR char *stringliteral(void)
{
  int len = 1;
  FAR char *answer = 0;
  FAR char *temp;
  FAR char *substr;
  FAR char *end;

  while (g_token == QUOTE)
    {
      while (isspace(*g_string))
        {
          g_string++;
        }

      end = mystrend(g_string, '"');
      if (end)
        {
          len = end - g_string;
          substr = malloc(len);
          if (!substr)
            {
              seterror(ERR_OUTOFMEMORY);
              return answer;
            }

          mystrgrablit(substr, g_string);
          if (answer)
            {
              temp = mystrconcat(answer, substr);
              free(substr);
              free(answer);
              answer = temp;
              if (!answer)
                {
                  seterror(ERR_OUTOFMEMORY);
                  return answer;
                }
            }
          else
            {
              answer = substr;
            }

          g_string = end;
        }
      else
        {
          seterror(ERR_SYNTAX);
          return answer;
        }

      match(QUOTE);
    }

  return answer;
}

/****************************************************************************
 * Name: integer
 *
 * Description:
 *   Cast a double to an integer, triggering errors if out of range
 *
 ****************************************************************************/

static int integer(double x)
{
  if (x < INT_MIN || x > INT_MAX)
    {
      seterror(ERR_BADVALUE);
    }

  if (x != floor(x))
    {
      seterror(ERR_NOTINT);
    }

  return (int)x;
}

/****************************************************************************
 * Name: match
 *
 * Description:
 *   Check that we have a token of the passed type (if not set the g_errorflag)
 *   Move parser on to next token. Sets token and string.
 *
 ****************************************************************************/

static void match(int tok)
{
  if (g_token != tok)
    {
      seterror(ERR_SYNTAX);
      return;
    }

  while (isspace(*g_string))
    {
      g_string++;
    }

  g_string += tokenlen(g_string, g_token);
  g_token = gettoken(g_string);
  if (g_token == SYNTAX_ERROR)
    {
      seterror(ERR_SYNTAX);
    }
}

/****************************************************************************
 * Name: seterror
 *
 * Description:
 *   Set the errorflag.
 *   Params: errorcode - the error.
 *   Notes: ignores error cascades
 *
 ****************************************************************************/

static void seterror(int errorcode)
{
  if (g_errorflag == 0 || errorcode == 0)
    {
      g_errorflag = errorcode;
    }
}

/****************************************************************************
 * Name: getnextline
 *
 * Description:
 *   Get the next line number
 *   Params: str - pointer to parse string
 *   Returns: line no of next line, 0 if end
 *   Notes: goes to newline, then finds
 *          first line starting with a digit.
 *
 ****************************************************************************/

static int getnextline(FAR const char *str)
{
  while (*str)
    {
      while (*str && *str != '\n')
        {
          str++;
        }

      if (*str == 0)
        {
          return 0;
        }

      str++;
      if (isdigit(*str))
        {
          return atoi(str);
        }
    }

  return 0;
}

/****************************************************************************
 * Name: gettoken
 *
 * Description:
 *   Get a token from the string
 *   Params: str - string to read token from
 *   Notes: ignores white space between tokens
 *
 ****************************************************************************/

static int gettoken(FAR const char *str)
{
  while (isspace(*str))
    {
      str++;
    }

  if (isdigit(*str))
    {
      return VALUE;
    }

  switch (*str)
    {
    case 0:
      return EOS;

    case '\n':
      return EOL;

    case '/':
      return DIV;

    case '*':
      return MULT;

    case '(':
      return OPAREN;

    case ')':
      return CPAREN;

    case '+':
      return PLUS;

    case '-':
      return MINUS;

    case '!':
      return SHRIEK;

    case ',':
      return COMMA;

    case ';':
      return SEMICOLON;

    case '"':
      return QUOTE;

    case '=':
      return EQUALS;

    case '<':
      return LESS;

    case '>':
      return GREATER;

    default:
      if (!strncmp(str, "e", 1) && !isalnum(str[1]))
        {
          return E;
        }

      if (isupper(*str))
        {
          if (!strncmp(str, "SIN", 3) && !isalnum(str[3]))
            {
              return SIN;
            }

          if (!strncmp(str, "COS", 3) && !isalnum(str[3]))
            {
              return COS;
            }

          if (!strncmp(str, "TAN", 3) && !isalnum(str[3]))
            {
              return TAN;
            }

          if (!strncmp(str, "LN", 2) && !isalnum(str[2]))
            {
              return LN;
            }

          if (!strncmp(str, "POW", 3) && !isalnum(str[3]))
            {
              return POW;
            }

          if (!strncmp(str, "PI", 2) && !isalnum(str[2]))
            {
              return PI;
            }

          if (!strncmp(str, "SQRT", 4) && !isalnum(str[4]))
            {
              return SQRT;
            }

          if (!strncmp(str, "PRINT", 5) && !isalnum(str[5]))
            {
              return PRINT;
            }

          if (!strncmp(str, "LET", 3) && !isalnum(str[3]))
            {
              return LET;
            }

          if (!strncmp(str, "DIM", 3) && !isalnum(str[3]))
            {
              return DIM;
            }

          if (!strncmp(str, "IF", 2) && !isalnum(str[2]))
            {
              return IF;
            }

          if (!strncmp(str, "THEN", 4) && !isalnum(str[4]))
            {
              return THEN;
            }

          if (!strncmp(str, "AND", 3) && !isalnum(str[3]))
            {
              return AND;
            }

          if (!strncmp(str, "OR", 2) && !isalnum(str[2]))
            {
              return OR;
            }

          if (!strncmp(str, "GOTO", 4) && !isalnum(str[4]))
            {
              return GOTO;
            }

          if (!strncmp(str, "INPUT", 5) && !isalnum(str[5]))
            {
              return INPUT;
            }

          if (!strncmp(str, "REM", 3) && !isalnum(str[3]))
            {
              return REM;
            }

          if (!strncmp(str, "FOR", 3) && !isalnum(str[3]))
            {
              return FOR;
            }

          if (!strncmp(str, "TO", 2) && !isalnum(str[2]))
            {
              return TO;
            }

          if (!strncmp(str, "NEXT", 4) && !isalnum(str[4]))
            {
              return NEXT;
            }

          if (!strncmp(str, "STEP", 4) && !isalnum(str[4]))
            {
              return STEP;
            }

          if (!strncmp(str, "MOD", 3) && !isalnum(str[3]))
            {
              return MOD;
            }

          if (!strncmp(str, "ABS", 3) && !isalnum(str[3]))
            {
              return ABS;
            }

          if (!strncmp(str, "LEN", 3) && !isalnum(str[3]))
            {
              return LEN;
            }

          if (!strncmp(str, "ASCII", 5) && !isalnum(str[5]))
            {
              return ASCII;
            }

          if (!strncmp(str, "ASIN", 4) && !isalnum(str[4]))
            {
              return ASIN;
            }

          if (!strncmp(str, "ACOS", 4) && !isalnum(str[4]))
            {
              return ACOS;
            }

          if (!strncmp(str, "ATAN", 4) && !isalnum(str[4]))
            {
              return ATAN;
            }

          if (!strncmp(str, "INT", 3) && !isalnum(str[3]))
            {
              return INT;
            }

          if (!strncmp(str, "RND", 3) && !isalnum(str[3]))
            {
              return RND;
            }

          if (!strncmp(str, "VAL", 3) && !isalnum(str[3]))
            {
              return VAL;
            }

          if (!strncmp(str, "VALLEN", 6) && !isalnum(str[6]))
            {
              return VALLEN;
            }

          if (!strncmp(str, "INSTR", 5) && !isalnum(str[5]))
            {
              return INSTR;
            }

          if (!strncmp(str, "CHR$", 4))
            {
              return CHRSTRING;
            }

          if (!strncmp(str, "STR$", 4))
            {
              return STRSTRING;
            }

          if (!strncmp(str, "LEFT$", 5))
            {
              return LEFTSTRING;
            }

          if (!strncmp(str, "RIGHT$", 6))
            {
              return RIGHTSTRING;
            }

          if (!strncmp(str, "MID$", 4))
            {
              return MIDSTRING;
            }

          if (!strncmp(str, "STRING$", 7))
            {
              return STRINGSTRING;
            }
        }

      /* end isupper() */

      if (isalpha(*str))
        {
          while (isalnum(*str))
            {
              str++;
            }

          switch (*str)
            {
            case '$':
              return str[1] == '(' ? DIMSTRID : STRID;

            case '(':
              return DIMFLTID;

            default:
              return FLTID;
            }
        }

      return SYNTAX_ERROR;
    }
}

/****************************************************************************
 * Name: tokenlen
 *
 * Description:
 *   Get the length of a token.
 *   Params: str - pointer to the string containing the token
 *           token - the type of the token read
 *   Returns: length of the token, or 0 for EOL to prevent
 *            it being read past.
 *
 ****************************************************************************/

static int tokenlen(FAR const char *str, int tokenid)
{
  int len = 0;

  switch (tokenid)
    {
    case EOS:
      return 0;

    case EOL:
      return 1;

    case VALUE:
      getvalue(str, &len);
      return len;

    case DIMSTRID:
    case DIMFLTID:
    case STRID:
      getid(str, g_iobuffer, &len);
      return len;

    case FLTID:
      getid(str, g_iobuffer, &len);
      return len;

    case PI:
      return 2;

    case E:
      return 1;

    case SIN:
      return 3;

    case COS:
      return 3;

    case TAN:
      return 3;

    case LN:
      return 2;

    case POW:
      return 3;

    case SQRT:
      return 4;

    case DIV:
      return 1;

    case MULT:
      return 1;

    case OPAREN:
      return 1;

    case CPAREN:
      return 1;

    case PLUS:
      return 1;

    case MINUS:
      return 1;

    case SHRIEK:
      return 1;

    case COMMA:
      return 1;

    case QUOTE:
      return 1;

    case EQUALS:
      return 1;

    case LESS:
      return 1;

    case GREATER:
      return 1;

    case SEMICOLON:
      return 1;

    case SYNTAX_ERROR:
      return 0;

    case PRINT:
      return 5;

    case LET:
      return 3;

    case DIM:
      return 3;

    case IF:
      return 2;

    case THEN:
      return 4;

    case AND:
      return 3;

    case OR:
      return 2;

    case GOTO:
      return 4;

    case INPUT:
      return 5;

    case REM:
      return 3;

    case FOR:
      return 3;

    case TO:
      return 2;

    case NEXT:
      return 4;

    case STEP:
      return 4;

    case MOD:
      return 3;

    case ABS:
      return 3;

    case LEN:
      return 3;

    case ASCII:
      return 5;

    case ASIN:
      return 4;

    case ACOS:
      return 4;

    case ATAN:
      return 4;

    case INT:
      return 3;

    case RND:
      return 3;

    case VAL:
      return 3;

    case VALLEN:
      return 6;

    case INSTR:
      return 5;

    case CHRSTRING:
      return 4;

    case STRSTRING:
      return 4;

    case LEFTSTRING:
      return 5;

    case RIGHTSTRING:
      return 6;

    case MIDSTRING:
      return 4;

    case STRINGSTRING:
      return 7;

    default:
      assert(0);
      return 0;
    }
}

/****************************************************************************
 * Name: isstring
 *
 * Description:
 *   Test if a token represents a string expression
 *   Params: token - token to test
 *   Returns: 1 if a string, else 0
 *
 ****************************************************************************/

static int isstring(int tokenid)
{
  if (tokenid == STRID || tokenid == QUOTE || tokenid == DIMSTRID
      || tokenid == CHRSTRING || tokenid == STRSTRING
      || tokenid == LEFTSTRING || tokenid == RIGHTSTRING
      || tokenid == MIDSTRING || tokenid == STRINGSTRING)
    {
      return 1;
    }

  return 0;
}

/****************************************************************************
 * Name: getvalue
 *
 * Description:
 *   Get a numerical value from the parse string
 *   Params: str - the string to search
 *           len - return pinter for no chars read
 *   Retuns: the value of the string.
 *
 ****************************************************************************/

static double getvalue(FAR const char *str, FAR int *len)
{
  double answer;
  FAR char *end;

  answer = strtod(str, &end);
  assert(end != str);
  *len = end - str;
  return answer;
}

/****************************************************************************
 * Name: getid
 *
 * Description:
 *   Get an id from the parse string:
 *   Params: str - string to search
 *           out - id output [32 chars max ]
 *         len - return pointer for id length
 *   Notes: triggers an error if id > 31 chars
 *          the id includes the $ and ( qualifiers.
 *
 ****************************************************************************/

static void getid(FAR const char *str, FAR char *out, FAR int *len)
{
  int nread = 0;
  while (isspace(*str))
    {
      str++;
    }

  assert(isalpha(*str));
  while (isalnum(*str))
    {
      if (nread < 31)
        {
          out[nread++] = *str++;
        }
      else
        {
          seterror(ERR_IDTOOLONG);
          break;
        }
    }

  if (*str == '$')
    {
      if (nread < 31)
        {
          out[nread++] = *str++;
        }
      else
        {
          seterror(ERR_IDTOOLONG);
        }
    }

  if (*str == '(')
    {
      if (nread < 31)
        {
          out[nread++] = *str++;
        }
      else
        {
          seterror(ERR_IDTOOLONG);
        }
    }

  out[nread] = 0;
  *len = nread;
}

/****************************************************************************
 * Name: mystrgrablit
 *
 * Description:
 *   Grab a literal from the parse string.
 *   Params: dest - destination string
 *           src - source string
 *   Notes: strings are in quotes, double quotes the escape
 *
 ****************************************************************************/

static void mystrgrablit(FAR char *dest, FAR const char *src)
{
  assert(*src == '"');
  src++;

  while (*src)
    {
      if (*src == '"')
        {
          if (src[1] == '"')
            {
              *dest++ = *src;
              src++;
              src++;
            }
          else
            {
              break;
            }
        }
      else
        {
          *dest++ = *src++;
        }
    }

  *dest++ = 0;
}

/****************************************************************************
 * Name: mystrend
 *
 * Description:
 *   Find where a source string literal ends
 *   Params: src - string to check (must point to quote)
 *           quote - character to use for quotation
 *   Returns: pointer to quote which ends string
 *   Notes: quotes escape quotes
 *
 ****************************************************************************/

static FAR char *mystrend(FAR const char *str, char quote)
{
  assert(*str == quote);
  str++;

  while (*str)
    {
      while (*str != quote)
        {
          if (*str == '\n' || *str == 0)
            return 0;
          str++;
        }

      if (str[1] == quote)
        {
          str += 2;
        }
      else
        {
          break;
        }
    }

  return (char *)(*str ? str : 0);
}

/****************************************************************************
 * Name: mystrcount
 *
 * Description:
 *   Count the instances of ch in str
 *   Params: str - string to check
 *           ch - character to count
 *   Returns: no time chs occurs in str.
 *
 ****************************************************************************/

static int mystrcount(FAR const char *str, char ch)
{
  int answer = 0;

  while (*str)
    {
      if (*str++ == ch)
        {
          answer++;
        }
    }

  return answer;
}

/****************************************************************************
 * Name: mystrdup
 *
 * Description:
 *   Duplicate a string:
 *   Params: str - string to duplicate
 *   Returns: malloced duplicate.
 *
 ****************************************************************************/

static FAR char *mystrdup(FAR const char *str)
{
  FAR char *answer;

  answer = malloc(strlen(str) + 1);
  if (answer)
    {
      strcpy(answer, str);
    }

  return answer;
}

/****************************************************************************
 * Name: mystrconcat
 *
 * Description:
 *   Concatenate two strings
 *   Params: str - firsts string
 *           cat - second string
 *   Returns: malloced string.
 *
 ****************************************************************************/

static FAR char *mystrconcat(FAR const char *str, FAR const char *cat)
{
  int len;
  FAR char *answer;

  len = strlen(str) + strlen(cat);
  answer = malloc(len + 1);
  if (answer)
    {
      strcpy(answer, str);
      strcat(answer, cat);
    }

  return answer;
}

/****************************************************************************
 * Name: factorial
 *
 * Description:
 *   Compute x!
 *
 ****************************************************************************/

static double factorial(double x)
{
  double answer = 1.0;
  double t;

  if (x > 1000.0)
    {
      x = 1000.0;
    }

  for (t = 1; t <= x; t += 1.0)
    {
      answer *= t;
    }

  return answer;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: basic
 *
 * Description:
 *   Interpret a BASIC script
 *
 * Input Parameters:
 *   script - the script to run
 *   in     - input stream
 *   out    - output stream
 *   err    - error stream
 *
 * Returned Value:
 *   Returns: 0 on success, 1 on error condition.
 *
 ****************************************************************************/

int basic(FAR const char *script, FILE * in, FILE * out, FILE * err)
{
  int curline = 0;
  int nextline;
  int answer = 0;

  g_fpin = in;
  g_fpout = out;
  g_fperr = err;

  if (setup(script) == -1)
    {
      return 1;
    }

  while (curline != -1)
    {
      g_string = g_lines[curline].str;
      g_token = gettoken(g_string);
      g_errorflag = 0;

      nextline = line();
      if (g_errorflag)
        {
          reporterror(g_lines[curline].no);
          answer = 1;
          break;
        }

      if (nextline == -1)
        {
          break;
        }

      if (nextline == 0)
        {
          curline++;
          if (curline == nlines)
            break;
        }
      else
        {
          curline = findline(nextline);
          if (curline == -1)
            {
              if (g_fperr)
                {
                  fprintf(g_fperr, "line %d not found\n", nextline);
                }
              answer = 1;
              break;
            }
        }
    }

  cleanup();
  return answer;
}