/****************************************************************************
 * apps/interpreters/bas/bas.c
 *
 *   Copyright (c) 1999-2014 Michael Haardt
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Adapted to NuttX and re-released under a 3-clause BSD license:
 *
 *   Copyright (C) 2014 Gregory Nutt. All rights reserved.
 *   Authors: Alan Carvalho de Assis <Alan Carvalho de Assis>
 *            Gregory Nutt <gnutt@nuttx.org>
 *
 * 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 <nuttx/config.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "bas_auto.h"
#include "bas.h"
#include "bas_error.h"
#include "bas_fs.h"
#include "bas_global.h"
#include "bas_program.h"
#include "bas_value.h"
#include "bas_var.h"

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

#define DIRECTMODE (g_pc.line== -1)
#define _(String) String

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

enum labeltype_e
  {
    L_IF = 1,
    L_ELSE,
    L_DO,
    L_DOcondition,
    L_FOR,
    L_FOR_VAR,
    L_FOR_LIMIT,
    L_FOR_BODY,
    L_REPEAT,
    L_SELECTCASE,
    L_WHILE,
    L_FUNC
  };

struct labelstack_s
  {
    enum labeltype_e type;
    struct Pc patch;
  };

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

static unsigned int g_labelstack_index;
static unsigned int g_labelstack_capacity;
static struct labelstack_s *g_labelstack;
static struct Pc *g_lastdata;
static struct Pc g_curdata;
static struct Pc g_nextdata;

static enum
  {
    DECLARE,
    COMPILE,
    INTERPRET
  } g_pass;

static int g_stopped;
static int g_optionbase;
static struct Pc g_pc;
static struct Auto g_stack;
static struct Program g_program;
static struct Global g_globals;
static int g_run_restricted;

/****************************************************************************
 * Public Data
 ****************************************************************************/

int g_bas_argc;
char *g_bas_argv0;
char **g_bas_argv;
bool g_bas_end;

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

static struct Value *statements(struct Value *value);
static struct Value *compileProgram(struct Value *v, int clearGlobals);
static struct Value *eval(struct Value *value, const char *desc);

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

static int cat(const char *filename)
{
  int fd;
  char buf[4096];
  ssize_t l;
  int errcode;

  if ((fd = open(filename, O_RDONLY)) == -1)
    {
      return -1;
    }

  while ((l = read(fd, buf, sizeof(buf))) > 0)
    {
      ssize_t off, w;

      off = 0;
      while (off < l)
        {
          if ((w = write(1, buf + off, l - off)) == -1)
            {
              errcode = errno;
              close(fd);
              errno = errcode;
              return -1;
            }

          off += w;
        }
    }

  if (l == -1)
    {
      errcode = errno;
      close(fd);
      errno = errcode;
      return -1;
    }

  close(fd);
  return 0;
}

static struct Value *lvalue(struct Value *value)
{
  struct Symbol *sym;
  struct Pc lvpc = g_pc;

  sym = g_pc.token->u.identifier->sym;
  assert(g_pass == DECLARE || sym->type == GLOBALVAR || sym->type == GLOBALARRAY
         || sym->type == LOCALVAR);

  if ((g_pc.token + 1)->type == T_OP)
    {
      struct Pc idxpc;
      unsigned int dim, capacity;
      int *idx;

      g_pc.token += 2;
      dim = 0;
      capacity = 0;
      idx = (int *)0;
      while (1)
        {
          if (dim == capacity && g_pass == INTERPRET)     /* enlarge idx */
            {
              int *more;

              more =
                realloc(idx,
                        sizeof(unsigned int) *
                        (capacity ? (capacity *= 2) : (capacity = 3)));
              if (!more)
                {
                  if (capacity)
                    free(idx);
                  return Value_new_ERROR(value, OUTOFMEMORY);
                }

              idx = more;
            }

          idxpc = g_pc;
          if (eval(value, _("index"))->type == V_ERROR ||
              VALUE_RETYPE(value, V_INTEGER)->type == V_ERROR)
            {
              if (capacity)
                {
                  free(idx);
                }

              g_pc = idxpc;
              return value;
            }

          if (g_pass == INTERPRET)
            {
              idx[dim] = value->u.integer;
              ++dim;
            }

          Value_destroy(value);
          if (g_pc.token->type == T_COMMA)
            {
              ++g_pc.token;
            }
          else
            {
              break;
            }
        }

      if (g_pc.token->type != T_CP)
        {
          assert(g_pass != INTERPRET);
          return Value_new_ERROR(value, MISSINGCP);
        }
      else
        {
          ++g_pc.token;
        }

      switch (g_pass)
        {
        case INTERPRET:
          {
            if ((value =
                 Var_value(&(sym->u.var), dim, idx, value))->type == V_ERROR)
              {
                g_pc = lvpc;
              }

            free(idx);
            return value;
          }

        case DECLARE:
          {
            return Value_nullValue(V_INTEGER);
          }

        case COMPILE:
          {
            return Value_nullValue(sym->type ==
                                   GLOBALARRAY ? sym->u.
                                   var.type : Auto_varType(&g_stack, sym));
          }

        default:
          assert(0);
        }

      return (struct Value *)0;
    }
  else
    {
      ++g_pc.token;
      switch (g_pass)
        {
        case INTERPRET:
          return VAR_SCALAR_VALUE(sym->type ==
                                  GLOBALVAR ? &(sym->u.var) : Auto_local(&g_stack,
                                                                         sym->
                                                                         u.local.offset));

        case DECLARE:
          return Value_nullValue(V_INTEGER);

        case COMPILE:
          return Value_nullValue(sym->type ==
                                 GLOBALVAR ? sym->u.
                                 var.type : Auto_varType(&g_stack, sym));

        default:
          assert(0);
        }

      return (struct Value *)0;
    }
}

static struct Value *func(struct Value *value)
{
  struct Identifier *ident;
  struct Pc funcpc = g_pc;
  int firstslot = -99;
  int args = 0;
  struct Symbol *sym;

  assert(g_pc.token->type == T_IDENTIFIER);

  /* Evaluating a function in direct mode may start a program, so it needs to
   * be compiled.  If in direct mode, programs will be compiled after the
   * direct mode pass DECLARE, but errors are ignored at that point, because
   * the program may not be needed.  If the program is fine, its symbols will
   * be available during the compile phase already.  If not and we need it at
   * this point, compile it again to get the error and abort.
   */

  if (DIRECTMODE && !g_program.runnable && g_pass != DECLARE)
    {
      if (compileProgram(value, 0)->type == V_ERROR)
        {
          return value;
        }

      Value_destroy(value);
    }

  ident = g_pc.token->u.identifier;
  assert(g_pass == DECLARE || ident->sym->type == BUILTINFUNCTION ||
         ident->sym->type == USERFUNCTION);
  ++g_pc.token;
  if (g_pass != DECLARE)
    {
      firstslot = g_stack.stackPointer;
      if (ident->sym->type == USERFUNCTION &&
          ident->sym->u.sub.retType != V_VOID)
        {
          struct Var *v = Auto_pushArg(&g_stack);
          Var_new(v, ident->sym->u.sub.retType, 0, (const unsigned int *)0, 0);
        }
    }

