/* #includes */ /*{{{C}}}*//*{{{*/
#include "config.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#ifdef HAVE_GETTEXT
#include <libintl.h>
#define _(String) gettext(String)
#else
#define _(String) String
#endif
#include <limits.h>
#include <math.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include "getopt.h"

#include "auto.h"
#include "bas.h"
#include "error.h"
#include "fs.h"
#include "global.h"
#include "program.h"
#include "value.h"
#include "var.h"

#ifdef USE_DMALLOC
#include "dmalloc.h"
#endif
/*}}}*/
/* #defines */ /*{{{*/
#define DIRECTMODE (pc.line==-1)
#ifndef __GNUC__
#define inline
#endif
/*}}}*/

/* types */ /*{{{*/
enum LabelType
{
  L_IF=1,
  L_ELSE,
  L_DO,
  L_DOcondition,
  L_FOR,
  L_REPEAT,
  L_SELECTCASE,
  L_WHILE,
  L_FUNC
};

struct LabelStack
{
  enum LabelType type;
  struct Pc patch;
};
/*}}}*/
/* variables */ /*{{{*/
static unsigned int labelStackPointer,labelStackCapacity;
static struct LabelStack *labelStack;
static struct Pc *lastdata;
static struct Pc curdata;
static struct Pc nextdata;
static enum { DECLARE, COMPILE, INTERPRET } pass;
static int stopped;
static long int optionbase;
static struct Pc pc;
static struct Auto stack;
static struct Program program;
static struct Global globals;
static int run_restricted;

int bas_argc;
char *bas_argv0;
char **bas_argv;
int bas_end;
/*}}}*/
/* forward 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);
/*}}}*/

static char *mytmpnam(void) /*{{{*/
{
  static char buf[_POSIX_PATH_MAX];
  const char *tmpdir;  
  unsigned int i;
  int fd=-1;

  if ((tmpdir=getenv("TMPDIR"))==(char*)0) tmpdir="/tmp";
  if ((strlen(tmpdir)+1+8+1)>=_POSIX_PATH_MAX) return (char*)0;
  i=getpid();
  while (i<0xffffffff && (snprintf(buf,sizeof(buf),"%s/%08x",tmpdir,i),(fd=open(buf,O_RDWR|O_CREAT|O_EXCL,0600)))==-1 && errno==EEXIST) ++i;
  if (fd==-1) return (char*)0;
  close(fd);
  return buf;
}
/*}}}*/
static int cat(const char *filename) /*{{{*/
{
  int fd;
  char buf[4096];
  ssize_t l;
  int err;

  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)
      {
        err=errno;
        close(fd);
        errno=err;
        return -1;
      }
      off+=w;
    }
  }
  if (l==-1)
  {
    err=errno;
    close(fd);
    errno=err;
    return -1;
  }
  close(fd);
  return 0;
}
/*}}}*/
static struct Value *lvalue(struct Value *value) /*{{{*/
{
  struct Symbol *sym;
  struct Pc lvpc=pc;