  if (g_pc.token->type == T_OP)   /* push arguments to stack */
    {
      ++g_pc.token;
      if (g_pc.token->type != T_CP)
        {
          while (1)
            {
              if (g_pass == DECLARE)
                {
                  if (eval(value, _("actual parameter"))->type == V_ERROR)
                    {
                      return value;
                    }

                  Value_destroy(value);
                }
              else
                {
                  struct Var *v = Auto_pushArg(&g_stack);

                  Var_new_scalar(v);
                  if (eval(v->value, (const char *)0)->type == V_ERROR)
                    {
                      Value_clone(value, v->value);
                      while (g_stack.stackPointer > firstslot)
                        {
                          Var_destroy(&g_stack.slot[--g_stack.stackPointer].var);
                        }

                      return value;
                    }

                  v->type = v->value->type;
                }

              ++args;
              if (g_pc.token->type == T_COMMA)
                {
                  ++g_pc.token;
                }
              else
                {
                  break;
                }
            }

          if (g_pc.token->type != T_CP)
            {
              if (g_pass != DECLARE)
                {
                  while (g_stack.stackPointer > firstslot)
                    {
                      Var_destroy(&g_stack.slot[--g_stack.stackPointer].var);
                    }
                }

              return Value_new_ERROR(value, MISSINGCP);
            }

          ++g_pc.token;
        }
    }

  if (g_pass == DECLARE)
    {
      Value_new_null(value, ident->defaultType);
    }
  else
    {
      int i;
      int nomore;
      int argerr;
      int overloaded;

      if (g_pass == INTERPRET && ident->sym->type == USERFUNCTION)
        {
          for (i = 0; i < ident->sym->u.sub.u.def.localLength; ++i)
            {
              struct Var *v = Auto_pushArg(&g_stack);
              Var_new(v, ident->sym->u.sub.u.def.localTypes[i], 0,
                      (const unsigned int *)0, 0);
            }
        }

      Auto_pushFuncRet(&g_stack, firstslot, &g_pc);

      sym = ident->sym;
      overloaded = (g_pass == COMPILE && sym->type == BUILTINFUNCTION &&
                    sym->u.sub.u.bltin.next);
      do
        {
          nomore = (g_pass == COMPILE &&
                    !(sym->type == BUILTINFUNCTION && sym->u.sub.u.bltin.next));
          argerr = 0;
          if (args < sym->u.sub.argLength)
            {
              if (nomore)
                {
                  Value_new_ERROR(value, TOOFEW);
                }

              argerr = 1;
            }

          else if (args > sym->u.sub.argLength)
            {
              if (nomore)
                {
                  Value_new_ERROR(value, TOOMANY);
                }

              argerr = 1;
            }
          else
            {
              for (i = 0; i < args; ++i)
                {
                  struct Value *arg =
                    Var_value(Auto_local(&g_stack, i), 0, (int *)0, value);

                  assert(arg->type != V_ERROR);
                  if (overloaded)
                    {
                      if (arg->type != sym->u.sub.argTypes[i])
                        {
                          if (nomore)
                            {
                              Value_new_ERROR(value, TYPEMISMATCH2, i + 1);
                            }

                          argerr = 1;
                          break;
                        }
                    }
                  else if (Value_retype(arg, sym->u.sub.argTypes[i])->type ==
                           V_ERROR)
                    {
                      if (nomore)
                        {
                          Value_new_ERROR(value, TYPEMISMATCH3,
                                          arg->u.error.msg, i + 1);
                        }

                      argerr = 1;
                      break;
                    }
                }
            }

          if (argerr)
            {
              if (nomore)
                {
                  Auto_funcReturn(&g_stack, (struct Pc *)0);
                  g_pc = funcpc;
                  return value;
                }
              else
                {
                  sym = sym->u.sub.u.bltin.next;
                }
            }
        }
      while (argerr);

      ident->sym = sym;
      if (sym->type == BUILTINFUNCTION)
        {
          if (g_pass == INTERPRET)
            {
              if (sym->u.sub.u.bltin.call(value, &g_stack)->type == V_ERROR)
                {
                  g_pc = funcpc;
                }
            }
          else
            {
              Value_new_null(value, sym->u.sub.retType);
            }
        }
      else if (sym->type == USERFUNCTION)
        {
          if (g_pass == INTERPRET)
            {
              int r = 1;

              g_pc = sym->u.sub.u.def.scope.start;
              if (g_pc.token->type == T_COLON)
                {
                  ++g_pc.token;
                }
              else
                {
                  Program_skipEOL(&g_program, &g_pc, STDCHANNEL, 1);
                }

              do
                {
                  if (statements(value)->type == V_ERROR)
                    {
                      if (strchr(value->u.error.msg, '\n') == (char *)0)
                        {
                          Auto_setError(&g_stack,
                                        Program_lineNumber(&g_program, &g_pc), &g_pc,
                                        value);
                          Program_PCtoError(&g_program, &g_pc, value);
                        }

                      if (g_stack.onerror.line != -1)
                        {
                          g_stack.resumeable = 1;
                          g_pc = g_stack.onerror;
                        }
                      else
                        {
                          Auto_frameToError(&g_stack, &g_program, value);
                          break;
                        }
                    }
                  else if (value->type != V_NIL)
                    {
                      break;
                    }

                  Value_destroy(value);
                }
              while ((r = Program_skipEOL(&g_program, &g_pc, STDCHANNEL, 1)));

              if (!r)
                {
                  Value_new_VOID(value);
                }
            }
          else
            {
              Value_new_null(value, sym->u.sub.retType);
            }
        }

      Auto_funcReturn(&g_stack, g_pass == INTERPRET &&
                      value->type != V_ERROR ? &g_pc : (struct Pc *)0);
    }

  return value;
}

#ifdef CONFIG_INTERPRETER_BAS_USE_LR0

/* Grammar with LR(0) sets */

/* Grammar:
 *
 *   1 EV -> E
 *   2 E  -> E op E
 *   3 E  -> op E
 *   4 E  -> ( E )
 *   5 E  -> value
 *
 *   i0:
 *   EV -> . E                goto(0,E)=5
 *   E  -> . E op E           goto(0,E)=5
 *   E  -> . op E      +,-    shift 2
 *   E  -> . ( E )     (      shift 3
 *   E  -> . value     value  shift 4
 *
 *   i5:
 *   EV -> E .         else   accept
 *   E  -> E . op E    op     shift 1
 *
 *   i2:
 *   E  -> op . E             goto(2,E)=6
 *   E  -> . E op E           goto(2,E)=6
 *   E  -> . op E      +,-    shift 2
 *   E  -> . ( E )     (      shift 3
 *   E  -> . value     value  shift 4
 *
 *   i3:
 *   E  -> ( . E )            goto(3,E)=7
 *   E  -> . E op E           goto(3,E)=7
 *   E  -> . op E      +,-    shift 2
 *   E  -> . ( E )     (      shift 3
 *   E  -> . value     value  shift 4
 *
 *   i4:
 *   E  -> value .            reduce 5
 *
 *   i1:
 *   E  -> E op . E           goto(1,E)=8
 *   E  -> . E op E           goto(1,E)=8
 *   E  -> . op E      +,-    shift 2
 *   E  -> . ( E )     (      shift 3
 *   E  -> . value     value  shift 4
 *
 *   i6:
 *   E  -> op E .             reduce 3
 *   E  -> E . op E    op*    shift 1 *=if stack[-2] contains op of unary lower priority
 *
 *   i7:
 *   E  -> ( E . )     )      shift 9
 *   E  -> E . op E    op     shift 1
 *
 *   i8:
 *   E  -> E op E .           reduce 2
 *   E  -> E . op E    op*    shift 1 *=if stack[-2] contains op of lower priority or if
 *                                      if it is of equal priority and right associative
 *   i9:
 *   E  -> ( E ) .            reduce 4
 */

static struct Value *eval(struct Value *value, const char *desc)
{
  /* Variables */

  static const int gotoState[10] = { 5, 8, 6, 7, -1, -1, -1, -1, -1, -1 };
  int capacity = 10;
  struct Pdastack
    {
      union
        {
          enum TokenType token;
          struct Value value;
        } u;
      char state;
    };
  struct Pdastack *pdastack = malloc(capacity * sizeof(struct Pdastack));
  struct Pdastack *sp = pdastack;
  struct Pdastack *stackEnd = pdastack + capacity - 1;
  enum TokenType ip;

  sp->state = 0;
  while (1)
    {
      if (sp == stackEnd)
        {
          pdastack =
            realloc(pdastack, (capacity + 10) * sizeof(struct Pdastack));
          sp = pdastack + capacity - 1;
          capacity += 10;
          stackEnd = pdastack + capacity - 1;
        }

      ip = g_pc.token->type;
      switch (sp->state)
        {
        case 0:
        case 1:
        case 2:
        case 3:                /* including 4 */
          {
            if (ip == T_IDENTIFIER)
              {
                /* printf("state %d: shift 4\n",sp->state); */
                /* printf("state 4: reduce E -> value\n"); */

                ++sp;
                sp->state = gotoState[(sp - 1)->state];
                if (g_pass == COMPILE)
                  {
                    if (((g_pc.token + 1)->type == T_OP ||
                         Auto_find(&g_stack, g_pc.token->u.identifier) == 0) &&
                        Global_find(&g_globals, g_pc.token->u.identifier,
                                    (g_pc.token + 1)->type == T_OP) == 0)
                      {
                        Value_new_ERROR(value, UNDECLARED);
                        goto error;
                      }
                  }

                if (g_pass != DECLARE &&
                    (g_pc.token->u.identifier->sym->type == GLOBALVAR ||
                     g_pc.token->u.identifier->sym->type == GLOBALARRAY ||
                     g_pc.token->u.identifier->sym->type == LOCALVAR))
                  {
                    struct Value *l;

                    if ((l = lvalue(value))->type == V_ERROR)
                      goto error;
                    Value_clone(&sp->u.value, l);
                  }
                else
                  {
                    struct Pc var = g_pc;

                    func(&sp->u.value);
                    if (sp->u.value.type == V_VOID)
                      {
                        g_pc = var;
                        Value_new_ERROR(value, VOIDVALUE);
                        goto error;
                      }
                  }
              }
            else if (ip == T_INTEGER)
              {
                /* printf("state %d: shift 4\n",sp->state); */
                /* printf("state 4: reduce E -> value\n"); */

                ++sp;
                sp->state = gotoState[(sp - 1)->state];
                VALUE_NEW_INTEGER(&sp->u.value, g_pc.token->u.integer);
                ++g_pc.token;
              }
            else if (ip == T_REAL)
              {
                /* printf("state %d: shift 4\n",sp->state); */
                /* printf("state 4: reduce E -> value\n"); */

                ++sp;
                sp->state = gotoState[(sp - 1)->state];
                VALUE_NEW_REAL(&sp->u.value, g_pc.token->u.real);
                ++g_pc.token;
              }
            else if (TOKEN_ISUNARYOPERATOR(ip))
              {
                /* printf("state %d: shift 2\n",sp->state); */

                ++sp;
                sp->state = 2;
                sp->u.token = ip;
                ++g_pc.token;
              }
            else if (ip == T_HEXINTEGER)
              {
                /* printf("state %d: shift 4\n",sp->state); */
                /* printf("state 4: reduce E -> value\n"); */

                ++sp;
                sp->state = gotoState[(sp - 1)->state];
                VALUE_NEW_INTEGER(&sp->u.value, g_pc.token->u.hexinteger);
                ++g_pc.token;
              }
            else if (ip == T_OCTINTEGER)
              {
                /* printf("state %d: shift 4\n",sp->state); */
                /* printf("state 4: reduce E -> value\n"); */

                ++sp;
                sp->state = gotoState[(sp - 1)->state];
                VALUE_NEW_INTEGER(&sp->u.value, g_pc.token->u.octinteger);
                ++g_pc.token;
              }
            else if (ip == T_OP)
              {
                /* printf("state %d: shift 3\n",sp->state); */

                ++sp;
                sp->state = 3;
                sp->u.token = T_OP;
                ++g_pc.token;
              }
            else if (ip == T_STRING)
              {
                /* printf("state %d: shift 4\n",sp->state); */
                /* printf("state 4: reduce E -> value\n"); */

                ++sp;
                sp->state = gotoState[(sp - 1)->state];
                Value_new_STRING(&sp->u.value);
                String_destroy(&sp->u.value.u.string);
                String_clone(&sp->u.value.u.string, g_pc.token->u.string);
                ++g_pc.token;
              }
            else
              {
                char state = sp->state;

                if (state == 0)
                  {
                    if (desc)
                      {
                        Value_new_ERROR(value, MISSINGEXPR, desc);
                      }
                    else
                      {
                        value = (struct Value *)0;
                      }
                  }
                else
                  {
                    Value_new_ERROR(value, MISSINGEXPR, _("operand"));
                  }

                goto error;
              }

            break;
          }

        case 5:
          {
            if (TOKEN_ISBINARYOPERATOR(ip))
              {
                /* printf("state %d: shift 1\n",sp->state); */

                ++sp;
                sp->state = 1;
                sp->u.token = ip;
                ++g_pc.token;
                break;
              }
            else
              {
                assert(sp == pdastack + 1);
                *value = sp->u.value;
                free(pdastack);
                return value;
              }

            break;
          }

        case 6:
          {
            if (TOKEN_ISBINARYOPERATOR(ip) &&
                TOKEN_UNARYPRIORITY((sp - 1)->u.token) <
                TOKEN_BINARYPRIORITY(ip))
              {
                assert(TOKEN_ISUNARYOPERATOR((sp - 1)->u.token));

                /* printf("state %d: shift 1 (not reducing E -> op E)\n", sp->state); */

                ++sp;
                sp->state = 1;
                sp->u.token = ip;
                ++g_pc.token;
              }
            else
              {
                enum TokenType op;

                /* printf("reduce E -> op E\n"); */

                --sp;
                op = sp->u.token;
                sp->u.value = (sp + 1)->u.value;
                switch (op)
                  {
                  case T_PLUS:
                    break;

                  case T_MINUS:
                    Value_uneg(&sp->u.value, g_pass == INTERPRET);
                    break;

                  case T_NOT:
                    Value_unot(&sp->u.value, g_pass == INTERPRET);
                    break;

                  default:
                    assert(0);
                  }

                sp->state = gotoState[(sp - 1)->state];
                if (sp->u.value.type == V_ERROR)
                  {
                    *value = sp->u.value;
                    --sp;
                    goto error;
                  }
              }

            break;
          }

        case 7:                /* including 9 */
          {
            if (TOKEN_ISBINARYOPERATOR(ip))
              {
                /* printf("state %d: shift 1\n"sp->state); */

                ++sp;
                sp->state = 1;
                sp->u.token = ip;
                ++g_pc.token;
              }
            else if (ip == T_CP)
              {
                /* printf("state %d: shift 9\n",sp->state); */
                /* printf("state 9: reduce E -> ( E )\n"); */

                --sp;
                sp->state = gotoState[(sp - 1)->state];
                sp->u.value = (sp + 1)->u.value;
                ++g_pc.token;
              }
            else
              {
                Value_new_ERROR(value, MISSINGCP);
                goto error;
              }

            break;
          }

        case 8:
          {
            int p1, p2;

            if (TOKEN_ISBINARYOPERATOR(ip)
                &&
                (((p1 = TOKEN_BINARYPRIORITY((sp - 1)->u.token)) < (p2 =
                                                                    TOKEN_BINARYPRIORITY
                                                                    (ip))) ||
                 (p1 == p2 && TOKEN_ISRIGHTASSOCIATIVE((sp - 1)->u.token))))
              {
                /* printf("state %d: shift 1\n",sp->state); */

                ++sp;
                sp->state = 1;
                sp->u.token = ip;
                ++g_pc.token;
              }
            else
              {
                /* printf("state %d: reduce E -> E op E\n",sp->state); */

                if (Value_commonType[(sp - 2)->u.value.type][sp->u.value.type]
                    == V_ERROR)
                  {
                    Value_destroy(&sp->u.value);
                    sp -= 2;
                    Value_destroy(&sp->u.value);
                    Value_new_ERROR(value, INVALIDOPERAND);
                    --sp;
                    goto error;
                  }
                else
                  {
                    switch ((sp - 1)->u.token)
                      {
                      case T_LT:
                        Value_lt(&(sp - 2)->u.value, &sp->u.value,
                                 g_pass == INTERPRET);
                        break;

                      case T_LE:
                        Value_le(&(sp - 2)->u.value, &sp->u.value,
                                 g_pass == INTERPRET);
                        break;

                      case T_EQ:
                        Value_eq(&(sp - 2)->u.value, &sp->u.value,
                                 g_pass == INTERPRET);
                        break;

                      case T_GE:
                        Value_ge(&(sp - 2)->u.value, &sp->u.value,
                                 g_pass == INTERPRET);
                        break;

                      case T_GT:
                        Value_gt(&(sp - 2)->u.value, &sp->u.value,
                                 g_pass == INTERPRET);
                        break;

                      case T_NE:
                        Value_ne(&(sp - 2)->u.value, &sp->u.value,
                                 g_pass == INTERPRET);
                        break;

                      case T_PLUS:
                        Value_add(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;
                      case T_MINUS:
                        Value_sub(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;

                      case T_MULT:
                        Value_mult(&(sp - 2)->u.value, &sp->u.value,
                                   g_pass == INTERPRET);
                        break;

                      case T_DIV:
                        Value_div(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;

                      case T_IDIV:
                        Value_idiv(&(sp - 2)->u.value, &sp->u.value,
                                   g_pass == INTERPRET);
                        break;

                      case T_MOD:
                        Value_mod(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;

                      case T_POW:
                        Value_pow(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;

                      case T_AND:
                        Value_and(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;

                      case T_OR:
                        Value_or(&(sp - 2)->u.value, &sp->u.value,
                                 g_pass == INTERPRET);
                        break;

                      case T_XOR:
                        Value_xor(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;

                      case T_EQV:
                        Value_eqv(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;

                      case T_IMP:
                        Value_imp(&(sp - 2)->u.value, &sp->u.value,
                                  g_pass == INTERPRET);
                        break;

                      default:
                        assert(0);
                      }
                  }

                Value_destroy(&sp->u.value);
                sp -= 2;
                sp->state = gotoState[(sp - 1)->state];
                if (sp->u.value.type == V_ERROR)
                  {
                    *value = sp->u.value;
                    --sp;
                    goto error;
                  }
              }

            break;
          }
        }
    }

error:
  while (sp > pdastack)
    {
      switch (sp->state)
        {
        case 5:
        case 6:
        case 7:
        case 8:
          Value_destroy(&sp->u.value);
        }
      --sp;
    }

  free(pdastack);
  return value;
}

#else
static inline struct Value *binarydown(struct Value *value,
                                       struct Value *(level) (struct Value *
                                                              value),
                                       const int prio)
{
  enum TokenType op;
  struct Pc oppc;

  if (level(value) == (struct Value *)0)
    {
      return (struct Value *)0;
    }

  if (value->type == V_ERROR)
    {
      return value;
    }

  do
    {
      struct Value x;

      op = g_pc.token->type;
      if (!TOKEN_ISBINARYOPERATOR(op) || TOKEN_BINARYPRIORITY(op) != prio)
        {
          return value;
        }

      oppc = g_pc;
      ++g_pc.token;
      if (level(&x) == (struct Value *)0)
        {
          Value_destroy(value);
          return Value_new_ERROR(value, MISSINGEXPR, _("binary operand"));
        }

      if (x.type == V_ERROR)
        {
          Value_destroy(value);
          *value = x;
          return value;
        }

      if (Value_commonType[value->type][x.type] == V_ERROR)
        {
          Value_destroy(value);
          Value_destroy(&x);
          return Value_new_ERROR(value, INVALIDOPERAND);
        }
      else
        {
          switch (op)
            {
            case T_LT:
              Value_lt(value, &x, g_pass == INTERPRET);
              break;

            case T_LE:
              Value_le(value, &x, g_pass == INTERPRET);
              break;

            case T_EQ:
              Value_eq(value, &x, g_pass == INTERPRET);
              break;

            case T_GE:
              Value_ge(value, &x, g_pass == INTERPRET);
              break;

            case T_GT:
              Value_gt(value, &x, g_pass == INTERPRET);
              break;

            case T_NE:
              Value_ne(value, &x, g_pass == INTERPRET);
              break;

            case T_PLUS:
              Value_add(value, &x, g_pass == INTERPRET);
              break;

            case T_MINUS:
              Value_sub(value, &x, g_pass == INTERPRET);
              break;

            case T_MULT:
              Value_mult(value, &x, g_pass == INTERPRET);
              break;

            case T_DIV:
              Value_div(value, &x, g_pass == INTERPRET);
              break;

            case T_IDIV:
              Value_idiv(value, &x, g_pass == INTERPRET);
              break;

            case T_MOD:
              Value_mod(value, &x, g_pass == INTERPRET);
              break;

            case T_POW:
              Value_pow(value, &x, g_pass == INTERPRET);
              break;

            case T_AND:
              Value_and(value, &x, g_pass == INTERPRET);
              break;

            case T_OR:
              Value_or(value, &x, g_pass == INTERPRET);
              break;

            case T_XOR:
              Value_xor(value, &x, g_pass == INTERPRET);
              break;

            case T_EQV:
              Value_eqv(value, &x, g_pass == INTERPRET);
              break;

            case T_IMP:
              Value_imp(value, &x, g_pass == INTERPRET);
              break;

            default:
              assert(0);
            }
        }

      Value_destroy(&x);
    }
  while (value->type != V_ERROR);

  if (value->type == V_ERROR)
    {
      g_pc = oppc;
    }

  return value;
}

static inline struct Value *unarydown(struct Value *value,
                                      struct Value *(level) (struct Value *
                                                             value),
                                      const int prio)
{
  enum TokenType op;
  struct Pc oppc;

  op = g_pc.token->type;
  if (!TOKEN_ISUNARYOPERATOR(op) || TOKEN_UNARYPRIORITY(op) != prio)
    {
      return level(value);
    }

  oppc = g_pc;
  ++g_pc.token;
  if (unarydown(value, level, prio) == (struct Value *)0)
    {
      return Value_new_ERROR(value, MISSINGEXPR, _("unary operand"));
    }

  if (value->type == V_ERROR)
    {
      return value;
    }

  switch (op)
    {
    case T_PLUS:
      Value_uplus(value, g_pass == INTERPRET);
      break;

    case T_MINUS:
      Value_uneg(value, g_pass == INTERPRET);
      break;

    case T_NOT:
      Value_unot(value, g_pass == INTERPRET);
      break;

    default:
      assert(0);
    }

  if (value->type == V_ERROR)
    {
      g_pc = oppc;
    }

  return value;
}

static struct Value *eval8(struct Value *value)
{
  switch (g_pc.token->type)
    {
    case T_IDENTIFIER:
      {
        struct Pc var;
        struct Value *l;

        var = g_pc;
        if (g_pass == COMPILE)
          {
            if (((g_pc.token + 1)->type == T_OP ||
                 Auto_find(&g_stack, g_pc.token->u.identifier) == 0) &&
                Global_find(&g_globals, g_pc.token->u.identifier,
                            (g_pc.token + 1)->type == T_OP) == 0)
              return Value_new_ERROR(value, UNDECLARED);
          }

        assert(g_pass == DECLARE || g_pc.token->u.identifier->sym);
        if (g_pass != DECLARE &&
            (g_pc.token->u.identifier->sym->type == GLOBALVAR ||
             g_pc.token->u.identifier->sym->type == GLOBALARRAY ||
             g_pc.token->u.identifier->sym->type == LOCALVAR))
          {
            if ((l = lvalue(value))->type == V_ERROR)
              {
                return value;
              }

            Value_clone(value, l);
          }
        else
          {
            func(value);
            if (value->type == V_VOID)
              {
                Value_destroy(value);
                g_pc = var;
                return Value_new_ERROR(value, VOIDVALUE);
              }
          }

        break;
      }

    case T_INTEGER:
      {
        VALUE_NEW_INTEGER(value, g_pc.token->u.integer);
        ++g_pc.token;
        break;
      }

    case T_REAL:
      {
        VALUE_NEW_REAL(value, g_pc.token->u.real);
        ++g_pc.token;
        break;
      }

    case T_STRING:
      {
        Value_new_STRING(value);
        String_destroy(&value->u.string);
        String_clone(&value->u.string, g_pc.token->u.string);
        ++g_pc.token;
        break;
      }

    case T_HEXINTEGER:
      {
        VALUE_NEW_INTEGER(value, g_pc.token->u.hexinteger);
        ++g_pc.token;
        break;
      }

    case T_OCTINTEGER:
      {
        VALUE_NEW_INTEGER(value, g_pc.token->u.octinteger);
        ++g_pc.token;
        break;
      }

    case T_OP:
      {
        ++g_pc.token;
        if (eval(value, _("parenthetic"))->type == V_ERROR)
          {
            return value;
          }

        if (g_pc.token->type != T_CP)
          {
            Value_destroy(value);
            return Value_new_ERROR(value, MISSINGCP);
          }

        ++g_pc.token;
        break;
      }

    default:
      {
        return (struct Value *)0;
      }
    }

  return value;
}

static struct Value *eval7(struct Value *value)
{
  return binarydown(value, eval8, 7);
}

static struct Value *eval6(struct Value *value)
{
  return unarydown(value, eval7, 6);
}

static struct Value *eval5(struct Value *value)
{
  return binarydown(value, eval6, 5);
}

static struct Value *eval4(struct Value *value)
{
  return binarydown(value, eval5, 4);
}

static struct Value *eval3(struct Value *value)
{
  return binarydown(value, eval4, 3);
}

static struct Value *eval2(struct Value *value)
{
  return unarydown(value, eval3, 2);
}

static struct Value *eval1(struct Value *value)
{
  return binarydown(value, eval2, 1);
}

static struct Value *eval(struct Value *value, const char *desc)
{
  /* Avoid function calls for atomic expression */

  switch (g_pc.token->type)
    {
    case T_STRING:
    case T_REAL:
    case T_INTEGER:
    case T_HEXINTEGER:
    case T_OCTINTEGER:
    case T_IDENTIFIER:
      if (!TOKEN_ISBINARYOPERATOR((g_pc.token + 1)->type) &&
          (g_pc.token + 1)->type != T_OP)
        {
          return eval7(value);
        }

    default:
      break;
    }

  if (binarydown(value, eval1, 0) == (struct Value *)0)
    {
      if (desc)
        {
          return Value_new_ERROR(value, MISSINGEXPR, desc);
        }
      else
        {
          return (struct Value *)0;
        }
    }
  else
    {
      return value;
    }
}
#endif

static void new(void)
{
  Global_destroy(&g_globals);
  Global_new(&g_globals);
  Auto_destroy(&g_stack);
  Auto_new(&g_stack);
  Program_destroy(&g_program);
  Program_new(&g_program);
  FS_closefiles();
  g_optionbase = 0;
}

static void pushLabel(enum labeltype_e type, struct Pc *patch)
{
  if (g_labelstack_index == g_labelstack_capacity)
    {
      struct labelstack_s *more;

      more =
        realloc(g_labelstack,
                sizeof(struct labelstack_s) *
                (g_labelstack_capacity ? (g_labelstack_capacity *= 2) : (32)));
      g_labelstack = more;
    }

  g_labelstack[g_labelstack_index].type = type;
  g_labelstack[g_labelstack_index].patch = *patch;
  ++g_labelstack_index;
}

static struct Pc *popLabel(enum labeltype_e type)
{
  if (g_labelstack_index == 0 || g_labelstack[g_labelstack_index - 1].type != type)
    {
      return (struct Pc *)0;
    }
  else
    {
      return &g_labelstack[--g_labelstack_index].patch;
    }
}

static struct Pc *findLabel(enum labeltype_e type)
{
  int i;

  for (i = g_labelstack_index - 1; i >= 0; --i)
    {
      if (g_labelstack[i].type == type)
        {
          return &g_labelstack[i].patch;
        }
    }

  return (struct Pc *)0;
}

static void labelStackError(struct Value *v)
{
  assert(g_labelstack_index);
  g_pc = g_labelstack[g_labelstack_index - 1].patch;
  switch (g_labelstack[g_labelstack_index - 1].type)
    {
    case L_IF:
      Value_new_ERROR(v, STRAYIF);
      break;

    case L_DO:
      Value_new_ERROR(v, STRAYDO);
      break;

    case L_DOcondition:
      Value_new_ERROR(v, STRAYDOcondition);
      break;

    case L_ELSE:
      Value_new_ERROR(v, STRAYELSE2);
      break;

    case L_FOR_BODY:
      {
        Value_new_ERROR(v, STRAYFOR);
        g_pc = *findLabel(L_FOR);
        break;
      }

    case L_WHILE:
      Value_new_ERROR(v, STRAYWHILE);
      break;

    case L_REPEAT:
      Value_new_ERROR(v, STRAYREPEAT);
      break;

    case L_SELECTCASE:
      Value_new_ERROR(v, STRAYSELECTCASE);
      break;

    case L_FUNC:
      Value_new_ERROR(v, STRAYFUNC);
      break;

    default:
      assert(0);
    }
}

static const char *topLabelDescription(void)
{
  if (g_labelstack_index == 0)
    {
      return _("program");
    }

  switch (g_labelstack[g_labelstack_index - 1].type)
    {
    case L_IF:
      return _("`if' branch");

    case L_DO:
      return _("`do' loop");

    case L_DOcondition:
      return _("`do while' or `do until' loop");

    case L_ELSE:
      return _("`else' branch");

    case L_FOR_BODY:
      return _("`for' loop");

    case L_WHILE:
      return _("`while' loop");

    case L_REPEAT:
      return _("`repeat' loop");

    case L_SELECTCASE:
      return _("`select case' control structure");

    case L_FUNC:
      return _("function or procedure");

    default:
      assert(0);
    }

  /* NOTREACHED */

  return (const char *)0;
}

static struct Value *assign(struct Value *value)
{
  struct Pc expr;

  if (strcasecmp(g_pc.token->u.identifier->name, "mid$") == 0)
    {
      long int n, m;
      struct Value *l;

      ++g_pc.token;
      if (g_pc.token->type != T_OP)
        {
          return Value_new_ERROR(value, MISSINGOP);
        }

      ++g_pc.token;
      if (g_pc.token->type != T_IDENTIFIER)
        {
          return Value_new_ERROR(value, MISSINGSTRIDENT);
        }

      if (g_pass == DECLARE)
        {
          if (((g_pc.token + 1)->type == T_OP ||
               Auto_find(&g_stack, g_pc.token->u.identifier) == 0) &&
              Global_variable(&g_globals, g_pc.token->u.identifier,
                              g_pc.token->u.identifier->defaultType,
                              (g_pc.token + 1)->type ==
                              T_OP ? GLOBALARRAY : GLOBALVAR, 0) == 0)
            {
              return Value_new_ERROR(value, REDECLARATION);
            }
        }

      if ((l = lvalue(value))->type == V_ERROR)
        {
          return value;
        }

      if (g_pass == COMPILE && l->type != V_STRING)
        {
          return Value_new_ERROR(value, TYPEMISMATCH4);
        }

      if (g_pc.token->type != T_COMMA)
        {
          return Value_new_ERROR(value, MISSINGCOMMA);
        }

      ++g_pc.token;
      if (eval(value, _("position"))->type == V_ERROR ||
          Value_retype(value, V_INTEGER)->type == V_ERROR)
        {
          return value;
        }

      n = value->u.integer;
      Value_destroy(value);
      if (g_pass == INTERPRET && n < 1)
        {
          return Value_new_ERROR(value, OUTOFRANGE, "position");
        }

      if (g_pc.token->type == T_COMMA)
        {
          ++g_pc.token;
          if (eval(value, _("length"))->type == V_ERROR ||
              Value_retype(value, V_INTEGER)->type == V_ERROR)
            {
              return value;
            }

          m = value->u.integer;
          if (g_pass == INTERPRET && m < 0)
            {
              return Value_new_ERROR(value, OUTOFRANGE, _("length"));
            }

          Value_destroy(value);
        }
      else
        {
          m = -1;
        }

      if (g_pc.token->type != T_CP)
        {
          return Value_new_ERROR(value, MISSINGCP);
        }

      ++g_pc.token;
      if (g_pc.token->type != T_EQ)
        {
          return Value_new_ERROR(value, MISSINGEQ);
        }

      ++g_pc.token;
      if (eval(value, _("rhs"))->type == V_ERROR ||
          Value_retype(value, V_STRING)->type == V_ERROR)
        {
          return value;
        }

      if (g_pass == INTERPRET)
        {
          if (m == -1)
            {
              m = value->u.string.length;
            }

          String_set(&l->u.string, n - 1, &value->u.string, m);
        }
    }
  else
    {
      struct Value **l = (struct Value **)0;
      int i, used = 0, capacity = 0;
      struct Value retyped_value;

      for (;;)
        {
          if (used == capacity)
            {
              struct Value **more;

              capacity = capacity ? 2 * capacity : 2;
              more = realloc(l, capacity * sizeof(*l));
              l = more;
            }

          if (g_pass == DECLARE)
            {
              if (((g_pc.token + 1)->type == T_OP ||
                   Auto_find(&g_stack, g_pc.token->u.identifier) == 0) &&
                  Global_variable(&g_globals, g_pc.token->u.identifier,
                                  g_pc.token->u.identifier->defaultType,
                                  (g_pc.token + 1)->type ==
                                  T_OP ? GLOBALARRAY : GLOBALVAR, 0) == 0)
                {
                  if (capacity)
                    {
                      free(l);
                    }

                  return Value_new_ERROR(value, REDECLARATION);
                }
            }

          if ((l[used] = lvalue(value))->type == V_ERROR)
            {
              return value;
            }

          ++used;
          if (g_pc.token->type == T_COMMA)
            {
              ++g_pc.token;
            }
          else
            {
              break;
            }
        }

      if (g_pc.token->type != T_EQ)
        {
          return Value_new_ERROR(value, MISSINGEQ);
        }

      ++g_pc.token;
      expr = g_pc;
      if (eval(value, _("rhs"))->type == V_ERROR)
        {
          return value;
        }

      for (i = 0; i < used; ++i)
        {
          Value_clone(&retyped_value, value);
          if (g_pass != DECLARE &&
              VALUE_RETYPE(&retyped_value, (l[i])->type)->type == V_ERROR)
            {
              g_pc = expr;
              free(l);
              Value_destroy(value);
              *value = retyped_value;
              return value;
            }

          if (g_pass == INTERPRET)
            {
              Value_destroy(l[i]);
              *(l[i]) = retyped_value;
            }
        }

      free(l);
      Value_destroy(value);
      *value = retyped_value;   /* for status only */
    }

  return value;
}

static struct Value *compileProgram(struct Value *v, int clearGlobals)
{
  struct Pc begin;

  g_stack.resumeable = 0;
  if (clearGlobals)
    {
      Global_destroy(&g_globals);
      Global_new(&g_globals);
    }
  else
    {
      Global_clearFunctions(&g_globals);
    }

  if (Program_beginning(&g_program, &begin))
    {
      struct Pc savepc;
      int savepass;

      savepc = g_pc;
      savepass = g_pass;
      Program_norun(&g_program);
      for (g_pass = DECLARE; g_pass != INTERPRET; ++g_pass)
        {
          if (g_pass == DECLARE)
            {
              g_stack.begindata.line = -1;
              g_lastdata = &g_stack.begindata;
            }

          g_optionbase = 0;
          g_stopped = 0;
          g_program.runnable = 1;
          g_pc = begin;
          while (1)
            {
              statements(v);
              if (v->type == V_ERROR)
                {
                  break;
                }

              Value_destroy(v);
              if (!Program_skipEOL(&g_program, &g_pc, 0, 0))
                {
                  Value_new_NIL(v);
                  break;
                }
            }

          if (v->type != V_ERROR && g_labelstack_index > 0)
            {
              Value_destroy(v);
              labelStackError(v);
            }

          if (v->type == V_ERROR)
            {
              g_labelstack_index = 0;
              Program_norun(&g_program);
              if (g_stack.cur)
                {
                  Auto_funcEnd(&g_stack); /* Always correct? */
                }

              g_pass = savepass;
              return v;
            }
        }

      g_pc = begin;
      if (Program_analyse(&g_program, &g_pc, v))
        {
          g_labelstack_index = 0;
          Program_norun(&g_program);
          if (g_stack.cur)
            {
              Auto_funcEnd(&g_stack);     /* Always correct? */
            }

          g_pass = savepass;
          return v;
        }

      g_curdata = g_stack.begindata;
      g_pc = savepc;
      g_pass = savepass;
    }

  return Value_new_NIL(v);
}

static void runline(struct Token *line)
{
  struct Value value;

  FS_flush(STDCHANNEL);
  for (g_pass = DECLARE; g_pass != INTERPRET; ++g_pass)
    {
      g_curdata.line = -1;
      g_pc.line = -1;
      g_pc.token = line;
      g_optionbase = 0;
      g_stopped = 0;
      statements(&value);
      if (value.type != V_ERROR && g_pc.token->type != T_EOL)
        {
          Value_destroy(&value);
          Value_new_ERROR(&value, SYNTAX);
        }

      if (value.type != V_ERROR && g_labelstack_index > 0)
        {
          Value_destroy(&value);
          labelStackError(&value);
        }

      if (value.type == V_ERROR)
        {
          struct String s;

          Auto_setError(&g_stack, Program_lineNumber(&g_program, &g_pc), &g_pc, &value);
          Program_PCtoError(&g_program, &g_pc, &value);
          g_labelstack_index = 0;
          FS_putChars(STDCHANNEL, _("Error: "));
          String_new(&s);
          Value_toString(&value, &s, ' ', -1, 0, 0, 0, 0, -1, 0, 0);
          Value_destroy(&value);
          FS_putString(STDCHANNEL, &s);
          String_destroy(&s);
          return;
        }

      if (!g_program.runnable && g_pass == COMPILE)
        {
          Value_destroy(&value);
          (void)compileProgram(&value, 0);
        }
    }

  g_pc.line = -1;
  g_pc.token = line;
  g_optionbase = 0;
  g_curdata = g_stack.begindata;
  g_nextdata.line = -1;
  Value_destroy(&value);
  g_pass = INTERPRET;

  do
    {
      assert(g_pass == INTERPRET);
      statements(&value);
      assert(g_pass == INTERPRET);
      if (value.type == V_ERROR)
        {
          if (strchr(value.u.error.msg, '\n') == (char *)0)
            {
              Auto_setError(&g_stack, Program_lineNumber(&g_program, &g_pc), &g_pc,
                            &value);
              Program_PCtoError(&g_program, &g_pc, &value);
            }

          if (g_stack.onerror.line != -1)
            {
              g_stack.resumeable = 1;
              g_pc = g_stack.onerror;
            }
          else
            {
              struct String s;

              String_new(&s);
              if (!g_stopped)
                {
                  g_stopped = 0;
                  FS_putChars(STDCHANNEL, _("Error: "));
                }

              Auto_frameToError(&g_stack, &g_program, &value);
              Value_toString(&value, &s, ' ', -1, 0, 0, 0, 0, -1, 0, 0);
              while (Auto_gosubReturn(&g_stack, (struct Pc *)0));
              FS_putString(STDCHANNEL, &s);
              String_destroy(&s);
              Value_destroy(&value);
              break;
            }
        }

      Value_destroy(&value);
    }
  while (g_pc.token->type != T_EOL ||
         Program_skipEOL(&g_program, &g_pc, STDCHANNEL, 1));
}

static struct Value *evalGeometry(struct Value *value, unsigned int *dim,
                                  unsigned int geometry[])
{
  struct Pc exprpc = g_pc;

  if (eval(value, _("dimension"))->type == V_ERROR ||
      (g_pass != DECLARE && Value_retype(value, V_INTEGER)->type == V_ERROR))
    {
      return value;
    }

  if (g_pass == INTERPRET && value->u.integer < g_optionbase)
    {
      Value_destroy(value);
      g_pc = exprpc;
      return Value_new_ERROR(value, OUTOFRANGE, _("dimension"));
    }

  geometry[0] = value->u.integer - g_optionbase + 1;
  Value_destroy(value);
  if (g_pc.token->type == T_COMMA)
    {
      ++g_pc.token;
      exprpc = g_pc;
      if (eval(value, _("dimension"))->type == V_ERROR ||
          (g_pass != DECLARE && Value_retype(value, V_INTEGER)->type == V_ERROR))
        {
          return value;
        }

      if (g_pass == INTERPRET && value->u.integer < g_optionbase)
        {
          Value_destroy(value);
          g_pc = exprpc;
          return Value_new_ERROR(value, OUTOFRANGE, _("dimension"));
        }

      geometry[1] = value->u.integer - g_optionbase + 1;
      Value_destroy(value);
      *dim = 2;
    }
  else
    {
      *dim = 1;
    }

  if (g_pc.token->type == T_CP)
    {
      ++g_pc.token;
    }
  else
    {
      return Value_new_ERROR(value, MISSINGCP);
    }

  return (struct Value *)0;
}

static struct Value *convert(struct Value *value, struct Value *l,
                             struct Token *t)
{
  switch (l->type)
    {
    case V_INTEGER:
      {
        char *datainput;
        char *end;
        long int v;
        int overflow;

        if (t->type != T_DATAINPUT)
          {
            return Value_new_ERROR(value, BADCONVERSION, _("integer"));
          }

        datainput = t->u.datainput;
        v = Value_vali(datainput, &end, &overflow);
        if (end == datainput || (*end != '\0' && *end != ' ' && *end != '\t'))
          {
            return Value_new_ERROR(value, BADCONVERSION, _("integer"));
          }

        if (overflow)
          {
            return Value_new_ERROR(value, OUTOFRANGE, _("converted value"));
          }

        Value_destroy(l);
        VALUE_NEW_INTEGER(l, v);
        break;
      }

    case V_REAL:
      {
        char *datainput;
        char *end;
        double v;
        int overflow;

        if (t->type != T_DATAINPUT)
          {
            return Value_new_ERROR(value, BADCONVERSION, _("real"));
          }

        datainput = t->u.datainput;
        v = Value_vald(datainput, &end, &overflow);
        if (end == datainput || (*end != '\0' && *end != ' ' && *end != '\t'))
          {
            return Value_new_ERROR(value, BADCONVERSION, _("real"));
          }

        if (overflow)
          {
            return Value_new_ERROR(value, OUTOFRANGE, _("converted value"));
          }

        Value_destroy(l);
        VALUE_NEW_REAL(l, v);
        break;
      }
    case V_STRING:
      {
        Value_destroy(l);
        Value_new_STRING(l);
        if (t->type == T_STRING)
          {
            String_appendString(&l->u.string, t->u.string);
          }
        else
          {
            String_appendChars(&l->u.string, t->u.datainput);
          }

        break;
      }

    default:
      assert(0);
    }

  return (struct Value *)0;
}

static struct Value *dataread(struct Value *value, struct Value *l)
{
  if (g_curdata.line == -1)
    {
      return Value_new_ERROR(value, ENDOFDATA);
    }

  if (g_curdata.token->type == T_DATA)
    {
      g_nextdata = g_curdata.token->u.nextdata;
      ++g_curdata.token;
    }

  if (convert(value, l, g_curdata.token))
    {
      return value;
    }

  ++g_curdata.token;
  if (g_curdata.token->type == T_COMMA)
    {
      ++g_curdata.token;
    }
  else
    {
      g_curdata = g_nextdata;
    }

  return (struct Value *)0;
}

static struct Value more_statements;
#include "bas_statement.c"
static struct Value *statements(struct Value *value)
{
more:
  if (g_pc.token->statement)
    {
      struct Value *v;

      if ((v = g_pc.token->statement(value)))
        {
          if (v == &more_statements)
            {
              goto more;
            }
          else
            {
              return value;
            }
        }
    }
  else
    {
      return Value_new_ERROR(value, MISSINGSTATEMENT);
    }

  if (g_pc.token->type == T_COLON && (g_pc.token + 1)->type == T_ELSE)
    {
      ++g_pc.token;
    }
  else if ((g_pc.token->type == T_COLON && (g_pc.token + 1)->type != T_ELSE) ||
           g_pc.token->type == T_QUOTE)
    {
      ++g_pc.token;
      goto more;
    }
  else if ((g_pass == DECLARE || g_pass == COMPILE) && g_pc.token->type != T_EOL &&
           g_pc.token->type != T_ELSE)
    {
      return Value_new_ERROR(value, MISSINGCOLON);
    }

  return Value_new_NIL(value);
}

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

void bas_init(int backslash_colon, int restricted, int uppercase, int lpfd)
{
  g_stack.begindata.line = -1;
  Token_init(backslash_colon, uppercase);
  Global_new(&g_globals);
  Auto_new(&g_stack);
  Program_new(&g_program);
  FS_opendev(STDCHANNEL, 0, 1);
  FS_opendev(LPCHANNEL, -1, lpfd);
  g_run_restricted = restricted;
}

void bas_runFile(const char *runFile)
{
  struct Value value;
  int dev;

  new();
  if ((dev = FS_openin(runFile)) == -1)
    {
      const char *errmsg = FS_errmsg;

      FS_putChars(0, _("bas: Executing `"));
      FS_putChars(0, runFile);
      FS_putChars(0, _("' failed ("));
      FS_putChars(0, errmsg);
      FS_putChars(0, _(").\n"));
    }
  else if (Program_merge(&g_program, dev, &value))
    {
      struct String s;

      FS_putChars(0, "bas: ");
      String_new(&s);
      Value_toString(&value, &s, ' ', -1, 0, 0, 0, 0, -1, 0, 0);
      FS_putString(0, &s);
      String_destroy(&s);
      FS_putChar(0, '\n');
      Value_destroy(&value);
    }
  else
    {
      struct Token line[2];

      Program_setname(&g_program, runFile);
      line[0].type = T_RUN;
      line[0].statement = stmt_RUN;
      line[1].type = T_EOL;
      line[1].statement = stmt_COLON_EOL;

      FS_close(dev);
      runline(line);
    }
}

void bas_runLine(const char *runLine)
{
  struct Token *line;

  line = Token_newCode(runLine);
  runline(line + 1);
  Token_destroy(line);
}

void bas_interpreter(void)
{
  if (FS_istty(STDCHANNEL))
    {
      FS_putChars(STDCHANNEL, "bas " CONFIG_INTERPRETER_BAS_VERSION "\n");
      FS_putChars(STDCHANNEL, "Copyright 1999-2014 Michael Haardt.\n");
      FS_putChars(STDCHANNEL,
                  "This is free software with ABSOLUTELY NO WARRANTY.\n");
    }

  new();
  while (1)
    {
      struct Token *line;
      struct String s;

      g_stopped = 0;
      FS_nextline(STDCHANNEL);
      if (FS_istty(STDCHANNEL))
        {
          FS_putChars(STDCHANNEL, "> ");
        }

      FS_flush(STDCHANNEL);
      String_new(&s);
      if (FS_appendToString(STDCHANNEL, &s, 1) == -1)
        {
          FS_putChars(STDCHANNEL, FS_errmsg);
          FS_flush(STDCHANNEL);
          String_destroy(&s);
          break;
        }

      if (s.length == 0)
        {
          String_destroy(&s);
          break;
        }

      line = Token_newCode(s.character);
      String_destroy(&s);
      if (line->type != T_EOL)
        {
          if (line->type == T_INTEGER && line->u.integer > 0)
            {
              if (g_program.numbered)
                {
                  if ((line + 1)->type == T_EOL)
                    {
                      struct Pc where;

                      if (Program_goLine(&g_program, line->u.integer, &where) ==
                          (struct Pc *)0)
                        {
                          FS_putChars(STDCHANNEL, (NOSUCHLINE));
                        }
                      else
                        {
                          Program_delete(&g_program, &where, &where);
                        }

                      Token_destroy(line);
                    }
                  else
                    {
                      Program_store(&g_program, line, line->u.integer);
                    }
                }
              else
                {
                  FS_putChars(STDCHANNEL,
                              _("Use `renum' to number program first"));
                  Token_destroy(line);
                }
            }
          else if (line->type == T_UNNUMBERED)
            {
              runline(line + 1);
              Token_destroy(line);
              if (FS_istty(STDCHANNEL) && g_bas_end)
                {
                  FS_putChars(STDCHANNEL, _("END program\n"));
                  g_bas_end = false;
                }
            }
          else
            {
              FS_putChars(STDCHANNEL, _("Invalid line\n"));
              Token_destroy(line);
            }
        }
      else
        {
          Token_destroy(line);
        }
    }
}

void bas_exit(void)
{
  /* Release resources */

  Auto_destroy(&g_stack);
  Global_destroy(&g_globals);
  Program_destroy(&g_program);
  if (g_labelstack)
    {
      free(g_labelstack);
      g_labelstack = (struct labelstack_s *)0;
    }

  /* Close files and devices.  NOTE that STDCHANNEL is also close here and
   * can no longer be use
   */

  FS_closefiles();
  FS_close(LPCHANNEL);
  FS_close(STDCHANNEL);
}