  sym=pc.token->u.identifier->sym;
  assert(pass==DECLARE || sym->type==GLOBALVAR || sym->type==GLOBALARRAY || sym->type==LOCALVAR);
  if ((pc.token+1)->type==T_OP)
  {
    struct Pc idxpc;
    unsigned int dim,capacity;
    int *idx;

    pc.token+=2;
    dim=0;
    capacity=0;
    idx=(int*)0;
    while (1)
    {
      if (dim==capacity && 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=pc;
      if (eval(value,_("index"))->type==V_ERROR || VALUE_RETYPE(value,V_INTEGER)->type==V_ERROR)
      {
        if (capacity) free(idx);
        pc=idxpc;
        return value;
      }
      if (pass==INTERPRET)
      {
        idx[dim]=value->u.integer;
        ++dim;
      }
      Value_destroy(value);
      if (pc.token->type==T_COMMA) ++pc.token;
      else break;
    }
    if (pc.token->type!=T_CP)
    {
      assert(pass!=INTERPRET);
      return Value_new_ERROR(value,MISSINGCP);
    }
    else ++pc.token;
    switch (pass)
    {
      case INTERPRET:
      {
        if ((value=Var_value(&(sym->u.var),dim,idx,value))->type==V_ERROR) 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(&stack,sym));
      }
      default: assert(0);
    }
    return (struct Value*)0;
  }
  else
  {
    ++pc.token;
    switch (pass)
    {
      case INTERPRET: return VAR_SCALAR_VALUE(sym->type==GLOBALVAR ? &(sym->u.var) : Auto_local(&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(&stack,sym));
      default: assert(0);
    }
    return (struct Value*)0;
  }
}
/*}}}*/
static struct Value *declare_lvalue(struct Value *value) /*{{{*/
{
  if (pass==DECLARE)
  {
    if
    (
      ((pc.token+1)->type==T_OP || Auto_find(&stack,pc.token->u.identifier)==0)
      && Global_variable(&globals,pc.token->u.identifier,pc.token->u.identifier->defaultType,(pc.token+1)->type==T_OP?GLOBALARRAY:GLOBALVAR,0)==0
    )
    {
      return Value_new_ERROR(value,REDECLARATION);
    }
  }
  return lvalue(value);
}
/*}}}*/
static struct Value *func(struct Value *value) /*{{{*/
{
  struct Identifier *ident;
  struct Pc funcpc=pc;
  int firstslot=-99;
  int args=0;
  struct Symbol *sym;

  assert(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 && !program.runnable && pass!=DECLARE)
  {
    if (compileProgram(value,0)->type==V_ERROR) return value;
    Value_destroy(value);
  }
  ident=pc.token->u.identifier;
  assert(pass==DECLARE || ident->sym->type==BUILTINFUNCTION || ident->sym->type==USERFUNCTION);
  ++pc.token;
  if (pass!=DECLARE)
  {
    firstslot=stack.stackPointer;
    if (ident->sym->type==USERFUNCTION && ident->sym->u.sub.retType!=V_VOID)
    {
      struct Var *v=Auto_pushArg(&stack);
      Var_new(v,ident->sym->u.sub.retType,0,(const unsigned int*)0,0);
    }
  }
  if (pc.token->type==T_OP) /* push arguments to stack */ /*{{{*/
  {
    ++pc.token;
    if (pc.token->type!=T_CP) while (1)
    {
      if (pass==DECLARE)
      {
        if (eval(value,_("actual parameter"))->type==V_ERROR) return value;
        Value_destroy(value);
      }
      else
      {
        struct Var *v=Auto_pushArg(&stack);

        Var_new_scalar(v);
        if (eval(v->value,(const char*)0)->type==V_ERROR)
        {
          Value_clone(value,v->value);
          while (stack.stackPointer>firstslot) Var_destroy(&stack.slot[--stack.stackPointer].var);
          return value;
        }
        v->type=v->value->type;
      }
      ++args;
      if (pc.token->type==T_COMMA) ++pc.token;
      else break;
    }
    if (pc.token->type!=T_CP)
    {
      if (pass!=DECLARE)
      {
        while (stack.stackPointer>firstslot) Var_destroy(&stack.slot[--stack.stackPointer].var);
      }
      return Value_new_ERROR(value,MISSINGCP);
    }
    ++pc.token;
  }
  /*}}}*/
  if (pass==DECLARE) Value_new_null(value,ident->defaultType);
  else
  {
    int i;
    int nomore;
    int argerr;
    int overloaded;

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

    sym=ident->sym;
    overloaded=(pass==COMPILE && sym->type==BUILTINFUNCTION && sym->u.sub.u.bltin.next);
    do
    {
      nomore=(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(&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(&stack,(struct Pc*)0);
          pc=funcpc;
          return value;
        }
        else sym=sym->u.sub.u.bltin.next;
      }
    } while (argerr);
    ident->sym=sym;
    if (sym->type==BUILTINFUNCTION)
    {
      if (pass==INTERPRET)
      {
        if (sym->u.sub.u.bltin.call(value,&stack)->type==V_ERROR) pc=funcpc;
      }
      else Value_new_null(value,sym->u.sub.retType);
    }
    else if (sym->type==USERFUNCTION)
    {
      if (pass==INTERPRET)
      {
        int r=1;

        pc=sym->u.sub.u.def.scope.start;
        if (pc.token->type==T_COLON) ++pc.token;
        else Program_skipEOL(&program,&pc,STDCHANNEL,1);
        do
        {
          if (statements(value)->type==V_ERROR)
          {
            if (strchr(value->u.error.msg,'\n')==(char*)0)
            {
              Auto_setError(&stack,Program_lineNumber(&program,&pc),&pc,value);
              Program_PCtoError(&program,&pc,value);
            }
            if (!FS_intr && stack.onerror.line!=-1)
            {
              stack.resumeable=1;
              pc=stack.onerror;
            }
            else /* No ON ERROR or terminal interrupt */
            {
              Auto_frameToError(&stack,&program,value);
              break;
            }
          }
          else if (value->type!=V_NIL) break;
          Value_destroy(value);
        } while ((r=Program_skipEOL(&program,&pc,STDCHANNEL,1)));
        if (!r) Value_new_VOID(value);
      }
      else Value_new_null(value,sym->u.sub.retType);
    }
    Auto_funcReturn(&stack,pass==INTERPRET && value->type!=V_ERROR ? &pc : (struct Pc*)0);
  }
  return value;
}
/*}}}*/

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=pc.token->type;
    if (!TOKEN_ISBINARYOPERATOR(op) || TOKEN_BINARYPRIORITY(op)!=prio) return value;
    oppc=pc;
    ++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,pass==INTERPRET);   break;
      case T_LE:    Value_le(value,&x,pass==INTERPRET);   break;
      case T_EQ:    Value_eq(value,&x,pass==INTERPRET);   break;
      case T_GE:    Value_ge(value,&x,pass==INTERPRET);   break;
      case T_GT:    Value_gt(value,&x,pass==INTERPRET);   break;
      case T_NE:    Value_ne(value,&x,pass==INTERPRET);   break;
      case T_PLUS:  Value_add(value,&x,pass==INTERPRET);  break;
      case T_MINUS: Value_sub(value,&x,pass==INTERPRET);  break;
      case T_MULT:  Value_mult(value,&x,pass==INTERPRET); break;
      case T_DIV:   Value_div(value,&x,pass==INTERPRET);  break;
      case T_IDIV:  Value_idiv(value,&x,pass==INTERPRET); break;
      case T_MOD:   Value_mod(value,&x,pass==INTERPRET);  break;
      case T_POW:   Value_pow(value,&x,pass==INTERPRET);  break;
      case T_AND:   Value_and(value,&x,pass==INTERPRET);  break;
      case T_OR:    Value_or(value,&x,pass==INTERPRET);   break;
      case T_XOR:   Value_xor(value,&x,pass==INTERPRET);  break;
      case T_EQV:   Value_eqv(value,&x,pass==INTERPRET);  break;
      case T_IMP:   Value_imp(value,&x,pass==INTERPRET);  break;
      default:      assert(0);
    }
    Value_destroy(&x);
  } while (value->type!=V_ERROR);
  if (value->type==V_ERROR) 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=pc.token->type;
  if (!TOKEN_ISUNARYOPERATOR(op) || TOKEN_UNARYPRIORITY(op)!=prio) return level(value);
  oppc=pc;
  ++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,pass==INTERPRET); break;
    case T_MINUS: Value_uneg(value,pass==INTERPRET);  break;
    case T_NOT:   Value_unot(value,pass==INTERPRET);  break;
    default:      assert(0);
  }
  if (value->type==V_ERROR) pc=oppc;
  return value;
}
/*}}}*/
static struct Value *eval8(struct Value *value) /*{{{*/
{
  switch (pc.token->type)
  {
    case T_IDENTIFIER: /*{{{*/
    {
      struct Pc var;
      struct Value *l;

      var=pc;
      if (pass==COMPILE)
      {
        if
        (
          ((pc.token+1)->type==T_OP || Auto_find(&stack,pc.token->u.identifier)==0)
          && Global_find(&globals,pc.token->u.identifier,(pc.token+1)->type==T_OP)==0
        ) return Value_new_ERROR(value,UNDECLARED);
      }
      assert(pass==DECLARE || pc.token->u.identifier->sym);
      if (pass!=DECLARE && (pc.token->u.identifier->sym->type==GLOBALVAR || pc.token->u.identifier->sym->type==GLOBALARRAY || 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);
          pc=var;
          return Value_new_ERROR(value,VOIDVALUE);
        }
      }
      break;
    }
    /*}}}*/
    case T_INTEGER: /*{{{*/
    {
      VALUE_NEW_INTEGER(value,pc.token->u.integer);
      ++pc.token;
      break;
    }
    /*}}}*/
    case T_REAL: /*{{{*/
    {
      VALUE_NEW_REAL(value,pc.token->u.real);
      ++pc.token;
      break;
    }
    /*}}}*/
    case T_STRING: /*{{{*/
    {
      Value_new_STRING(value);
      String_destroy(&value->u.string);
      String_clone(&value->u.string,pc.token->u.string);
      ++pc.token;
      break;
    }
    /*}}}*/
    case T_HEXINTEGER: /*{{{*/
    {
      VALUE_NEW_INTEGER(value,pc.token->u.hexinteger);
      ++pc.token;
      break;
    }
    /*}}}*/
    case T_OCTINTEGER: /*{{{*/
    {
      VALUE_NEW_INTEGER(value,pc.token->u.octinteger);
      ++pc.token;
      break;
    }
    /*}}}*/
    case T_OP: /*{{{*/
    {
      ++pc.token;
      if (eval(value,_("parenthetic"))->type==V_ERROR) return value;
      if (pc.token->type!=T_CP)
      {
        Value_destroy(value);
        return Value_new_ERROR(value,MISSINGCP);
      }
      ++pc.token;
      break;
    }
    /*}}}*/
    default: /*{{{*/
    {
      if (TOKEN_ISUNARYOPERATOR(pc.token->type)) return unarydown(value,eval8,6);
      else 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 (pc.token->type)
  {
    case T_STRING:
    case T_REAL:
    case T_INTEGER:
    case T_HEXINTEGER:
    case T_OCTINTEGER:
    case T_IDENTIFIER: if (!TOKEN_ISBINARYOPERATOR((pc.token+1)->type) && (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;
}
/*}}}*/

static void new(void) /*{{{*/
{
  Global_destroy(&globals);
  Global_new(&globals);
  Auto_destroy(&stack);
  Auto_new(&stack);
  Program_destroy(&program);
  Program_new(&program);
  FS_closefiles();
  optionbase=0;
}
/*}}}*/
static void pushLabel(enum LabelType type, struct Pc *patch) /*{{{*/
{
  if (labelStackPointer==labelStackCapacity)
  {
    struct LabelStack *more;

    more=realloc(labelStack,sizeof(struct LabelStack)*(labelStackCapacity?(labelStackCapacity*=2):(labelStackCapacity=32)));
    labelStack=more;
  }
  assert(labelStackPointer<labelStackCapacity);
  labelStack[labelStackPointer].type=type;
  labelStack[labelStackPointer].patch=*patch;
  ++labelStackPointer;
}
/*}}}*/
static struct Pc *popLabel(enum LabelType type) /*{{{*/
{
  if (labelStackPointer==0 || labelStack[labelStackPointer-1].type!=type) return (struct Pc*)0;
  else return &labelStack[--labelStackPointer].patch;
}
/*}}}*/
static struct Pc *findLabel(enum LabelType type) /*{{{*/
{
  int i;

  for (i=labelStackPointer-1; i>=0; --i) if (labelStack[i].type==type) return &labelStack[i].patch;
  return (struct Pc*)0;
}
/*}}}*/
static void labelStackError(struct Value *v) /*{{{*/
{
  assert(labelStackPointer);
  pc=labelStack[labelStackPointer-1].patch;
  switch (labelStack[labelStackPointer-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: Value_new_ERROR(v,STRAYFOR); 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 (labelStackPointer==0)
  {
    return _("program");
  }
  switch (labelStack[labelStackPointer-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: 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;

  assert(pc.token->type==T_IDENTIFIER);
  if (strcasecmp(pc.token->u.identifier->name,"mid$")==0) /* mid$(a$,n,m)=b$ */ /*{{{*/
  {
    long int n,m;
    struct Value *l;

    ++pc.token;
    if (pc.token->type!=T_OP) return Value_new_ERROR(value,MISSINGOP);
    ++pc.token;
    if (pc.token->type!=T_IDENTIFIER) return Value_new_ERROR(value,MISSINGSTRIDENT);
    if ((l=declare_lvalue(value))->type==V_ERROR) return value;
    if (pass==COMPILE && l->type!=V_STRING) return Value_new_ERROR(value,TYPEMISMATCH4);
    if (pc.token->type!=T_COMMA) return Value_new_ERROR(value,MISSINGCOMMA);
    ++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 (pass==INTERPRET && n<1) return Value_new_ERROR(value,OUTOFRANGE,"position");
    if (pc.token->type==T_COMMA)
    {
      ++pc.token;
      if (eval(value,_("length"))->type==V_ERROR || Value_retype(value,V_INTEGER)->type==V_ERROR) return value;
      m=value->u.integer;
      if (pass==INTERPRET && m<0) return Value_new_ERROR(value,OUTOFRANGE,_("length"));
      Value_destroy(value);
    }
    else m=-1;
    if (pc.token->type!=T_CP) return Value_new_ERROR(value,MISSINGCP);
    ++pc.token;
    if (pc.token->type!=T_EQ) return Value_new_ERROR(value,MISSINGEQ);
    ++pc.token;
    if (eval(value,_("rhs"))->type==V_ERROR || Value_retype(value,V_STRING)->type==V_ERROR) return value;
    if (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 (pc.token->type!=T_IDENTIFIER) return Value_new_ERROR(value,MISSINGVARIDENT);

      if (used==capacity)
      {
        struct Value **more;
      
        capacity=capacity?2*capacity:2;
        more=realloc(l,capacity*sizeof(*l));
        l=more;
      }

      if ((l[used]=declare_lvalue(value))->type==V_ERROR)
      {
        if (capacity) free(l);
        return value;
      }
      ++used;
      if (pc.token->type==T_COMMA) ++pc.token;
      else break;
    }
    if (pc.token->type!=T_EQ) return Value_new_ERROR(value,MISSINGEQ);
    ++pc.token;
    expr=pc;
    if (eval(value,_("rhs"))->type==V_ERROR) return value;
    for (i=0; i<used; ++i)
    {
      Value_clone(&retyped_value,value);
      if (pass!=DECLARE && VALUE_RETYPE(&retyped_value,(l[i])->type)->type==V_ERROR)
      {
        pc=expr;
        free(l);
        Value_destroy(value);
        *value=retyped_value;
        return value;
      }
      if (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;

  stack.resumeable=0;
  if (clearGlobals)
  {
    Global_destroy(&globals);
    Global_new(&globals);
  }
  else Global_clearFunctions(&globals);
  if (Program_beginning(&program,&begin))
  {
    struct Pc savepc;
    int savepass;

    savepc=pc;
    savepass=pass;
    Program_norun(&program);
    for (pass=DECLARE; pass!=INTERPRET; ++pass)
    {
      if (pass==DECLARE)
      {
        stack.begindata.line=-1;
        lastdata=&stack.begindata;
      }
      optionbase=0;
      FS_intr=0;
      stopped=0;
      program.runnable=1;
      pc=begin;
      while (1)
      {
        statements(v);
        if (v->type==V_ERROR) break;
        Value_destroy(v);
        if (!Program_skipEOL(&program,&pc,0,0)) { Value_new_NIL(v); break; }
      }
      if (v->type!=V_ERROR && labelStackPointer>0)
      {
        Value_destroy(v);
        labelStackError(v);
      }
      if (v->type==V_ERROR)
      {
        labelStackPointer=0;
        Program_norun(&program);
        if (stack.cur) Auto_funcEnd(&stack); /* Always correct? */
        pass=savepass;
        return v;
      }
    }
    pc=begin;
    if (Program_analyse(&program,&pc,v))
    {
      labelStackPointer=0;
      Program_norun(&program);
      if (stack.cur) Auto_funcEnd(&stack); /* Always correct? */
      pass=savepass;
      return v;
    }
    curdata=stack.begindata;
    pc=savepc;
    pass=savepass;
  }
  return Value_new_NIL(v);
}
/*}}}*/
static void runline(struct Token *line) /*{{{*/
{
  struct Value value;

  FS_flush(STDCHANNEL);
  for (pass=DECLARE; pass!=INTERPRET; ++pass)
  {
    curdata.line=-1;
    pc.line=-1;
    pc.token=line;
    optionbase=0;
    FS_intr=0;
    stopped=0;
    statements(&value);
    if (value.type!=V_ERROR && pc.token->type!=T_EOL)
    {
      Value_destroy(&value);
      Value_new_ERROR(&value,SYNTAX);
    }
    if (value.type!=V_ERROR && labelStackPointer>0)
    {
      Value_destroy(&value);
      labelStackError(&value);
    }
    if (value.type==V_ERROR)
    {
      struct String s;

      Auto_setError(&stack,Program_lineNumber(&program,&pc),&pc,&value);
      Program_PCtoError(&program,&pc,&value);
      labelStackPointer=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 (!program.runnable && pass==COMPILE)
    {
      Value_destroy(&value);
      (void)compileProgram(&value,0);
    }
  }

  pc.line=-1;
  pc.token=line;
  optionbase=0;
  curdata=stack.begindata;
  nextdata.line=-1;
  Value_destroy(&value);
  pass=INTERPRET;
  FS_allowIntr(1);
  do
  {
    assert(pass==INTERPRET);
    statements(&value);
    assert(pass==INTERPRET);
    if (value.type==V_ERROR)
    {
      if (strchr(value.u.error.msg,'\n')==(char*)0)
      {
        Auto_setError(&stack,Program_lineNumber(&program,&pc),&pc,&value);
        Program_PCtoError(&program,&pc,&value);
      }
      if (!FS_intr && stack.onerror.line!=-1)
      {
        stack.resumeable=1;
        pc=stack.onerror;
      }
      else /* No ON ERROR or terminal break */
      {
        struct String s;

        String_new(&s);
        if (!stopped)
        {
          stopped=0;
          FS_putChars(STDCHANNEL,_("Error: "));
        }
        Auto_frameToError(&stack,&program,&value);
        Value_toString(&value,&s,' ',-1,0,0,0,0,-1,0,0);
        while (Auto_gosubReturn(&stack,(struct Pc*)0));
        FS_putString(STDCHANNEL,&s);
        String_destroy(&s);
        Value_destroy(&value);
        break;
      }
    }
    Value_destroy(&value);
  } while (pc.token->type!=T_EOL || Program_skipEOL(&program,&pc,STDCHANNEL,1));
  FS_allowIntr(0);
}
/*}}}*/
static struct Value *evalGeometry(struct Value *value, unsigned int *dim, unsigned int geometry[]) /*{{{*/
{
  struct Pc exprpc=pc;

  if (eval(value,_("dimension"))->type==V_ERROR || (pass!=DECLARE && Value_retype(value,V_INTEGER)->type==V_ERROR)) return value;
  if (pass==INTERPRET && value->u.integer<optionbase)
  {
    Value_destroy(value);
    pc=exprpc;
    return Value_new_ERROR(value,OUTOFRANGE,_("dimension"));
  }
  geometry[0]=value->u.integer-optionbase+1;
  Value_destroy(value);
  if (pc.token->type==T_COMMA)
  {
    ++pc.token;
    exprpc=pc;
    if (eval(value,_("dimension"))->type==V_ERROR || (pass!=DECLARE && Value_retype(value,V_INTEGER)->type==V_ERROR)) return value;
    if (pass==INTERPRET && value->u.integer<optionbase)
    {
      Value_destroy(value);
      pc=exprpc;
      return Value_new_ERROR(value,OUTOFRANGE,_("dimension"));
    }
    geometry[1]=value->u.integer-optionbase+1;
    Value_destroy(value);
    *dim=2;
  }
  else *dim=1;
  if (pc.token->type==T_CP) ++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 (curdata.line==-1)
  {
    return Value_new_ERROR(value,ENDOFDATA);
  }
  if (curdata.token->type==T_DATA)
  {
    nextdata=curdata.token->u.nextdata;
    ++curdata.token;
  }
  if (convert(value,l,curdata.token))
  {
    return value;
  }
  ++curdata.token;
  if (curdata.token->type==T_COMMA) ++curdata.token;
  else curdata=nextdata;
  return (struct Value*)0;
}
/*}}}*/
static struct Value more_statements;
#include "statement.c"
static struct Value *statements(struct Value *value) /*{{{*/
{
  more:
  if (pc.token->statement)
  {
    struct Value *v;

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

  if (FS_intr)
  {
    stopped=1;
    return Value_new_ERROR(value,BREAK);
  }
  else if (pc.token->type==T_COLON && (pc.token+1)->type==T_ELSE) ++pc.token;
  else if ((pc.token->type==T_COLON && (pc.token+1)->type!=T_ELSE) || pc.token->type==T_QUOTE)
  {
    ++pc.token;
    goto more;
  }
  else if ((pass==DECLARE || pass==COMPILE) && pc.token->type!=T_EOL && pc.token->type!=T_ELSE)
  {
    return Value_new_ERROR(value,MISSINGCOLON);
  }
  return Value_new_NIL(value);
}
/*}}}*/

void bas_init(int backslash_colon, int do_repeat, int restricted, int uppercase, int lpfd) /*{{{*/
{
#ifdef HAVE_GETTEXT
  bindtextdomain("bas",LOCALEDIR);
  textdomain("bas");
#endif
  stack.begindata.line=-1;
  Token_init(backslash_colon,do_repeat,uppercase);
  Global_new(&globals);
  Auto_new(&stack);
  Program_new(&program);
  FS_opendev(STDCHANNEL,0,1);
  FS_opendev(LPCHANNEL,-1,lpfd);
  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(&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(&program,runFile);
    program.unsaved=0;
    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 " VERSION "\n");
    FS_putChars(STDCHANNEL,"Copyright 1999-2019 Michael Haardt.\n");
    FS_putChars(STDCHANNEL,_("This is free software with ABSOLUTELY NO WARRANTY.\n"));
  }
  new();
  while (1)
  {
    struct Token *line;
    struct String s;

    FS_intr=0; stopped=0;
    FS_allowIntr(1);
    FS_nextline(STDCHANNEL);
    if (FS_istty(STDCHANNEL)) FS_putChars(STDCHANNEL,"> ");
    FS_flush(STDCHANNEL);
    String_new(&s);
    if (FS_appendToString(STDCHANNEL,&s,1)==-1)
    {
      if (FS_intr)
      {
        FS_putChars(STDCHANNEL,_("\nBreak\n"));
        FS_flush(STDCHANNEL);
        String_destroy(&s);
        continue;
      }
      else
      {
        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 (program.numbered)
        {
          if ((line+1)->type==T_EOL)
          {
            struct Pc where;

            if (Program_goLine(&program,line->u.integer,&where)==(struct Pc*)0) FS_putChars(STDCHANNEL,NOSUCHLINE_MSG);
            else Program_delete(&program,&where,&where);
            Token_destroy(line);
          }
          else Program_store(&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) && bas_end>0)
        {
          FS_putChars(STDCHANNEL,_("END program\n"));
          bas_end=0;
        }
      }
      else
      {
        FS_putChars(STDCHANNEL,_("Invalid line\n"));
        Token_destroy(line);
      }
    }
    else Token_destroy(line);
  }
  FS_allowIntr(0);
}
/*}}}*/
void bas_exit(void) /*{{{*/
{
  Auto_destroy(&stack);
  Global_destroy(&globals);
  Program_destroy(&program);
  if (labelStack) free(labelStack);
  FS_closefiles();
  FS_close(LPCHANNEL);
  FS_close(STDCHANNEL);
}
/*}}}*/
