/*
  HI65 - a high-level Commodore 65 emulator
  Copyright (C) 2013-2023  Simone Gremmo
  Contact: devilmaster@email.com

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see
  http://www.gnu.org/licenses/gpl.txt
*/

#include <allegro.h>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <fstream>
#include "f8x8.h"
#include "gdef.h"
#include "pro.h"
#include "var.h"

#define DEBUG_PER_IF

void getinstructionendaddress(void)
{
 uint aux;

 // we read a 2-byte number
 instruction_end_address=program[pc++];
 aux=program[pc];
 aux-=32; // during the tokenization, the value 0x20 is added to the
          // MSB of the address, so the same value must be subtracted
          // to obtain the correct address
 aux<<=8; // the MSB is shifted 8 bits to the left
 instruction_end_address+=aux;
 pc++;
}

// The next two functions are used by the interpretation of conditional
// statements, in order to process a comparison condition.
void processnumbercondition(void)
{
 uchar comparison,mubak;
 double firstnumterm,secondnumterm;

 mubak=enable_muparser;
 enable_muparser=0;
 firstnumterm=evaluateexpression();

 // Now we evaluate the comparison. The possible values can be:
 // C65EQU < > D L G
 comparison=evaluatecondition();
 
 // After the comparison sign, there may or may not be on a number of spaces
 skipspaces();
  
 secondnumterm=evaluateexpression();
 enable_muparser=mubak;
 
 // Now there might or might not be a number of spaces, which we skip
 skipspaces();

 // Now that we reached the THEN token, we must
 // decide whether the condition is true or false, in order to decide
 // whether to execute the THEN or ELSE branch
 condition=conditionistrue_num(firstnumterm,comparison,secondnumterm);
}

void processstringcondition(void)
{
 uchar comparison;
 uint i,array_index;
 
 // Now we might be on a " character or on the first character of a
 // string variable. If we are on a ", we "get" the immediate string
 // and we store it into DS$, normally used for DOS errors
 if (program[pc]==0x22)
 {
  stringvarpos=140; // DS$
  pc++; // we go past it
  getstring(0);
 }
 else // if we are on a string variable
 {
  stringvarpos=locatevar();
  array_index=getarrayindex();
 }
 // we copy the string into the stringtemp array
 for (i=0; i<255; i++)
  stringtemp[i][0]=stringvars[i][stringvarpos][array_index];
  
 // We advance to the final " or to the $ sign
 while (program[pc]!=0x22 && program[pc]!='$')
  pc++;
  
 // We advance to the comparison
 pc++; // skipping the $ character
 // Now we might be on an open parenthesis, which means the last
 // variable was an element of an array. If we are, we skip the
 // array index
 if (program[pc]=='(')
  skiparrayindex();
 skipspaces();
 
 // Now we evaluate the comparison. The possible values can be:
 // C65EQU D
 comparison=evaluatecondition();
 // After the comparison sign, there may or may not be on a number of spaces
 skipspaces();
 
 // We now are on the second term of the comparison. It may be an
 // immediate string or a string variable, so we do the same things
 // we did before
 
 if (program[pc]==0x22)
 {
  stringvarpos=140; // DS$
  pc++; // we go past it
  getstring(0);
 }
 else // if we are on a string variable
 {
  stringvarpos=locatevar();
  array_index=getarrayindex();
 }

 // we copy the string into the stringtemp array (this time, to the
 // other position)
 for (i=0; i<255; i++)
  stringtemp[i][1]=stringvars[i][stringvarpos][array_index];
  
 // We advance to the final " or to the $ sign
 while (program[pc]!=0x22 && program[pc]!='$')
  pc++;
 pc++;
   
 // Now there might or might not be a number of spaces, which we skip
 skipspaces();

 // Now that we reached the THEN token, we must
 // decide whether the condition is true or false, in order to decide
 // whether to execute the THEN or ELSE branch
 condition=conditionistrue_string(comparison);
}

void singlelinediscrimination(void)
{
 // If the condition is true, we must execute the THEN instructions.
 // In that case, we do nothing, because they are executed normally.
 // If the condition is false, we must execute the ELSE instructions.
 if (!condition)
 {
  // we skip to the ELSE token or to the instruction end address,
  // whichever comes first (of course, if we find the instruction end
  // address, it means that there is no ELSE)
  while (program[pc]!=ELSE && pc<instruction_end_address)
   pc++;
   
  // if we found an ELSE, we go back to the previous token (assumed to
  // be a colon)
  if (program[pc]==ELSE)
  {
   while (program[pc--]==' ')
    ;
  }
  inside_if_clause=0;
 }
}

void multilinediscrimination(void)
{
 // If the condition is true, we must continue and execute the THEN
 // branch. If it's not, we must skip to the correct BEND token
 // (what constitutes "correct"?) and eventually execute the ELSE
 // branch
 if (!condition)
 {
  coming_from=ELSE;
  while (program[pc]!=0xFE
    || program[pc+1]!=0x19) // BEND = 0xFE19
  pc++;
   
  // If the BEND token is followed by a : token, we reach it
  // If it's not, we reach the instruction end address
  while (program[pc]!=':' && program[pc])
   pc++;
 }
 else
  coming_from=THEN;
}

void skipspacesandevaluateexpression(uchar index)
{
 // We reach the = token and skip the eventual following spaces
 while (program[pc]!=C65EQU)
  pc++;
 pc++;
 skipspaces();
 
 // We evaluate the numeric expression, unless we reached a reserved variable
 if (numvarpos!=DS  // numvars[140][0] (DS) is reserved for DOS errors
  && numvarpos!=EL  // numvars[170][0] (EL) is reserved for BASIC error line
  && numvarpos!=ER  // numvars[176][0] (ER) is reserved for BASIC errors
  && numvarpos!=TI) // numvars[722][0] (TI) is reserved for the system timer
  numvars[numvarpos][index]=evaluateexpression();
 else
  printerror(SYNTAXERROR);
}

void interpret(void)
{
 uint two_bytes_token, aux, i;

#ifdef COLON_TEST
 if (!found_colon)
 {
#endif

  // old_instruction_end_address stores the value of the end address
  // of the PREVIOUS instruction
  old_instruction_end_address=instruction_end_address;

  // Whenever this function is called, the program counter is on the
  // first char of an instruction. The first char is the address where
  // the instruction ends.
  getinstructionendaddress();
  
  // the next two chars comprise the line number, which is tokenized
  // LSB-first (little endian).
  line_number=program[pc];
  pc++;
  aux=program[pc];
  aux<<=8;
  line_number=aux+line_number;
  pc++;
#ifdef COLON_TEST
 }
 else
 {
  found_colon=0;
 }
#endif

 if (!must_exit)
 {               
  // Now the program counter is positioned on the instruction token.
  // An appropriate action must be executed according to the token value.
  switch (program[pc])
  {
   // A 2-byte token will ALWAYS start with 0xFE or 0xCE
   case 0xFE:
   case 0xCE:
        // two_bytes_token=program[pc++]+(program[pc++]<<8);
        two_bytes_token=program[pc];
        pc++;
        aux=program[pc];
        two_bytes_token<<=8;
        two_bytes_token=two_bytes_token+aux;
        pc++;
        switch (two_bytes_token)
        {
         case APPEND:  Append(); break;
         case BANK:    Bank(); break;
         case BEND:    Bend(); break;
         case BORDER:  Border(); break;
         case DCLOSE:  Dclose(); break;
         case ERASE:   DeleteEraseScratch(two_bytes_token); break;
         case DOPEN:   Dopen(); break;
         case ELLIPSE: Ellipse(); break; 
         case PALETTE: Palette(); break;   
         case PEN:     Pen(); break;
         case SCREEN:
              // the SCREEN command is never used on its own. This is
              // followed by another token, which may or may not be
              // separated from the first by a number of spaces
              skipspaces();

              switch (program[pc])
              {
               // there could be two 2-byte-tokens one after another
               case 0xFE:
               case 0xCE:
                    two_bytes_token=program[pc];
                    pc++;
                    aux=program[pc];
                    two_bytes_token<<=8;
                    two_bytes_token=two_bytes_token+aux;
                    switch (two_bytes_token)
                    {
                     case SET: ScreenSet(); break;
                    }
                    break;
               case DEF:   ScreenDef(); break;
               case CLOSE: ScreenClose(); break;
               case OPEN:  ScreenOpen(); break;
              }
              break; // this breaks the SCREEN case
         case SLEEP: Sleep(); break;
        }
        break; // this breaks the "two possible initial bytes for long token" case
   case BOX:        Box(); break;
   case CHAR:       Char(); break;
   case CIRCLE:     Circle(); break;
   case CLR:        Clr(); break;
   case DELETE:
   case SCRATCH:    DeleteEraseScratch((uint)program[pc]); break;
   case DIM:        Dim(); break;
   case DLOAD:      Dload(0); break;
   case DO:         Do(); break;
   case ELSE:       Else(); break;
   case END:        End(); break;
   case EXIT:       Exit(); break;
   case FOR:        For(); break;
   case GET:        Get(); break;
   case GO:         Go(0); break;
   case GOSUB:      Gosub(); break;
   case GOTO:       Goto(); break;
   case IF:         If(); break;
   case INPUT:      Input(); break;
   case INPUTSHARP: InputSharp(); break;
   case LEN:        printerror(SYNTAXERROR); break;
   case LET:        Let(); break;
   case LINE:       Line(); break;
   case LOCATE:     Locate(); break;
   case LOOP:       Loop(); break;
   case NEXT:       Next(); break;
   case PAINT:      Paint(); break;
   case PRINT:      Print(); break;
   case PRINTSHARP: PrintSharp(); break;
   case SCNCLR:     Scnclr(); break;
   case READ:       Read(); break;
   case REM:        Rem(); break;
   case RESTORE:    Restore(); break;
   case RETURN:     Return(); break;
   case RND:        printerror(SYNTAXERROR); break;
   case RUN:        Run(0); break;
   case TRAP:       Trap(); break;
   default: if (program[pc]>='A' && program[pc]<='Z')
            {
             // if the first character is an ASCII letter instead of a
             // token, we might be on a variable assignment (LET) or
             // on the "4-byte token" DRAW. The C65 does not have a DRAW
             // command, and such a command is not tokenized by Petcat
             // either, but it appears in several BASIC 10 programs on
             // the Internet. For this reason, we consider the four
             // chars 'D','R','A','W' like a 4-byte token, and if we
             // find them, we run the appropriate function.
             // We do the same for the pseudocommand MUPARSER, which controls
             // the status of the MuParser expression evaluator.
             // Instead, if we find a a C65EQU (=) token before the end
             // of the instruction, it is a variable assignment (LET).
             
             if (program[pc]=='D' &&
               program[pc+1]=='R' &&
               program[pc+2]=='A' &&
               program[pc+3]=='W')
              Draw();
             else if (program[pc]=='M' &&
                    program[pc+1]=='U' &&
                    program[pc+2]=='P' &&
                    program[pc+3]=='A' &&
                    program[pc+4]=='R' &&
                    program[pc+5]=='S' &&
                    program[pc+6]=='E' &&
                    program[pc+7]=='R')
              MuParserToggle(0);
             else
             {              
              aux=pc;
              while (program[aux]!=C65EQU && aux<instruction_end_address)
               aux++;
              if (program[aux]==C65EQU)
               Let();
              else
              {              
               /* prints((uchar *)"program[");
               printn((ulong)pc);
               prints((uchar *)"]=");
               printn((ulong)program[pc]);
               Enter(); */
               printerror (SYNTAXERROR);
              }
             }
            }
            break;
  }
 }
 else // must_exit
 {
  // If we must exit from a loop, we must NOT execute the instructions
  // that follow in the loop. Instead, we must reach the end of the
  // correct loop
  switch (program[pc])
  {
   case DO: loopnestinglevel++; break;
   case LOOP:
        if (loopnestinglevel==oldloopnestinglevel)
         must_exit=0;
        loopnestinglevel--;
        break;
   default: while (program[pc]!=C65EQU && pc<instruction_end_address)
             pc++;
  }
  
 }
 // At the end, we check if we found a GOTO or an END. If we did not,
 // we proceed to the next instruction:
 if (!found_goto && !found_end)
 {
  // the program counter is moved forward until it either finds a ':'
  // or reaches the instruction end address.
  // --
  // If we found a ':' we must walk 1 step forward and skip the
  // eventual following spaces, instead of "advancing to the next
  // instruction or the end of the program".
  // --
  // When the next interpretation cycle begins: if we found a ':' char
  // the interpreter must immediately interpret the next instruction,
  // otherwise everything is done as usual
  // Eventual THEN or ELSE tokens will correspond to different values
  // of found_colon. The variable will still be considered true because
  // they precede instructions that must be executed (just like
  // a colon), but the actual value will tell what has been found.
#ifdef COLON_TEST
  while (pc<instruction_end_address && program[pc]!=':' && program[pc]!=THEN && program[pc]!=ELSE && program[pc]!=DO)
   pc++;
  if (program[pc]==':' || program[pc]==THEN || program[pc]==ELSE || program[pc]==DO)
   found_colon=program[pc];
#else
  pc=instruction_end_address;
#endif
 
  // The program counter is advanced to the beginning of the next
  // instruction (non-null char) or the end of the program (three
  // contiguous null char)
#ifdef COLON_TEST
  if (!found_colon)
  {
#endif
   while (program[pc]==0 && null_char_counter<3)
   {
    null_char_counter++;
    pc++;
   }
   
   // Additional check: it might happen that the LSB of the current
   // instruction end address is 0. When this happens, we have just
   // counted 2 null chars (the previous instruction end and the LSB
   // of the current instruction end address. In this case, we must
   // go back 1 byte to read the instruction end address correctly.
   if (null_char_counter==2)
    pc--;
 
   // if we are at the beginning of the next instruction,
   // null_char_counter is reset to zero
   // if (program[pc]!=0)
   if (null_char_counter<3)
    null_char_counter=0;
#ifdef COLON_TEST
  }
  else if (program[pc]!=DO) // if we are on a DO instruction we don't need to do this, we're already there
  {
   pc++;
   skipspaces();
  }
#endif
 }
 else // If instead we DID find a GOTO:
 {
  if (found_goto)
   found_goto=0;
 }
}

void interpret_line(void)
{ 
 lc=0;
 if (linebuffer[0]=='b' &&
     linebuffer[1]=='o' &&
     linebuffer[2]=='r' &&
     linebuffer[3]=='d' &&
     linebuffer[4]=='e' &&
     linebuffer[5]=='r')
  Border();
 else if (linebuffer[0]=='d' &&
          linebuffer[1]=='l' &&
          linebuffer[2]=='o' &&
          linebuffer[3]=='a' &&
          linebuffer[4]=='d')
  Dload(1);
 else if (linebuffer[0]=='g' &&
          linebuffer[1]=='o')
  Go(1);
 else if (linebuffer[0]=='h' &&
          linebuffer[1]=='e' &&
          linebuffer[2]=='l' &&
          linebuffer[3]=='p')
 {
  printn((ulong)numvars[EL][0]);
  Enter();
  Ready();
 }
 else if (linebuffer[0]=='m' &&
          linebuffer[1]=='u' &&
          linebuffer[2]=='p' &&
          linebuffer[3]=='a' &&
          linebuffer[4]=='r' &&
          linebuffer[5]=='s' &&
          linebuffer[6]=='e' &&
          linebuffer[7]=='r')
  MuParserToggle(1);
 else if (linebuffer[0]=='p' &&
          linebuffer[1]=='r' &&
          linebuffer[2]=='i' &&
          linebuffer[3]=='n' &&
          linebuffer[4]=='t')
  Print();
 else if (linebuffer[0]=='r' &&
          linebuffer[1]=='u' &&
          linebuffer[2]=='n')
 {
  program_loaded_from_frontend=0;
  Run(1);
 }
 else if (linebuffer[0]>='0' && linebuffer[0]<='9')
 {
  Enter();
  if (screentype==65)
   forecolor=makecol(240,240,0);
  acquire_screen();
  if (!screentype || screentype==65)
  {
   prints((uchar *)"hi65 does not support direct program editing at the moment"); Enter(); Enter();
   prints((uchar *)"the correct procedure to create and run a program is as follows:"); Enter();
   prints((uchar *)"1) write your program with a text editor in your operating system"); Enter();
   prints((uchar *)"2) tokenize it with petcat"); Enter();
   prints((uchar *)"3) open hi65, load the tokenized program and run it"); Enter();
  }
  else if (screentype==20)
  {
   prints((uchar *)"no program editing"); Enter();
   prints((uchar *)"on hi65 for now"); Enter(); Enter();
   prints((uchar *)"please use petcat"); Enter();
  }
  else
  {
   prints((uchar *)"hi65 does not support direct program"); Enter();
   prints((uchar *)"editing at the moment"); Enter(); Enter();
   prints((uchar *)"the correct procedure to create and run"); Enter();
   prints((uchar *)"a program is as follows:"); Enter();
   prints((uchar *)"1) write your program with a text editor"); Enter();
   prints((uchar *)"in your operating system"); Enter();
   prints((uchar *)"2) tokenize it with petcat"); Enter();
   prints((uchar *)"3) open hi65, load the tokenized program"); Enter();
   prints((uchar *)"and run it"); Enter();
  }
  if (screentype==65)
   forecolor=makecol(240,240,240);
  Ready();
 }
 else if (linebuffer[0])
 {
  Enter();
  if (screentype==65)
   forecolor=makecol(240,0,0);
  acquire_screen();
  prints((uchar *)"?syntax error");
  if (screentype==65)
   forecolor=makecol(240,240,240);
  Ready();
 }
}

void getstring(uint index)
{
 uchar i;
 

 // The string is filled with null chars to get rid of eventual pre-existing longer strings
 for (i=0; i<255; i++)
  stringvars[i][stringvarpos][index]=0;
 
 i=0;


 // We collect every string character until we reach the " character
 // marking the end of the string or the end of the instruction (the
 // second " is optional
 // In any case, we stop when we reach the 255th character
 while (program[pc]!=0x22 && program[pc]!=0 && i<255)
 {
  if (program[pc]>='A' && program[pc]<='Z')
   stringvars[i][stringvarpos][index]=program[pc]+32;
  else
   stringvars[i][stringvarpos][index]=program[pc];
  i++;
  pc++;
 }
}

void getdatastring(void)
{
 uchar i;
 
 i=0;
 
 // We collect every string character until we reach the " character
 // marking the end of the string or the end of the instruction (the
 // second " is optional
 while (program[dc]!=0x22 && program[dc]!=0)
 {
  if (program[dc]>='A' && program[dc]<='Z')
   stringvars[i][stringvarpos][0]=program[dc]+32;
  else
   stringvars[i][stringvarpos][0]=program[dc];
  i++;
  dc++;
 }
}

// IMPLEMENTATION OF THE INDIVIDUAL BASIC 10 COMMANDS STARTS HERE

// NOTE: some commands are not implemented in the physical C65, but this
// program implements them anyway because its goal is high quality,
// not high fidelity.

double Asc(void) // ASC (string)
{
 uchar i,command,parameter,number_of_parentheses;
 uint buffer,otherstringvarpos,otherarray_index;
 double asciivalue;
  
 // We are still on the token. We reach the open parenthesis...
 while (program[pc]!='(' || pc==instruction_end_address || program[pc]==':')
  pc++;
 if (pc==instruction_end_address || program[pc]==':')
  printerror(SYNTAXERROR); // (no parenthesis? no party)
 else
 {
  skipspaces();
  pc++; // ...and go past it
 }
 
 // Now we might be:
 // - on a quotes symbol (the parameter is an immediate string)
 // - on a letter (the parameter is a variable)
 // - on a string operation token
 
 while (pc<instruction_end_address && program[pc]!=':')
 {
  if (program[pc]==0x22)
  {
   pc++;
   asciivalue=(double)program[pc];
   while (program[pc]!=0x22)
    pc++;
  }
  else if (program[pc]>='A' && program[pc]<='Z')
  {
   otherstringvarpos=locatevar();
   otherarray_index=getarrayindex();
   asciivalue=(double)stringvars[0][otherstringvarpos][otherarray_index];
  }
  
  // THE FOLLOWING LINES HAVE BEEN COPIED AND PASTED FROM THE LEN COMMAND
  // AND MUST BE CHANGED
  /* else // part of an immediate string or part of a string variable
  {
   command=program[pc];
   // After the command, there will be eventually an open parenthesis, so
   // we reach it
   while (program[pc]!='(')
    pc++;
   pc++; // We go past it and skip the eventual spaces
   skipspaces();
   buffer=pc;
   // we reach the numeric parameter
   while (program[pc]!=',')
    pc++;
   pc++;
   skipspaces();
   
   switch (command)
   {
    case MID$:
         // we reach the second parameter
         while (program[pc]!=',')
          pc++;
         pc++;
         skipspaces();
    case LEFT$:
    case RIGHT$:
         // If we're not on a MID$ token, we already are on the correct parameter
         parameter=(uchar)evaluateexpression();
         length+=(double)parameter;
         break;
    case HEX$:
         // this operator always returns a 4-character string
         length+=4;
         break;
    case CHR$:
         // this operator always returns a 1-character string
         length++;
         break;
   }
  } */
  
  // Next, we advance until we either find the instruction end address
  // or a C65ADD token
  while (pc<instruction_end_address && program[pc]!=':' && program[pc]!=C65ADD)
   pc++;
  if (program[pc]==C65ADD)
   pc++;
 }
 
 return asciivalue;
}

void Append(void) // APPEND#1, "filename"
                  // APPEND#1, (file$)
{
 uint i,array_index;
 uchar lfn; // logical file number
 uchar forbidden=0;
 uchar *aux;
 
 // We have already moved past the APPEND token, so we don't need to
 // increase pc again
 
 // After the APPEND token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we must be on a # character. If we are not, the program is
 // stopped with a SYNTAX ERROR
 if (program[pc]!='#')
  printerror(SYNTAXERROR);
 else
 {
  // We move past the # character and get the logical file number we
  // will use
  pc++;
  lfn=(uchar)getnum();
  
  // We are now past the comma, so we skip more eventual spaces
  skipspaces();
  
  // Now we may be on a "quotes" character (so the file name is an
  // immediate string) or on an open parenthesis (so the file name is
  // contained in a string variable).
  if (program[pc]==0x22) // if we are on a quote character
  {
   stringvarpos=140; // DS$
   array_index=0;
   pc++; // we go past it
   getstring(0);
  }
  else if (program[pc]==0x28) // if we are on an open parenthesis
  {
   pc++;
   skipspaces();
   stringvarpos=locatevar();
   array_index=getarrayindex();   
  }
  else // if we are not on either, the syntax is wrong
   printerror(SYNTAXERROR);
   
  // We copy the string into the aux array, but only if we haven't
  // encountered any error
  if (numvars[ER][0]==-1)
  {
   for (i=0; i<255; i++)
    aux[i]=stringvars[i][stringvarpos][array_index];
    
   // This check is done to protect n00bs from themselves. We cannot
   // allow them to open system files (so we refuse to open a file that
   // ends in .exe or .dll) as well as to wander to other directories
   // (so we won't accept the characters / and \ as part of the file
   // name, nor two consecutive periods, an asterisk or a question
   // mark)
   // (move the check to a dedicated function)
   for (i=0; aux[i]; i++)
   {
    if (aux[i]=='\\' ||
        aux[i]=='/' ||
        (aux[i]=='d' && aux[i+1]=='l' && aux[i+2]=='l') ||
        (aux[i]=='e' && aux[i+1]=='x' && aux[i+2]=='e') ||
        (aux[i]=='.' && aux[i+1]=='.') ||
        aux[i]=='*' || 
        aux[i]=='?')
    {
     forbidden=1;
     break;
    }
   }
   if (forbidden)
    printerror(INVALIDFILENAME);
   else
   {
    // Now we can open the file, but only if it isn't already open.
    // If the file does not exist, it will be created (unlike with the
    // DOPEN command, which gives an error)
    if (!filestatus[lfn])
    {
     cFile[lfn]=fopen ((const char *)aux,"a");
     filestatus[lfn]=OPEN_TO_APPEND;
    }
    else
     printerror(FILEOPEN);
   }
  } 
 }
}

void Bank(void)
{
 pc++; // We move past the BANK token
 
 // After the BANK token, there may or may not be a number of spaces
 skipspaces();
 
 currentbank=(uchar)getnum();
}

void Bend(void)
{
 uint aux;
 
 switch (coming_from)
 {
  case THEN:
       coming_from=0;
  
       // When we reach this function from the THEN branch of a
       // multiline IF statement, there are 2 possibilities. Either
       // 1) the BEND token marks the end of the IF statement (so we
       // must reach the instruction end address)
       // or
       // 2) the BEND token is followed by a colon, an ELSE token and
       // a BEGIN token (so we must skip forward to the BEND token that
       // actually marks the end of the IF statement)
       // In the first case, if we move forward we will find the
       // instruction end address; in the second case, we will find a
       // colon
       // BEND = 0xFE19
  
       while (pc!=instruction_end_address && program[pc]!=':')
        pc++;
       
       // If we have reached the instruction end address, we do nothing
       // If we reached a colon, we advance to the BEND token that
       // marks the end of the IF statement
       if (program[pc]==':')
       {
        while (program[pc])
         pc++;
        pc++;
        getinstructionendaddress();
        
        // Now we reached the next instruction. For this one and each
        // that follows, we advance one bit at a time until we find
        // the correct BEND token.
        // UNFINISHED: the current implementation of this cycle does
        // not account for nested IF statements)
        while (program[pc]!=0xFE &&
               program[pc+1]!=0x19)
        {
         pc++;
         if (pc==instruction_end_address)
         {
          pc++;
          instruction_end_address=program[pc];
         }
        }
        // Now we found the correct BEND token, so we advance to the
        // instruction end address and continue the program
        while (program[pc])
         pc++;
       }
       break;
 }
 inside_if_clause--;
}

void Border(void) // BORDER color
{
 // This function does not do anything at the following resolutions:
 //  640 x 400
 // 1280 x 720
 // 1280 x 800
 // because at those resolutions, the border lies outside the screen.
 uchar par;
 uint auxcolor;
 
 pc++; // We move past the BORDER token
 
 // After the BORDER token, there may or may not be a number of spaces
 skipspaces();
 
 par=(uchar)getnum();
 auxcolor=makecol(palette[DrawViewScreen[1]][par][0],
                   palette[DrawViewScreen[1]][par][1],
                   palette[DrawViewScreen[1]][par][2]);
                   
 makeborder(auxcolor);
}

// The BOX command can be used to draw any kind of quadrilateral, not
// just rectangles. This is possible because the coordinates for each
// of the vertices are specified 
void Box(void) // BOX x0,y0, x1,y0, x0,y1, x1,y1 [,solid] (up left, up right, down left, down right)
{
 uint par[9],xmiddle,ymiddle;
 uchar i,j,k;
 
 // this array contains the native (Allegro screen) coordinates for
 // each of the vertices
 // uint vertex[4][2];
 
 pc++; // We move past the BOX token
 
 // After the BOX token, there may or may not be a number of spaces
 skipspaces();
 
 // The first eight parameters are compulsory. They are the XY coordinates
 // of each of the 4 vertices of the quadrilateral
 for (i=0; i<8; i++)
  par[i]=(uint)getnum();
  
 par[8]=0; // The default case for the ninth parameter is 0 (outline only)
 
 // The ninth parameter is optional. To tell whether it's there or not,
 // we read the current program byte. If it's 0 or ':', we are at the
 // end of the instruction. Otherwise, the parameter is there and can
 // be read
 if (program[pc] && program[pc]!=':')
  par[8]=(uint)getnum();
 
 vertex[0][0]=par[0]*xzoom; vertex[0][1]=par[1]*yzoom;
 vertex[1][0]=par[2]*xzoom; vertex[1][1]=par[3]*yzoom;
 vertex[2][0]=par[4]*xzoom; vertex[2][1]=par[5]*yzoom;
 vertex[3][0]=par[6]*xzoom; vertex[3][1]=par[7]*yzoom;
 
 // The main outline is traced
 // acquire_screen();
 fastbox(0,0);

 // Additional outlines must be traced to simulate different resolutions
 for (j=2; j<7; j++)
 {
  k=j-1;
  if (xzoom>=j)
  {
   for (i=0; i<j; i++)
    fastbox(k,i);
  }
  if (yzoom>=j)
  {
   for (i=0; i<j; i++)
    fastbox(i,k);
  }
  if (xzoom>=j && yzoom>=j)
   fastbox(k,k);
 }
 
 // If the ninth parameter is !=0, the box must be filled
 // (this only works when the box is convex)
 if (par[8])
 {
  xmiddle=(vertex[1][0]+screen_border_x+vertex[2][0]+screen_border_x)>>1;
  ymiddle=(vertex[1][1]+screen_border_y+vertex[2][1]+screen_border_y)>>1;
  floodfill(screen,xmiddle,ymiddle,forecolor);
 }
 // release_screen();
}

void Char(void) // CHAR column, row, height, width, direction, "string" [,charsetadr]
{
 uint par[5],i,array_index;
 uchar shiftedbak,xzoombak,yzoombak;
 
 pc++; // We move past the CHAR token
 
 // After the CHAR token, there may or may not be a number of spaces
 skipspaces();
 
 for (i=0; i<5; i++)
  par[i]=(uint)getnum();
  
 // column: Character position (For 320 wide screens, 0-39; for 640 wide screens, 0-79)
 CharX=par[0]*(xzoom<<3);
 
 // row: Pixel line (For 200 line screens, 0-199; for 400 line screens, 0-399)
 CharY=par[1]*yzoom;
 
 // height: Multiple of 8-bit character height (1=8 pixels high, 2=16 pixels, etc.)
 // width: Multiple of 8-bit character width (1=8 pixels high, 2=16 pixels, etc.)
 yzoombak=yzoom; xzoombak=xzoom;
 yzoom=yzoom*par[2]; xzoom=xzoom*par[3];
 
 shiftedbak=shifted; shifted=1;
  
 // Now the program counter might be on the character " (quotes), or on
 // the first letter of a variable. We consider the two cases.
 if (program[pc]==34) // "
 {
  // We advance 1 position to place it at the beginning of the string
  pc++;
 
  // acquire_screen();
  while (program[pc]!=34)
  {
   if (program[pc]>='A' && program[pc]<='Z') // actually lowercase letters
    Cout(program[pc]+32);
   else if (program[pc]>=193 && program[pc]<=218) // actually uppercase letters
    Cout(program[pc]-128);
   else
    Cout(program[pc]); 
  
   CharX-=(xzoom<<3); // the cursor must NOT advance to the right
    
   /* direction: Bit mask 0000xxxx
                              ||||
                              |||- up
                              ||-- right
                              |--- down
                              ---- left */
   if (par[4]&1) CharY-=(yzoom<<3);
   if (par[4]&2) CharX+=(xzoom<<3);
   if (par[4]&4) CharY+=(yzoom<<3);
   if (par[4]&8) CharX-=(xzoom<<3);
  
   pc++;
  }
 }
 else if (program[pc+1]=='$' || program[pc+2]=='$')
 {
  stringvarpos=locatevar();
  array_index=getarrayindex();
  for (i=0; stringvars[i][stringvarpos][array_index] && i<256; i++)
  {
   if (stringvars[i][stringvarpos][array_index]>='A' && stringvars[i][stringvarpos][array_index]<='Z') // actually lowercase letters
    Cout(stringvars[i][stringvarpos][array_index]+32);
   // else if (stringvars[i][stringvarpos][array_index]>=193 && stringvars[i][stringvarpos][array_index]<=218) // actually uppercase letters
   else if (stringvars[i][stringvarpos][array_index]>=-63 && stringvars[i][stringvarpos][array_index]<=-38) // actually uppercase letters, fixed for signed chars
    Cout(stringvars[i][stringvarpos][array_index]-128);
   else
    Cout(stringvars[i][stringvarpos][array_index]); 
  
   CharX-=(xzoom<<3); // the cursor must NOT advance to the right
    
   /* direction: Bit mask 0000xxxx
                              ||||
                              |||- up
                              ||-- right
                              |--- down
                              ---- left */
   if (par[4]&1) CharY-=(yzoom<<3);
   if (par[4]&2) CharX+=(xzoom<<3);
   if (par[4]&4) CharY+=(yzoom<<3);
   if (par[4]&8) CharX-=(xzoom<<3);
  }
 }
 shifted=shiftedbak;
 yzoom=yzoombak; xzoom=xzoombak; 
 
 // release_screen();
}

void Circle(void) // CIRCLE x_center, y_center, radius [,solid]
{
 uint par[4];
 uchar i,j,k;
 
 // these arrays contain the native (Allegro screen) coordinates for
 // the center and the native (Allegro screen) lengths of the
 // horizontal and vertical radius
 uint center[2],radius[2];

 pc++; // We move past the CIRCLE token
 
 // After the CIRCLE token, there may or may not be a number of spaces
 skipspaces();
 
 // The first three parameters are compulsory. They are the XY
 // coordinates of the center and the radius
 for (i=0; i<3; i++)
  par[i]=(uint)getnum();
  
 par[3]=0; // The default case for the fourth parameter is 0 (outline only)
 
 // The fourth parameter is optional. To tell whether it's there or not,
 // we read the current program byte. If it's 0 or ':', we are at the
 // end of the instruction. Otherwise, the parameter is there and can
 // be read
 if (program[pc] && program[pc]!=':')
  par[3]=(uint)getnum();
 
 center[0]=par[0]*xzoom; center[1]=par[1]*xzoom;
 radius[0]=par[2]*xzoom; radius[1]=par[2]*yzoom;
 
 // acquire_screen();
 if (par[3]) // If the fourth parameter is !=0, a filled "circle" must be drawn
  ellipsefill(screen,center[0]+screen_border_x,center[1]+screen_border_y,radius[0],radius[1],forecolor);
 else // If not, only the outline must be drawn
  ellipse(screen,center[0]+screen_border_x,center[1]+screen_border_y,radius[0],radius[1],forecolor);
  
 // Additional outlines must be traced to simulate different resolutions
 for (j=2; j<7; j++)
 {
  k=j-1;
  if (xzoom>=j)
  {
   for (i=0; i<j; i++)
    ellipse(screen,center[0]+screen_border_x+k,center[1]+screen_border_y+i,radius[0],radius[1],forecolor);
  }
  if (yzoom>=j)
  {
   for (i=0; i<j; i++)
    ellipse(screen,center[0]+screen_border_x+i,center[1]+screen_border_y+k,radius[0],radius[1],forecolor);
  }
  if (xzoom>=j && yzoom>=j)
    ellipse(screen,center[0]+screen_border_x+k,center[1]+screen_border_y+k,radius[0],radius[1],forecolor);
 }
 // release_screen();
}

void Clr(void)
{
 uint i; 
 
 resetc65vars();
 for (i=0; i<MAXLOOPS; i++)
 {
  forvarpos[i]=loopstack[i]=forarrayindex[i]=gosubstack[i]=0;
  forto[i]=forstep[i]=0;
 }
 dc=0;
 
 // In the physical C65, all open files are "forgotten", but a Close operation
 // is not peformed. It is more efficient to actually peform it.
 for (i=0; i<256; i++)
 {
  if (filestatus[i])
  {
   fclose(cFile[i]);
   filestatus[i]=CLOSED;
  }
 }
}

void Dclose(void)
{
 uchar lfn; // logical file number
 uint i;
 
 // We have already moved past the DCLOSE token, so we don't need to
 // increase pc again
 
 // After the DCLOSE token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we might be over a # character or not. If we are not, we close
 // all open files. If we are, we check for eventual errors and we
 // close the specified file if there are none.
 if (program[pc]!='#')
 {
  for (i=0; i<256; i++)
  {
   if (filestatus[i])
   {
    fclose(cFile[i]);
    filestatus[i]=CLOSED;
   }
  }
 }
 else
 {
  pc++;
  lfn=(uchar)getnum();
  if (filestatus[lfn])
  {
   fclose(cFile[lfn]);
   filestatus[lfn]=CLOSED;
  }
  else
   printerror(FILENOTOPEN);
 }
}

void DeleteEraseScratch(uint token) // DELETE [startline] [-[endline]]
                                    // DELETE "myfile"
                                    // ERASE "myfile"
                                    // SCRATCH "filespec"
{
 // BASIC 10 has three commands that do the same thing: erase a file.
 // A uselessly redundant implementation that will be nevertheless
 // reproduced here.
 // The DELETE command (but not its ERASE and SCRATCH clones) can also
 // be used to delete one or more code lines, if followed by a line
 // number. That is somewhat redundant too, because typing a line
 // number followed by <RETURN> accomplishes the same thing (except for
 // the "multiple lines" part).
 // 
 // NOTE: the function is commented for the time being, to prevent antivirus
 // programs to identify Hi65 as a malicious program, because of the presence
 // of the "remove" function
 
 uint i,array_index;
 double startline,endline=-1;
 uchar *aux;
 uchar forbidden=0;
 
 // ERASE is a 2-bit token, the other commands are not. This means that
 // if this function has been called by an ERASE command, the program
 // counter will already be on a quote character (or on an open
 // parenthesis). In all other cases, it will be on the command token. We must
 // know where it is, to reach the first character of the first parameter.
 if (!(program[pc]==0x22 || program[pc]==0x28))
 {
  pc++;
  skipspaces();
 }
 
 // The first character of the first parameter may be a digit (so the
 // command is meant to delete program lines), a quote character or an
 // open parenthesis character (so the command is meant to delete a
 // file). We distinguish the cases.
 if (program[pc]>='0' && program[pc]<='9') // deletion of lines
 {
  startline=getnum();
  if (pc<instruction_end_address && program[pc]!=':')
   endline=getnum();
   
  if (endline==-1) // we must delete a single program line
  {
   // implement later
  }
  else // we must delete multiple lines
  {
   // implement later
  }
  return;
 }
 else if (program[pc]==0x22) // if we are on a quote character 
 {
  stringvarpos=140; // DS$
  array_index=0;
  pc++; // we go past it
  getstring(0);
 }
 else if (program[pc]==0x28) // if we are on an open parenthesis
 {
  pc++;
  skipspaces();
  stringvarpos=locatevar();
  array_index=getarrayindex();
 }
 else // if we are not on either, the syntax is wrong
  printerror(SYNTAXERROR);
  
 // If we have not encountered a syntax error, we delete the file
 // (this part will never be reached if the function was called to
 // delete program lines, because in that case we returned earlier)
 if (numvars[ER][0]==-1)
 {
  for (i=0; i<255; i++)
   aux[i]=stringvars[i][stringvarpos][array_index];
   
  // check to prevent n00bs from deleting files in other directories,
  // executables, libraries or multiple files
  // (move the check to a dedicated function)
  for (i=0; aux[i]; i++)
  {
   if (aux[i]=='\\' ||
       aux[i]=='/' ||
       (aux[i]=='d' && aux[i+1]=='l' && aux[i+2]=='l') ||
       (aux[i]=='e' && aux[i+1]=='x' && aux[i+2]=='e') ||
       (aux[i]=='.' && aux[i+1]=='.') ||
       aux[i]=='*' || 
       aux[i]=='?')
   {
    forbidden=1;
    break;
   }
  }
  if (forbidden)
   printerror(INVALIDFILENAME);
  else
  {
   if (remove((const char *)aux)) // if there has been an error in deleting the file
    printerror(FILENOTFOUND);
  }
 }
}

void Dim(void)
{
 // 1-dimensional arrays are already defined at the beginning of the
 // program. Each of them contains 256 elements, of which, the element
 // 0 is used as the variable of the same name. This function just
 // checks whether the number of elements in an array is allowed.
 // It is possible to define multiple arrays with a single DIM instructions
 // (the names are separated by commas) so this function must work as a loop.
 uint par;

 while (program[pc]!=':' && pc!=instruction_end_address)
 {
  // we reach the number of elements
  while (program[pc]!='(')
   pc++;

  // We move past the open parenthesis and the eventual spaces
  pc++;
  skipspaces();
 
  // Now we can check the correctness of the parameter
  par=(uint)getnum();
 
  if (par>256)
   printerror(ILLEGALQUANTITY);
   
  // We move to the next parameter (or to the end of the instruction)
  while (program[pc]!=',' && program[pc]!=':' && pc!=instruction_end_address)
   pc++;
   
  // if we found a comma, we move past it, skip the eventual spaces and reach
  // the next parameter
  if (program[pc]==',')
  {
   pc++;
   skipspaces();
  }
  // Now we are on the next parameter and the loop can repeat
 }
}

void Dload(uchar from_line)
{
 uchar aux[255];
 uint i;
 loaderror=0;
 
 for (i=0; i<256; i++)
  aux[i]=0;

 if (from_line)
 {
  // If we have reached this function from a line command, the line
  // buffer contains the name of the file we want to load, so we
  // must increase the line counter until we find it
  // BUT: if the syntax is incorrect (so there is no " character to be
  // found) the search must be aborted
  while(linebuffer[lc]!='"' && lc<79) // the search is aborted if no character in the line is a "
   lc++;
  if (linebuffer[lc]!='"')
  {
   Enter();
   forecolor=makecol(240,0,0);
   acquire_screen();
   prints((uchar *)"?syntax error");
   forecolor=makecol(240,240,240);
   Ready();
   release_screen();
   return;
  }
  else
   lc++;
  
  // Then we copy the name to the auxiliary array
  for (i=0; linebuffer[lc]!='"'; i++)
   aux[i]=linebuffer[lc++];
 }
 else
 {
  // Yes, it makes sense to load a program from another program.
  // For example, for a compilation where an index program asks
  // you which program to load.
  // So now we must search within the current program for the
  // name of the program we want to load
  while(linebuffer[pc]!='"')
  {
   pc++;
   if (linebuffer[pc]!=' ' && linebuffer[pc]!='"')
   {
    acquire_screen();
    printerror(SYNTAXERROR);
    release_screen();
   }
   break;
  }
  pc++;
  
  // Then we copy the name to the auxiliary array
  for (i=0; linebuffer[pc]!='"'; i++)
   aux[i]=linebuffer[pc++];
 }
 // And now we call the LOAD function with the file name we found
 loaderror=0;
 loaderror=load((uchar *)aux);
 resetc65vars(); // It doesn't make sense to retain the values of the C65 variables when we load another program
 acquire_screen();
 simulateloadinstructions((uchar *)aux,0);
 if (loaderror)
  Ready();
 release_screen();
}

void Do(void) // DO/LOOP/WHILE/UNTIL/EXIT -- Program loop definition and control
              // 
              // DO   [UNTIL boolean_expression | WHILE boolean_expression]
              //            .
              //            .         statements [EXIT]
              //            .
              // LOOP [UNTIL boolean_expression | WHILE boolean_expression]
{
 uint cycletype;
 // We entered a new cycle. Thus, we are now one nesting level deeper
 // than before. However, if we are returning to the DO token after an
 // iteration, this must be disregarded.
 if (old_instruction_end_address<instruction_end_address)
  loopnestinglevel++;
 
 pc++; // we move past the DO token
 
 // After the DO token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we might be on an instruction end address, a WHILE token, an
 // UNTIL token or a colon. In all cases, this is the address we must
 // return to for the next iterations, so we copy the value in the
 // appropriate variable
 loopstack[loopnestinglevel]=pc;
 loop_instruction_end_address=instruction_end_address;
 
 // If we are on an instruction end address, we do everything as usual
 // and no special check is necessary. If we are on a WHILE or UNTIL
 // token, we must decide whether to exit the loop
 switch(program[pc])
 {
  case WHILE:
  case UNTIL:
       cycletype=program[pc];
       pc++; // we move past the token
       skipspaces(); // we skip the eventual spaces
       
       // We check whether the comparison is between two strings or
       // two numbers, and act accordingly
       if (program[pc]!=0x22 && program[pc+1]!='$' && program[pc+2]!='$') // "number" cases
        processnumbercondition(); // We process the number condition, to know whether it's true or false
       else // string cases
        processstringcondition(); // We process the string condition, to know whether it's true or false
       
       // The WHILE cycle loops if the condition is true. The UNTIL
       // cycle loops if the condition is false. We check the type and
       // act accordingly
       // In order to recycle code, we use a single check, but we
       // invert the value of "condition" if the cycle type is WHILE
       if (cycletype==WHILE)
        condition ? condition=0 : condition=1;
       // Now we can always treat the cycle like an UNTIL cycle
       if (condition)
       {
        must_exit=1;
        oldloopnestinglevel=loopnestinglevel;
       }
       break;
 }
}

void Dopen(void) // DOPEN#1,"readfile"
                 // DOPEN#1,"writefile",W
                 // File commands are DOPEN, DCLOSE and APPEND
{
 uint i,array_index;
 uchar lfn; // logical file number
 uchar *aux;
 uchar open_to_write=0; // the default option is to open a file to read

 for (i=0; i<256; i++)
  aux[i]=0;
 
 // We have already moved past the DOPEN token, so we don't need to
 // increase pc again
 
 // After the DOPEN token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we must be on a # character. If we are not, the program is
 // stopped with a SYNTAX ERROR
 if (program[pc]!='#')
  printerror(SYNTAXERROR);
 else
 {
  // We move past the # character and get the logical file number we
  // will use
  pc++;
  lfn=(uchar)getnum();
  
  // We are now past the comma, so we skip more eventual spaces
  skipspaces();
  
  // Now we may be on a "quotes" character (so the file name is an
  // immediate string) or on an open parenthesis (so the file name is
  // contained in a string variable).
  if (program[pc]==0x22) // if we are on a quote character
  {
   stringvarpos=140; // DS$
   array_index=0;
   pc++; // we go past it
   getstring(0);
  }
  else if (program[pc]==0x28) // if we are on an open parenthesis
  {
   pc++;
   skipspaces();
   stringvarpos=locatevar();
   array_index=getarrayindex();   
  }
  else // if we are not on either, the syntax is wrong
   printerror(SYNTAXERROR);
   
  // We copy the string into the aux array, but only if we haven't
  // encountered any error
  if (numvars[ER][0]==-1)
  {
   for (i=0; i<255; i++)
    aux[i]=stringvars[i][stringvarpos][array_index];
   
   // We check whether we must open an existing file (to read it) or
   // create it (to write it). In the first case, the instruction is
   // over; in the latter, there is another comma and one more parameter
   // (the immediate letter W)
   pc++;
   while (program[pc]!=':' && program[pc]!=',' && pc<instruction_end_address)
    pc++;
   
   // If we found a comma and then the letter W, the file must be created
   if (program[pc]==',')
   {
    pc++;
    skipspaces();
    if (program[pc]=='W')
     open_to_write=1;
    else
     printerror(SYNTAXERROR);
   }
  
   // Now we can open the file, but only if it isn't already open
   if (!filestatus[lfn])
   {
    if (!open_to_write) // read, file must exist (if it doesn't: FILE NOT FOUND)
    {
     cFile[lfn]=fopen ((const char *) aux,"r");
   
     // Without doing this, checking the value of cFile[lfn] directly
     // will cause the program to crash. It's okay to do so anyway,
     // because the condition doesn't care for the exact value of the
     // pointer, just whether or not it's zero. And when something goes
     // wrong, it's always zero.
     i=(uint)cFile[lfn];
   
     if (!i)
      printerror(FILENOTFOUND);
     else
      filestatus[lfn]=OPEN_TO_READ;
    }
    else // write, file must not exist (if it does: FILE EXISTS)
    {
     // The C function "fopen" empties the file if it exists, but in
     // the C65, the existence of the file must trigger an error. For
     // this, we first attempt to read it. If we succeed, it means it
     // exists, so we trigger a C65 error. If we don't (so fopen
     // returns a null pointer) we proceed to create the file.
    
     cFile[lfn]=fopen ((const char *) aux,"r");
   
     // Without doing this, checking the value of cFile[lfn] directly
     // will cause the program to crash. It's okay to do so anyway,
     // because the condition doesn't care for the exact value of the
     // pointer, just whether or not it's zero. And when something goes
     // wrong, it's always zero.
     i=(uint)cFile[lfn];
   
     if (i)
     {
      fclose(cFile[lfn]);
      printerror(FILEEXISTS);
     }
     else
     {
      cFile[lfn]=fopen ((const char *) aux,"w");
      filestatus[lfn]=OPEN_TO_WRITE;
     }
    }
   }
   else
    printerror(FILEOPEN);
  }
 }
}

void Draw(void) // DRAW x,y
{
 // UNIMPLEMENTED IN THE REAL C65
 // This implementation follows the syntax that is inferred from many
 // BASIC 10 programs on the Internet, where it is implied to accept
 // two parameters (X and Y) specifying the coordinates of a pixel on
 // the screen, which will be drawn with the current PEN color. It is
 // basically equivalent to a "putpixel".
 
 uint par[2];
 uchar i;
 
 // This "token" is formed by four characters ('D','R','A','W'). We
 // move past them and skip the eventual spaces.
 pc+=4;
 skipspaces();
 
 //  We get the two parameters
 par[0]=(uint)getnum();
 par[1]=(uint)getnum();
 par[0]*=xzoom;
 par[1]*=yzoom;
 
 // We draw the pixel
 c65draw(par[0],par[1]);
}

void Ellipse(void) // ELLIPSE x_center, y_center, x_radius, y_radius [,solid]
{
 uint par[5];
 uchar i,j,k;
 
 // these arrays contain the native (Allegro screen) coordinates for
 // the center and the native (Allegro screen) lengths of the
 // horizontal and vertical radius
 uint center[2],radius[2];

 // We have already moved past the ELLIPSE token, so we don't need to
 // increase pc again
 
 // After the ELLIPSE token, there may or may not be a number of spaces
 skipspaces();
 
 // The first four parameters are compulsory. They are the XY coordinates
 // of the center and the radius
 for (i=0; i<4; i++)
  par[i]=(uint)getnum();
  
 par[4]=0; // The default case for the fifth parameter is 0 (outline only)
 
 // The fifth parameter is optional. To tell whether it's there or not,
 // we read the current program byte. If it's 0 or ':', we are at the
 // end of the instruction. Otherwise, the parameter is there and can
 // be read
 if (program[pc] && program[pc]!=':')
  par[4]=(uint)getnum();
 
 center[0]=par[0]*xzoom; center[1]=par[1]*xzoom;
 radius[0]=par[2]*xzoom; radius[1]=par[3]*yzoom;
 
 // acquire_screen();
 if (par[4]) // If the fifth parameter is !=0, a filled ellipse must be drawn
  ellipsefill(screen,center[0]+screen_border_x,center[1]+screen_border_y,radius[0],radius[1],forecolor);
 else // If not, only the outline must be drawn
  ellipse(screen,center[0]+screen_border_x,center[1]+screen_border_y,radius[0],radius[1],forecolor);
  
 // Additional outlines must be traced to simulate different resolutions
 for (j=2; j<7; j++)
 {
  k=j-1;
  if (xzoom>=j)
  {
   for (i=0; i<j; i++)
    ellipse(screen,center[0]+screen_border_x+k,center[1]+screen_border_y+i,radius[0],radius[1],forecolor);
  }
  if (yzoom>=j)
  {
   for (i=0; i<j; i++)
    ellipse(screen,center[0]+screen_border_x+i,center[1]+screen_border_y+k,radius[0],radius[1],forecolor);
  }
  if (xzoom>=j && yzoom>=j)
    ellipse(screen,center[0]+screen_border_x+k,center[1]+screen_border_y+k,radius[0],radius[1],forecolor);
 }
 
 /* if (xzoom>=2)
  ellipse(screen,center[0]+screen_border_x+1,center[1]+screen_border_y,radius[0],radius[1],forecolor);
 if (yzoom>=2)
  ellipse(screen,center[0]+screen_border_x,center[1]+screen_border_y+1,radius[0],radius[1],forecolor);
 if (xzoom>=2 && yzoom>=2)
  ellipse(screen,center[0]+screen_border_x+1,center[1]+screen_border_y+1,radius[0],radius[1],forecolor); */
 // release_screen();
}

void Else(void)
{
 // Single-line "if" instruction:
 // if the condition is true, we must skip to the end of the instruction
 // if it's false, we do nothing (the other instructions are executed automatically)
 if (condition)
  pc=instruction_end_address;
}

void End(void) // END
{
 // We advance to the end of the program
 while (!(program[pc]==0 && program[pc+1]==0 && program[pc+2]==0))
  pc++;
 found_end=1;
}

void Exit(void)
{
 // This instruction is used to immediately exit a loop, disregarding
 // the condition expressed in the WHILE or UNTIL statement
 must_exit=1;
}

void For (void) // FOR index = start TO end [STEP increment]
                //  |
                // NEXT index [,index]
{
 uint aux;
 
 // We entered a new cycle. Thus, we are now one nesting level deeper
 // than before.
 loopnestinglevel++;
 
 pc++; // we move past the FOR token
 
 // After the FOR token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we are on the name of the variable used by this FOR loop. We
 // locate the position of that variable.
 numvarpos=forvarpos[loopnestinglevel]=locatevar();
 forarrayindex[loopnestinglevel]=getarrayindex();
 
 // We skip to the C65EQU token, skip the following spaces and
 // evaluate the following numeric expression, inserting the result
 // into the located variable.
 skipspacesandevaluateexpression(forarrayindex[loopnestinglevel]);

 // Now we have reached the TO token, so we move one step forward, skip
 // the eventual spaces and evaluate the next expression.
 pc++;
 skipspaces();
 forto[loopnestinglevel]=evaluateexpression();
 
 // At this point, there might or might not be a STEP token. If there
 // is, we use the value of its parameter to determine how much the
 // variable must be incremented in every cycle. If we are not, the
 // variable will be incremented of 1 unit for each cycle.
 if (program[pc]==STEP)
 {
  // We move past the STEP token and skip the eventual spaces
  pc++;
  skipspaces();
  forstep[loopnestinglevel]=evaluateexpression();
 }
 else
  forstep[loopnestinglevel]=1;
  
 // Now we are either on an instruction end address or on a colon. In
 // both cases, this is the address we must return to for the next
 // iterations, so we copy the value in the appropriate variable
 loopstack[loopnestinglevel]=pc;
}

void Get(void) // GET variable_list
{
 // NOTE: this command is documented on the C65 manual as accepting a
 // "variable list". However, only the case of a single variable is
 // useable. Checks for multiple variables, for example like this:
 // 
 // 10 get a$, b$ : rem this does not work
 // 20 if a$<>"a" and b$<>"b" goto 10 else goto 30
 // 30 print "end of program"
 //
 // always fail, as either only the first variable is checked, or the
 // result of the check is considered true even when it's false.
 // For this reason, this implementation of the GET function accepts
 // one parameter only.
 
 uint k,array_index;
 
 
 pc++; // we move past the GET token
 
 // After the GET token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we are on the name of the variable, so we locate its position
 stringvarpos=locatevar();
 array_index=getarrayindex();
  
 // we read a single key read and we store it into the appropriate
 // variable
 stringvars[0][stringvarpos][array_index]=keyboardinput(0);
 
 // We advance past the $ character
 while (program[pc]!='$')
  pc++;
 pc++;
 
 // We skip the eventual spaces
 skipspaces();
}

void Go(uchar from_line) // GO TO line_number | GO PET | GO 16 | GO 64 | GO 128 | GO 65
{
 uchar par;
 
 if (from_line)
 {
  lc+=2; // we move past the characters forming the word GO
  
  // After them, there may or may not be a number of spaces
  while (linebuffer[lc]==' ')
   lc++;
  
  // Now we may be on the immediate numbers 20, 64 or 128 (so we change
  // the main screen to a simulacrum of the main screens of the VIC20,
  // C64 or C128 respectively) or on something else (so we display a
  // syntax error)
  if (linebuffer[lc]=='p' &&
      linebuffer[lc+1]=='e' &&
      linebuffer[lc+2]=='t')
  {
   screentype=0;
   alternatescreen();
  }
  else if (linebuffer[lc]=='1' &&
      linebuffer[lc+1]=='6')
  {
   screentype=16;
   alternatescreen();
  }
  else if (linebuffer[lc]=='2' &&
      linebuffer[lc+1]=='0')
  {
   screentype=20;
   alternatescreen();
  }
  else if (linebuffer[lc]=='6' &&
      linebuffer[lc+1]=='4')
  {
   screentype=64;
   alternatescreen();
  }
  else if (linebuffer[lc]=='1' &&
           linebuffer[lc+1]=='2' &&
           linebuffer[lc+2]=='8')
  {
   screentype=128;
   alternatescreen();
  }
  else if (linebuffer[lc]=='6' &&
      linebuffer[lc+1]=='5')
  {
   screentype=65;
   chperline=80;
   shifted=0;
   startscreen(1); // the parameter used to be 0, but it hanged the emulator
   Ready();
  }
  else
  {
   Enter();
   forecolor=makecol(240,0,0);
   // acquire_screen();
   prints((uchar *)"?syntax error");
   forecolor=makecol(240,240,240);
   Ready();
   // release_screen();
  }
 }
 else
 {
  pc++; // we move past the GO token
  
  // After the GO token, there may or may not be a number of spaces
  skipspaces();
 
  // Now we may either be on a TO token (so we must act as we would if
  // we were on a GOTO token) or on the immediate number 64 (so we must
  // inform the user that the C64 mode is not available)
  if (program[pc]==TO)
   Goto();
  else
  {
   if (program[pc]>'0' && program[pc]<='9')
    screentype=(uchar)getnum();
   else if (program[pc]=='p' &&
            program[pc+1]=='e' &&
            program[pc+2]=='t')
    screentype=0;
   switch(screentype)
   {
    case 0:
    case 16:
    case 20:
    case 64:
    case 128:
         alternatescreen();
         break;
    case 65:
         shifted=0;
         startscreen(1); // the parameter used to be 0, but it hanged the emulator
         Ready();
         break;
    default: printerror(SYNTAXERROR); break;
   }
  }
 }
}

void Gosub(void) // GOSUB line_number
{
 // This must be done INSTEAD of the program counter increments that
 // are executed at the end of an instruction
 uint par, scanned_line_number, aux, scanned_instruction_end;
 
 // found_goto=1;
 // We entered a GOSUB instruction. Thus, we are now one GOSUB nesting level 
 // deeper than before.
 gosubnestinglevel++;
 
 found_goto=1;
  
 pc++; // we move past the GOSUB token
  
 // After the GOSUB token, there may or may not be a number of spaces
 skipspaces();
 
 // We read the line number we must skip to
 par=(uint)getnum();
 // NOTE: this also "gets" variables, allowing for expressions like
 // P=100: GOSUB P
 // This is not allowed in a physical C65, but we're not adding checks to
 // prevent that, because it does not impair compatibility with existing
 // BASIC 10 programs.
 
 // After that, there may or may not be more spaces
 skipspaces();
 
 // Now we might be on an instruction end address or a colon. In all cases,
 // this is the address we must return to to continue the program, so we copy
 // the value in the appropriate variable
 gosubstack[gosubnestinglevel]=pc;
 gosub_instruction_end_address=instruction_end_address;
 
 // If we must go to a line number greater than the current one, we
 // just move forward. If we must go to a line number less than or 
 // equal to the current one, we position the program counter to the
 // beginning of the first instruction and move forward.
 if (par<=line_number)
  pc=0;

 do
 {
  // We skip to the end of the instruction
  // If we are at the beginning of the program, we consider the second
  // char (where pc==1) as the end of a hypothetical "instruction #0"
  // otherwise we advance to the actual end of the instruction
  if (!pc)
   pc++;
  else
  {
   while (program[pc])
    pc++;
  }
  scanned_instruction_end=pc;
  // prints((uchar *)"instruction end at "); printn(scanned_instruction_end); prints((uchar *)"; ");
     
  // We move 3 bytes forward, to "land" on the first byte of the line number
  pc+=3;
   
  // We read the line number (tokenized LSB-first = little endian)
  scanned_line_number=program[pc];
  pc++;
  aux=program[pc];
  aux<<=8;
  scanned_line_number=aux+scanned_line_number;
   
  // The MSB of the line number may be 0 (which would be interpreted
  // as an instruction end) so we move 1 step forward
  pc++;
 }
 while (scanned_line_number<par);
 
 // Now we found the correct line. We must return to the beginning of
 // the instruction
 pc=scanned_instruction_end+1;
}

void Goto(void) // GOTO  line_number
{
 // This must be done INSTEAD of the program counter increments that
 // are executed at the end of an instruction
 uint par, scanned_line_number, aux, scanned_instruction_end;
 
 found_goto=1;
  
 pc++; // we move past the GOTO token
  
 // After the GOTO token, there may or may not be a number of spaces
 skipspaces();
 
 // We read the line number we must skip to
 par=(uint)getnum();
 // NOTE: this also "gets" variables, allowing for expressions like
 // P=100: GOTO P
 // This is not allowed in a physical C65, but we're not adding checks to
 // prevent that, because it does not impair compatibility with existing
 // BASIC 10 programs.
 
 // If we must go to a line number greater than the current one, we
 // just move forward. If we must go to a line number less than or 
 // equal to the current one, we position the program counter to the
 // beginning of the first instruction and move forward.
 if (par<=line_number)
  pc=0;
 
 do
 {
  // We skip to the end of the instruction
  // If we are at the beginning of the program, we consider the second
  // char (where pc==1) as the end of a hypothetical "instruction #0"
  // otherwise we advance to the actual end of the instruction
  if (!pc)
   pc++;
  else
  {
   while (program[pc])
    pc++;
  }
  scanned_instruction_end=pc;
  // prints((uchar *)"instruction end at "); printn(scanned_instruction_end); prints((uchar *)"; ");
    
  // We move 3 bytes forward, to "land" on the first byte of the line number
  pc+=3;
  
  // We read the line number (tokenized LSB-first = little endian)
  scanned_line_number=program[pc];
  pc++;
  aux=program[pc];
  aux<<=8;
  scanned_line_number=aux+scanned_line_number;
  
  // The MSB of the line number may be 0 (which would be interpreted
  // as an instruction end) so we move 1 step forward
  pc++;
 }
 while (scanned_line_number<par);

 // Now we found the correct line. We must return to the beginning of
 // the instruction
 pc=scanned_instruction_end+1;
}

void If(void) // Single line:
              // IF expression <THEN then_clause> [:ELSE else_clause]
              //
              // Multiline:
              // IF expression THEN BEGIN
              // <commands>
              // [<commands>]
              // BEND : ELSE BEGIN
              // <commands>
              // [<commands>]
              // BEND
{
 // The single-line IF statement has the following format:
 // 20 IF <condition> THEN <command> [: <command> : <command>] : ELSE <command> : [<command> : <command>]
 // with every command separated by a colon. Only one command after
 // THEN is compulsory, everything else is optional.
 
 // A program like the following:
 // 20 IF <condition> THEN <command>
 // 30 ELSE <command>
 // 40 <more commands>
 // would result in the ELSE instruction being ignored completely, even
 // when <condition> is false
 
 // This is what we must do:
 // - we evaluate the condition
 // - if the condition is TRUE, we do nothing (the next loops will
 //   interpret THEN as a colon and execute what follows, then a
 //   check skips the eventual instructions after ELSE)
 // - if the condition is FALSE, we move the program counter forward
 //   until we meet an ELSE (it will be interpreted as a colon and the
 //   following instructions will be executed) or the end of the
 //   instruction.
 
 // The structure of a multiline IF statement is as follows:
 // 30 if <condition> then begin
 // 40 <command>
 // [50 <command>]
 // 60 bend : else begin
 // 70 <command>
 // [80 <command>]
 // 90 bend
 // 100 <more commands>
 
 // The format "bend : else begin" is compulsory. A section as follows:
 // 60 bend
 // 70 else begin
 // 80 <command>
 // 90 <command>
 // 100 bend
 // would never execute anything past the first BEND instruction.
 
 // Multiline IF statements can be nested: a multiline IF statement can
 // be placed into the THEN or ELSE part of another multiline IF
 // statement
 
 uint aux,val,i;
 
 // Before doing anything condition-related, we must check whether the user
 // is trying to initialize a variable called IF (which could exist in the
 // variable space, but the C65 disallows). If she is (LOL I used SHE as a
 // generic pronoun so I'm woke) the program must return a syntax error.
 // We actually check if the user has typed an = sign or a $ sign after the
 // letters IF.
 aux=pc;
 pc++;
 skipspaces();
 if (program[pc]==C65EQU || program[pc]=='$')
  // the user is trying to initialize the IF variable so he's a dumbass
  // (LOL I'm not woke anymore)
  printerror (SYNTAXERROR);
 else
 {
  pc=aux;
  inside_if_clause++;
  // First, we check whether the IF statement is single-line or multiline
  do
  {
   val=program[pc++];
   val=val<<8;
   val+=program[pc];
  }
  while (val!=BEGIN && pc<instruction_end_address);
  pc=aux;
 
  pc++; // we move past the IF token
  
  // After the IF token, there may or may not be a number of spaces
  skipspaces();
 
  // Now we may be on the " that marks the beginning of a string, on an
  // immediate number, on the first character of a numeric variable or
  // on the first character of a string variable. We must discriminate
  // the "number" cases from the "string" cases.
  // Note that the type check is done on the first term only . We
  // assume that the second term is the same type as the first or the
  // comparison would be impossible.
  if (program[pc]!=0x22 && program[pc+1]!='$' && program[pc+2]!='$') // "number" cases
  {
   if (program[pc]==DO
    || program[pc]==FN
    || program[pc]==GO
    || program[pc]==IF
    || program[pc]==ON
    || program[pc]==OR
    || program[pc]==TO)
    printerror(SYNTAXERROR);
   else
    processnumbercondition(); // We process the number condition, to know whether it's true or false
  }
  else // string cases
   processstringcondition(); // We process the string condition, to know whether it's true or false
 
  if (val!=BEGIN) // If we don't find a BEGIN token, the IF statement is single-line
   singlelinediscrimination(); // We decide whether to execute the THEN or the ELSE instructions
  else // multiline 
   multilinediscrimination(); // We decide whether to execute the THEN or the ELSE branch
 }
}

void Input(void)
{
 uint aux;
 uchar separator=1; // "1" is an invalid value
 uchar how_many_vars=1; // 1 is the minimum amount of variables
 uchar how_many_inputs=0; // at the beginning, no variable received a value
 uchar how_many_decimals=0; // at the beginning, an eventual non-integer value does not have any decimals yet
 double decimal_part=0;
 uchar i;
 uchar currentkey=0;
 uint array_index;
 uchar number_is_negative=0;
 char numberstring[255];
 
 pc++; // We move past the INPUT token
 
 // After the INPUT token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we may be on the " that marks the beginning of a string, or on
 // a variable name. If we are on a ", we display the string, otherwise
 // we display a ? and a space
 if (program[pc]==0x22)
 {
  pc++;
  // acquire_screen();
  while (program[pc]!=0x22)
  {
   if (program[pc]>='A' && program[pc]<='Z')
    Cout (program[pc++]+32);
   else
    Cout (program[pc++]);
  }
  // release_screen();
  pc++; // We move past the second "
  skipspaces();
  
  // If there is a ; after the string, we display an additional ? and space
  if (program[pc]==';')
   prints((uchar *)"? ");
   
  pc++; // We move past the ; or the ,
  skipspaces();
 }
 else
  prints((uchar *)"? ");
  
 // Now we are on the first char of the first variable name. First, we
 // count how many variables the instruction expects to receive, from
 // the current position to the end of the instruction, which can be
 // marked by a 0x00 byte or by a ':' character.
 aux=pc;
 while (program[aux] && program[aux]!=':')
 {
  if (program[aux]==',')
   how_many_vars++;
  aux++;
 }

 // Now we are ready to receive the values for each of the variables
 while (how_many_inputs<how_many_vars)
 {
  // we decide whether we are on a string or a numeric variable and
  // act accordingly
  // UNFINISHED: it does not accept multiple inputs correctly
  
  if (program[pc+1]=='$' || program[pc+2]=='$') // string
  {
   i=0;
   stringvarpos=locatevar();
   array_index=getarrayindex();
   
   while (currentkey!=',' && currentkey!=ENTER)
   {
    currentkey=keyboardinput(1);
    if (currentkey!=BACKSPACE && currentkey!=ENTER && currentkey)
     stringvars[i++][stringvarpos][array_index]=currentkey;
    else if (currentkey==BACKSPACE && i)
     i--;
    // clear_keybuf();
   }
   // The string has been entered. Now, the rest of the array (255
   // chars in total) must be set to 0.
   for (; i<255; i++)
    stringvars[i][stringvarpos][array_index]=0;
    
   how_many_inputs++;
  }
  else // numeric variable
  {
   if (program[pc]==DO
    || program[pc]==FN
    || program[pc]==GO
    || program[pc]==IF
    || program[pc]==ON
    || program[pc]==OR
    || program[pc]==TO)
    printerror(SYNTAXERROR);
   else
   {
    numvarpos=locatevar();
    array_index=getarrayindex();
    numvars[numvarpos][array_index]=0; // before assigning the new value, we erase the older one

    for (i=0; i<255; i++)
     numberstring[i]=0;
    i=0;
    while (currentkey!=',' && currentkey!=ENTER) // this loop does not yet recognize multiple values correctly
    {
     currentkey=keyboardinput(1);
     if ((currentkey!=BACKSPACE && currentkey>='0' && currentkey<='9' && currentkey) || currentkey=='-' || currentkey=='.')
      numberstring[i++]=currentkey;
     else if (currentkey==BACKSPACE)
      numberstring[i--]=0;
    }
    how_many_inputs++;
   }
  }
  
  // if the user pressed ENTER before inputting all the variables, we
  // go to a new line on the screen and display two ? and a space
  if (currentkey==ENTER)
  {
   // Enter();
   numvars[numvarpos][array_index]=atof(numberstring);
   if (how_many_inputs<how_many_vars)
    prints((uchar *)"?? ");
  }
  currentkey=0;
  
  // We reach the next variable
  while (program[pc]!=',' && program[pc]!=':' && program[pc])
   pc++;
  if (program[pc]!=':' && program[pc])
   pc++; // if we landed on a ':' character or the end of the line, WE MUST NOT SKIP IT!
  skipspaces();
 }
}

void InputSharp(void) // [LINE] INPUT#logical_channel_number, variable_list
{
 uchar lfn,i;
 char numberstring[255];
 uint array_index;
 long filepos;
 int buffer;
 
 // We move past the INPUTSHARP token and skip the eventual spaces
 pc++;
 skipspaces();
 
 // Now we are on a numerical value telling us which logical file
 // number to read data from. We read that value.
 lfn=(uchar)getnum();
 
 // If it's not open to read, it's useless to parse the rest of the
 // instruction, so we check that first.
 switch (filestatus[lfn])
 {
  case CLOSED: printerror(FILENOTOPEN); break;
  case OPEN_TO_WRITE:
  case OPEN_TO_APPEND: printerror(NOTINPUTFILE); break;
  case OPEN_TO_READ:
       // now that we are sure that everything is right and we are
       // located on the first item in the variable list, we continue
       // parsing the instruction
       while (pc<instruction_end_address)
       {
        if (program[pc+1]=='$' || program[pc+2]=='$') // reading a string
        {
         // We locate the variable to use
         stringvarpos=locatevar();
         array_index=getarrayindex();
        
         // We read the string from the file and we write it into the
         // appropriate variable (we stop reading when we reach the
         // ETX character)
         for(i=0; i<255; i++)
          stringvars[i][stringvarpos][array_index]=0;
         for (i=0; buffer!=0x3; i++)
         {
          buffer=fgetc(cFile[lfn]);
          if (buffer==0x3)
           break; // it looks bad but it does the job of not including an ETX character into the string
          if (buffer==EOF)
          {
           fclose(cFile[lfn]);
           filestatus[lfn]=CLOSED;
           printerror(FILEREAD);
           return;
          }
          stringvars[i][stringvarpos][array_index]=(uchar)buffer;
         }
         
         // Now that we have read the string, we must advance to the
         // next element of the file. This is located at the next
         // character that is not a space.
         do
          buffer=fgetc(cFile[lfn]);
         while (buffer==' ');
         
         fseek(cFile[lfn],-1,SEEK_CUR);
        }
        else // reading a number
        {
         numvarpos=locatevar();
         array_index=getarrayindex();
         for (i=0; i<255; i++)
          numberstring[i]=0;
         
         // A number is memorized into a file as a string. But, unlike
         // an actual string, it cannot contain spaces. For this, we
         // make a first read of all characters that are comprised
         // between 0 and 9. This, we put into a string. Then, we make
         // a second read of all spaces that follow (to position the
         // file position indicator to the next element of the file).
         // This, we throw away.
         for (i=0; i<255; i++) // first read (the one that matters)
         {
          buffer=fgetc(cFile[lfn]);
          if (buffer>='0' && buffer<='9') // we trust that no number will ever be 255 characters long
           numberstring[i]=(char)buffer;
          else
          {
           if (buffer==EOF)
           {
            fclose(cFile[lfn]);
            filestatus[lfn]=CLOSED;
            printerror(FILEREAD);
           }
           break;
          }
         }
         for (; i<255; i++) // second read (the one to discard)
         {
          buffer=fgetc(cFile[lfn]);
          if (buffer!=' ') // it's completely impossible to find 255 spaces after a number, because a tab equals 10 spaces, so this works
           break;
         }
         fseek(cFile[lfn],-1,SEEK_CUR);
         
         // Now that we have the number memorized as a sequence of
         // ASCII characters, we turn it into an actual number, which
         // we put into the appropriate variable
         numvars[numvarpos][array_index]=atof(numberstring);
        }
        // After reading the file and inserting the value into the
        // appropriate variable, we advance to either the next variable
        // or to the instruction end address
        while (program[pc]!=',' && program[pc]!=';' && pc<instruction_end_address)
         pc++;
         
        // If we are on a separator (, or ;) we advance to the next
        // variable
        if (program[pc]==',' || program[pc]==';')
        {
         pc++;
         skipspaces();
        }
        
        // Now, the file position indicator might lie on the first
        // character of a newline sequence (CR+LF on Windows, only LF
        // on Un*x operating systems). In order to know that, we read
        // the byte(s) at the current position. If they are a newline
        // sequence, the simple act of reading them will place the file
        // position indicator correctly. If they are not, we will move
        // the file position indicator backwards appropriately.
        numberstring[0]=(char)fgetc(cFile[lfn]);
#ifdef ALLEGRO_WINDOWS
        numberstring[1]=(char)fgetc(cFile[lfn]);
        if (!(numberstring[0]==0x0D && numberstring[1]==0x0A))
         fseek(cFile[lfn],-2,SEEK_CUR);
#elif defined ALLEGRO_UNIX
        if (numberstring[0]!=0x0A)
         fseek(cFile[lfn],-1,SEEK_CUR);
#endif
       }
       break;
 }
}

double Len(void) // LEN (string)
{
 uchar i,command,parameter,number_of_parentheses;
 uint buffer,otherstringvarpos,otherarray_index;
 double length;
  
 // We are still on the token. We reach the open parenthesis...
 while (program[pc]!='(' || pc==instruction_end_address || program[pc]==':')
  pc++;
 if (pc==instruction_end_address || program[pc]==':')
  printerror(SYNTAXERROR); // (no parenthesis? no party)
 else
 {
  skipspaces();
  pc++; // ...and go past it
 }
 
 // Now we might be:
 // - on a quotes symbol (the parameter is an immediate string)
 // - on a letter (the parameter is a variable)
 // - on a string operation token
 length=0;
 
 while (pc<instruction_end_address && program[pc]!=':')
 {
  if (program[pc]==0x22)
  {
   pc++;
   for (; program[pc]!=0x22; pc++)
    length++;
  }
  else if (program[pc]>='A' && program[pc]<='Z')
  {
   otherstringvarpos=locatevar();
   otherarray_index=getarrayindex();
   
   for (i=0; stringvars[i][otherstringvarpos][otherarray_index]!=0; i++)
    length++;
  }
  else // part of an immediate string or part of a string variable
  {
   command=program[pc];
   // After the command, there will be eventually an open parenthesis, so
   // we reach it
   while (program[pc]!='(')
    pc++;
   pc++; // We go past it and skip the eventual spaces
   skipspaces();
   buffer=pc;
   // we reach the numeric parameter
   while (program[pc]!=',')
    pc++;
   pc++;
   skipspaces();
   
   switch (command)
   {
    case MID$:
         // we reach the second parameter
         while (program[pc]!=',')
          pc++;
         pc++;
         skipspaces();
    case LEFT$:
    case RIGHT$:
         // If we're not on a MID$ token, we already are on the correct parameter
         parameter=(uchar)evaluateexpression();
         length+=(double)parameter;
         break;
    case HEX$:
         // this operator always returns a 4-character string
         length+=4;
         break;
    case CHR$:
         // this operator always returns a 1-character string
         length++;
         break;
   }
  }
  
  // Next, we advance until we either find the instruction end address
  // or a C65ADD token
  while (pc<instruction_end_address && program[pc]!=':' && program[pc]!=C65ADD)
   pc++;
  if (program[pc]==C65ADD)
   pc++;
 }
 
 return length;
}

void Let(void) // [LET] variable = expression | [LET] variable = variable
{
 uint aux;
 uint otherstringvarpos,array_index,otherarray_index,sbfc_index;
 uchar i,command,parameter[2],stringbuffer[255],howmanyparameters,stringbuffer_for_chaining[255];
 double val;
  
 // At the beginning of this instruction, we may be on the LET token or
 // on the first character of the variable name. If we are on the LET
 // token, we must reach the variable name
 if (program[pc]==LET)
 {
  pc++;
  skipspaces();
 }
 
 // If the user is trying to use a variable name that coincides with that of
 // a command (tokenized as a command) we output a syntax error
 if (program[pc]==DO
  || program[pc]==FN
  || program[pc]==GO
  || program[pc]==IF
  || program[pc]==ON
  || program[pc]==OR
  || program[pc]==TO)
  printerror(SYNTAXERROR);

 // We must determine whether the variable contains a number or a string
 aux=pc;
 do
  aux++;
 while (program[aux]!=C65EQU && program[aux]!='$');
 
 // In both cases, we move back to the first character of the variable
 // name. If the variable contains a number, we insert the value into
 // the number variable array; if it contains a string, the value will
 // go into the string variable array
 if (program[aux]==C65EQU)
 {
  numvarpos=locatevar(); // now numvarpos contains the correct value
  array_index=getarrayindex();

  // We skip to the C65EQU token, skip the following spaces and
  // evaluate the following numeric expression
  skipspacesandevaluateexpression(array_index);
 }
 else
 {
  // We are NOT on a C65EQU token. This means that the variable is a string!
  stringvarpos=locatevar();
  array_index=getarrayindex();
  for (i=0; i<255; i++)
   stringbuffer_for_chaining[i]=0;
  
  // We must reach the next meaningful character (either a " or the 
  // first letter of the name of the second variable
  // To do this, we first advance to the = token, then find the correct
  // character
  while (program[pc]!=C65EQU)
   pc++;
  pc++;
  skipspaces();
  
  sbfc_index=0;
  
  // START OF CHAINING LOOP
  // Look for the C65ADD token!
  
  while (pc<instruction_end_address && program[pc]!=':')
  {
  
   while (program[pc]!=0x22 &&
         (program[pc]<'A' || program[pc]>'Z') &&
          program[pc]!=HEX$ &&
          program[pc]!=CHR$ &&
          program[pc]!=LEFT$ &&
          program[pc]!=MID$ &&
          program[pc]!=RIGHT$)
    pc++;
  
   // if it's a " character
   if (program[pc]==0x22)
   {
    pc++; // we go past it
    // We collect the characters of the string into the appropriate part
    // of the string array
    if (stringvarpos!=DS  // stringvars[i][140][0] (DS) is reserved for DOS errors
     && stringvarpos!=TI) // stringvars[i][722][0] (TI) is reserved for the system time
     // getstring(array_index);
    {
     while (program[pc]!=0x22 && program[pc]!=0 /* && sbfc_index<255 */)
     {
      if (program[pc]>='A' && program[pc]<='Z')
       stringbuffer_for_chaining[sbfc_index]=program[pc]+32;
      else
       stringbuffer_for_chaining[sbfc_index]=program[pc];
      sbfc_index++;
      if (sbfc_index>255)
      {
       printerror(STRINGTOOLONG);
       break;
      }
      pc++;
     }
    }
    else
     printerror(SYNTAXERROR);
   }
   else if (program[pc]==LEFT$ ||
            program[pc]==MID$ ||
            program[pc]==RIGHT$)
   {
    command=program[pc];
    otherarray_index=array_index;
    // After the command, there will be eventually an open parenthesis, so
    // we reach it
    while (program[pc]!='(')
     pc++;
    pc++; // We go past it and skip the eventual spaces
    skipspaces();
   
    // Now we might be on a quotes character (so we must do the operation on
    // an immediate string), or on a letter (so we must do the operation on
    // another variable). We discriminate the two cases.
    if (program[pc]==0x22)
    {
     pc++;
     for (i=0; program[pc]!=0x22; i++)
     {
      if (program[pc]>='A' && program[pc]<='Z')
       stringbuffer[i]=program[pc]+32;
      else
       stringbuffer[i]=program[pc];
      pc++;
     }
    }
    else
    {
     otherstringvarpos=locatevar();
     otherarray_index=getarrayindex();
     if (stringvarpos!=DS)
     {
      for (i=0; stringvars[i][otherstringvarpos][otherarray_index]!=0; i++)
       stringbuffer[i]=stringvars[i][otherstringvarpos][otherarray_index];
      for (; i<255; i++) // after the end of the string, the remainder of the array is filled with null chars
       stringbuffer[i]=0;
     }
     else
      printerror(SYNTAXERROR);
    }
    // Now, stringbuffer contains the string we must perform the operation on.
    // So, we read the parameter(s)...
    while (program[pc]!=',')
     pc++;
    pc++;
    skipspaces();
    parameter[0]=(uchar)evaluateexpression();
    howmanyparameters=1;
    if (program[pc]!=')') // If we haven't reached the end of the instruction, there's still one more parameter to read
    {
     parameter[1]=(uchar)getnum();
     howmanyparameters=2;
    }
    // ... and perform the operation.
    switch (command)
    {
     case LEFT$:
          for (i=0; i<parameter[0]; i++)
           // stringvars[i][stringvarpos][otherarray_index]=stringbuffer[i];
           stringbuffer_for_chaining[sbfc_index++]=stringbuffer[i];
          break;
     case MID$:
          for (aux=0; aux<parameter[0]; aux++)
           ;
          aux--;
          if (howmanyparameters==2)
          {
           for (i=0; i<parameter[1]; i++)
            // stringvars[i][stringvarpos][otherarray_index]=stringbuffer[aux++];
            stringbuffer_for_chaining[sbfc_index++]=stringbuffer[aux++];
          }
          else
          {
           for (i=0; stringbuffer[aux]; i++)
            // stringvars[i][stringvarpos][otherarray_index]=stringbuffer[aux++];
            stringbuffer_for_chaining[sbfc_index++]=stringbuffer[aux++];
          }
          break;
     case RIGHT$:
          for (aux=0; stringbuffer[aux]; aux++)
           ;
          aux-=parameter[0];
          for (i=0; i<parameter[0]; i++)
           // stringvars[i][stringvarpos][otherarray_index]=stringbuffer[aux++];
           stringbuffer_for_chaining[sbfc_index++]=stringbuffer[aux++];
          break;
    }
    if (sbfc_index>255)
    {
     printerror(STRINGTOOLONG);
     break;
    }
    /* for (; i<255; i++) // after the end of the string, the rest of the array is filled with null chars
     stringvars[i][stringvarpos][otherarray_index]=0; */
    for (i=sbfc_index; i<255; i++)
      stringbuffer_for_chaining[i]=0;
   }
   else if (program[pc]==HEX$)
   {
    // We go past the open parenthesis and eventual spaces to reach the
    // parameter, which can be a variable or an expression
    while (program[pc]!='(')
     pc++;
    pc++;
    skipspaces();
    val=evaluateexpression();
    if (val==((uint)val) && val>=0 && val<=65535)
    {
     for (i=0; i<255; i++)
      stringbuffer[i]=0;
     sprintf((char *)stringbuffer,"%x",(uint)val);
     for (i=0; i<255; i++)
      // stringvars[i][stringvarpos][array_index]=stringbuffer[i];
     {
      stringbuffer_for_chaining[sbfc_index++]=stringbuffer[i];
      if (sbfc_index>255)
      {
       printerror(STRINGTOOLONG);
       break;
      }
     }
    }
    else
     printerror(ILLEGALQUANTITY);
   }
   else if (program[pc]==CHR$)
   {
    // We go past the open parenthesis and eventual spaces to reach the
    // parameter, which can be a variable or an expression
    while (program[pc]!='(')
     pc++;
    pc++;
    skipspaces();
    val=evaluateexpression();
    if (val==((uint)val) && val>=0 && val<=255)
    {
     /* for (i=0; i<255; i++)
      stringvars[i][stringvarpos][array_index]=0;
     stringvars[0][stringvarpos][array_index]=(uchar)val; */
     stringbuffer_for_chaining[sbfc_index++]=(uchar)val;
     if (sbfc_index>255)
     {
      printerror(STRINGTOOLONG);
      break;
     }
    }
    else
     printerror(ILLEGALQUANTITY);
   }
   else // if it's another variable, we copy it into the first variable
   {
    otherstringvarpos=locatevar();
    otherarray_index=getarrayindex();
    if (stringvarpos!=DS)
    {
     for (i=0; stringvars[i][otherstringvarpos][otherarray_index]!=0; i++)
      // stringvars[i][stringvarpos][otherarray_index]=stringvars[i][otherstringvarpos][otherarray_index];
      stringbuffer_for_chaining[sbfc_index++]=stringvars[i][otherstringvarpos][otherarray_index];
     /* for (; i<255; i++) // after the end of the string, the rest of the array is filled with null chars
      stringvars[i][stringvarpos][otherarray_index]=0; */
     if (sbfc_index>255)
     {
      printerror(STRINGTOOLONG);
      break;
     }
    
     for (i=sbfc_index; i<255; i++)
      stringbuffer_for_chaining[i]=0;
    }
    else
     printerror(SYNTAXERROR);
   }
   // Next, we advance until we either find the instruction end address,
   // a colon or a C65ADD token
   while (pc<instruction_end_address && program[pc]!=':' && program[pc]!=C65ADD)
    pc++;
   if (program[pc]==C65ADD)
    pc++;
   
  } // END OF CHAINING LOOP
  
  // Now that we've done what we needed to do and we've exited the loop, we
  // copy the contents of the buffer into the appropriate string
  for (i=0; i<255; i++)
   stringvars[i][stringvarpos][array_index]=stringbuffer_for_chaining[i];
 }
}

void Line(void) // LINE x0, y0, x1, y1
{
 uint par[4];
 uchar i,j,k;
 
 // This array will contain the native (Allegro screen) coordinates for
 // each of the vertices
 // uint vertex[2][2]; // vertex[vertexnumber][coordinate]
 
 pc++; // We move past the LINE token
 
 // After the LINE token, there may or may not be a number of spaces
 skipspaces();
 
 for (i=0; i<4; i++)
  par[i]=(uint)getnum();
   
 vertex[0][0]=par[0]*xzoom; vertex[0][1]=par[1]*yzoom;
 
 // acquire_screen();
 // If both vertices have the same coordinates, the command is
 // equivalent to a "putpixel", so that is what will be done
 if (par[0]==par[2] && par[1]==par[3])
  c65draw(vertex[0][0],vertex[0][1]);
 else
 {
  vertex[1][0]=par[2]*xzoom; vertex[1][1]=par[3]*yzoom;
  // The main line is traced
  fastline(screen,vertex[0][0]+screen_border_x,vertex[0][1]+screen_border_y,vertex[1][0]+screen_border_x,vertex[1][1]+screen_border_y,forecolor);
 
  // Additional lines must be traced to simulate different resolutions
  // (note: only the thickness is simulated. The pixelation remains
  // constant for every simulated resolution)
  for (j=2; j<7; j++)
  {
   k=j-1;
   if (xzoom>=j)
   {
    for (i=0; i<j; i++)
     fastline(screen,vertex[0][0]+screen_border_x+k,vertex[0][1]+screen_border_y+i,vertex[1][0]+screen_border_x+k,vertex[1][1]+screen_border_y+i,forecolor);
   }
   if (yzoom>=j)
   {
    for (i=0; i<j; i++)
     fastline(screen,vertex[0][0]+screen_border_x+i,vertex[0][1]+screen_border_y+k,vertex[1][0]+screen_border_x+i,vertex[1][1]+screen_border_y+k,forecolor);
   }
   if (xzoom>=j && yzoom>=j)
    fastline(screen,vertex[0][0]+screen_border_x+k,vertex[0][1]+screen_border_y+k,vertex[1][0]+screen_border_x+k,vertex[1][1]+screen_border_y+k,forecolor);
  }
  
  // ORIGINAL IMPLEMENTATION
  /* if (xzoom>=2)
   fastline(screen,vertex[0][0]+screen_border_x+1,vertex[0][1]+screen_border_y,vertex[1][0]+screen_border_x+1,vertex[1][1]+screen_border_y,forecolor);
  if (yzoom>=2)
   fastline(screen,vertex[0][0]+screen_border_x,vertex[0][1]+screen_border_y+1,vertex[1][0]+screen_border_x,vertex[1][1]+screen_border_y+1,forecolor);
  if (xzoom>=2 && yzoom>=2)
   fastline(screen,vertex[0][0]+screen_border_x+1,vertex[0][1]+screen_border_y+1,vertex[1][0]+screen_border_x+1,vertex[1][1]+screen_border_y+1,forecolor);
  
  if (xzoom==4)
  {
   fastline(screen,vertex[0][0]+screen_border_x+2,vertex[0][1]+screen_border_y,vertex[1][0]+screen_border_x+2,vertex[1][1]+screen_border_y,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x+3,vertex[0][1]+screen_border_y,vertex[1][0]+screen_border_x+3,vertex[1][1]+screen_border_y,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x+2,vertex[0][1]+screen_border_y+1,vertex[1][0]+screen_border_x+2,vertex[1][1]+screen_border_y+1,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x+3,vertex[0][1]+screen_border_y+1,vertex[1][0]+screen_border_x+3,vertex[1][1]+screen_border_y+1,forecolor);
  }
  if (yzoom==4)
  {
   fastline(screen,vertex[0][0]+screen_border_x,vertex[0][1]+screen_border_y+2,vertex[1][0]+screen_border_x,vertex[1][1]+screen_border_y+2,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x,vertex[0][1]+screen_border_y+3,vertex[1][0]+screen_border_x,vertex[1][1]+screen_border_y+3,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x+1,vertex[0][1]+screen_border_y+2,vertex[1][0]+screen_border_x+1,vertex[1][1]+screen_border_y+2,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x+1,vertex[0][1]+screen_border_y+3,vertex[1][0]+screen_border_x+1,vertex[1][1]+screen_border_y+3,forecolor);
  }
  if (xzoom==4 && yzoom==4)
  {
   fastline(screen,vertex[0][0]+screen_border_x+2,vertex[0][1]+screen_border_y+2,vertex[1][0]+screen_border_x+2,vertex[1][1]+screen_border_y+2,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x+3,vertex[0][1]+screen_border_y+2,vertex[1][0]+screen_border_x+3,vertex[1][1]+screen_border_y+2,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x+2,vertex[0][1]+screen_border_y+3,vertex[1][0]+screen_border_x+2,vertex[1][1]+screen_border_y+3,forecolor);
   fastline(screen,vertex[0][0]+screen_border_x+3,vertex[0][1]+screen_border_y+3,vertex[1][0]+screen_border_x+3,vertex[1][1]+screen_border_y+3,forecolor);
  } */
 }
 // release_screen();
}

void Locate(void) // LOCATE x,y
{
 // UNIMPLEMENTED IN THE REAL C65
 // Since the C65 manual offers no clue to how the command should work
 // (all it says is [*** NOT YET IMPLEMENTED ***]), this implementation
 // follows the syntax of the Amiga BASIC command with the same name,
 // which positions the cursor on the text window. The parameters X and Y
 // Y specify the text column and line the cursor must be placed on.
 // Both parameters are compulsory.
 uint par[2];
 
 pc++; // We move past the LOCATE token
 
 // After the LOCATE token, there may or may not be a number of spaces
 skipspaces();
 
 // We read the new text coordinates
 par[0]=(uint)getnum();
 par[1]=(uint)getnum();
 
 // We place the cursor to the new position
 CharX=par[0]*(xzoom<<3);
 CharY=par[1]*(yzoom<<3);
 Cout(' '); CharX-=(xzoom<<3); // Without emitting a character, the command does not work
}

void Loop(void)
{
 uint cycletype;
 
 pc++; // We move past the LOOP token
 
 // After the LOOP token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we may be on an instruction end address, a colon (so we must
 // return after the corresponding DO instruction), a WHILE token or an
 // UNTIL token (so we must decide whether to exit the loop)
 
 switch (program[pc])
 {
  case ':':
       found_colon=':';
  case 0:
       if (!must_exit)
       {
        pc=loopstack[loopnestinglevel];
        if (program[pc]) // If we are here, we landed on a WHILE or UNTIL token
        {
         while (program[pc]!=DO)
          pc--;
         loopstack[loopnestinglevel]=pc;
         instruction_end_address=loop_instruction_end_address;
        }
        else
         instruction_end_address=pc;
       }
       break;
  case WHILE:
  case UNTIL:
       cycletype=program[pc];
       pc++; // we move past the token
       skipspaces(); // we skip the eventual spaces
       
       // We check whether the comparison is between two strings or
       // two numbers, and act accordingly
       if (program[pc]!=0x22 && program[pc+1]!='$' && program[pc+2]!='$') // "number" cases
        processnumbercondition(); // We process the number condition, to know whether it's true or false
       else // string cases
        processstringcondition(); // We process the string condition, to know whether it's true or false
       
       // The WHILE cycle loops if the condition is true. The UNTIL
       // cycle loops if the condition is false. We check the type and
       // act accordingly
       // In order to recycle code, we use a single check, but we
       // invert the value of "condition" if the cycle type is WHILE
       if (cycletype==WHILE)
        condition ? condition=0 : condition=1;
       // Now we can always treat the cycle like an UNTIL cycle
       if (condition)
        loopnestinglevel--;
       else
       {
        pc=loopstack[loopnestinglevel];
        instruction_end_address=pc;
       }
       break;
 }
}

void MuParserToggle(uchar from_line)
{
 uchar i=8; // the word "muparser" is made out of 8 characters, from 0
            // to 7. So, if we have reached this function from the
            // command line, we must start reading the command line
            // from character 8.
 Enter();
 if (from_line)
 {
  while (linebuffer[i]==' '&& i<80)
   i++;
  if (linebuffer[i]=='0'|| linebuffer[i]=='1')
   enable_muparser=linebuffer[i]-'0';
  else
  {
   if (screentype==65)
    forecolor=makecol(240,240,0);
   acquire_screen();
   prints((uchar *)"muparser is currently ");
   if (enable_muparser)
    prints((uchar *)"enabled");
   else
    prints((uchar *)"disabled");
   Enter();
   prints((uchar *)"type \"muparser 1\" to enable muparser");
   Enter();
   prints((uchar *)"type \"muparser 0\" to disable muparser");

   if (screentype==65)
    forecolor=makecol(240,240,240);
   Ready();
   release_screen();
   return;
  }
 }
 else
 {
  skipspaces();
  if (program[pc]=='0'|| program[pc]=='1')
   enable_muparser=(uchar)getnum();
  else
   printerror(SYNTAXERROR);
 }
 
 if (screentype==65)
  forecolor=makecol(240,240,0);
 acquire_screen();
 if (enable_muparser)
  prints((uchar *)"muparser enabled");
 else
  prints((uchar *)"muparser disabled");
 if (screentype==65)
  forecolor=makecol(240,240,240);
 Ready();
 release_screen();
}

void Next(void)
{
 ulong aux;
 
 pc++; // we move past the NEXT token
 
 // After the NEXT token, there may or may not be a number of spaces
 skipspaces();
 
 // Now we might either be on the instruction end address, on a colon
 // or on the first letter of a variable name. This is completely
 // irrelevant. Even the variable name is ignored, because the variable
 // we are using is determined by our current nesting level, not by a
 // useless parameter that might even not be there.
 
 // First, we add the step value to the variable
 numvars[forvarpos[loopnestinglevel]][forarrayindex[loopnestinglevel]]+=forstep[loopnestinglevel];
 
 // Now we check if the new value is greater than the value that must
 // be reached. If it is, we decrease the loop nesting level. If it's
 // not, we go back to the beginning of the FOR cycle.
 if (numvars[forvarpos[loopnestinglevel]][forarrayindex[loopnestinglevel]] > forto[loopnestinglevel])
 {
  loopnestinglevel--; // this is true when the For cycle is completed
 }
 else
 {
  pc=loopstack[loopnestinglevel];
  if (!program[pc])
   instruction_end_address=pc;
  else if (program[pc]==':')
  {
   found_colon=':';
   // We retrieve the actual instruction end address
   aux=pc;
   while (program[aux])
    aux++;
   instruction_end_address=aux;
  }
 }
}

void Paint(void) // PAINT x,y, mode [,color]
{
 // UNIMPLEMENTED IN THE REAL C65
 uint par[4];
 uchar i;
 uint copycolor;
 
 // This array contains the native (Allegro screen) coordinates for
 // the specified point
 uint native[2];
 
 pc++; // We move past the PAINT token
 
 // After the PAINT token, there may or may not be a number of spaces
 skipspaces();
 
 // The first three parameters are compulsory. They are the starting
 // coordinates (X,Y) of the PAINT operation, and the mode.
 for (i=0; i<3; i++)
  par[i]=(uint)getnum();
  
 // The fourth parameter is optional. To tell whether it's there or not,
 // we read the current program byte. If it's 0 or ':', we are at the
 // end of the instruction and we just use the current forecolor.
 // Otherwise, the parameter is there and can be read, so we
 // temporarily modify the forecolor accordingly.
 copycolor=forecolor;
 if (program[pc] && program[pc]!=':')
 {
  par[3]=(uint)getnum();
  forecolor=makecol(palette[DrawViewScreen[1]][par[3]][0],
                    palette[DrawViewScreen[1]][par[3]][1],
                    palette[DrawViewScreen[1]][par[3]][2]);
 }
 
 native[0]=par[0]*xzoom; native[1]=par[1]*yzoom;
 
 // Now we can flood fill the area, but ONLY if mode is set to 0.
 // From the C65 manual: "mode 0: fill area to edge = color
                            // 1: fill area to edge=same as color at x,y"
 // But the area is *ALREADY* the same color as at x,y, so if par[2]==1
 // we do nothing
 if (!par[2])
 {
  // acquire_screen();
  floodfill(screen,native[0],native[1],forecolor);
  // release_screen();
 }
  
 // If the forecolor has been changed, we resume it
 if (forecolor!=copycolor)
  forecolor=copycolor;
}

void Palette(void) // PALETTE [screen#|COLOR], color#, red, green, blue
{
 uint par[4];
 uchar i;
 uint index;
 
 pc++; // We move past the PALETTE token
 
 // After the PALETTE token, there may or may not be a number of spaces
 skipspaces();
 
 // This could be a normal PALETTE instruction or a PALETTE RESTORE
 // instruction. We perform accordingly.
 if (program[pc]==RESTORE)
  paletterestore();
 else
 {
  for (i=0; i<5; i++)
   par[i]=(uint)getnum();
  
  // Every color is repeated multiple times along the 256 elements of
  // the palette array, depending on the bit depth of the open screen.
  // For example: if the screen is 8 bit (256 colors), every color will  
  // be present just once.
  // If the screen is 5 bit (32 colors), color 1 will be repeated to
  // 32,64,96,128,160,192,224; the color at position 1 will be repeated
  // at 33,65,97,129,161,193,225 and so on
  for (index=0; index<256; index+=(1<<graphicscreen[openscreen][2]))
  {
   palette[par[0]][par[1]+index][0]=c65color[par[2]]; // red
   palette[par[0]][par[1]+index][1]=c65color[par[3]]; // green
   palette[par[0]][par[1]+index][2]=c65color[par[4]]; // blue
  }

 }
}

void Pen(void) // PEN pen, color
{
 uchar par[2];
 uchar i;
 
 // After the PEN token, there may or may not be a number of spaces
 skipspaces();
 
 for (i=0; i<2; i++)
  par[i]=(uint)getnum();
  
 // The first parameter of PEN is useless.
 // From the C65 manual: "Whatever graphic commands you use after a PEN
 // command will use the PEN you specified."
 // If the first parameter is changed during the program, graphic
 // operations are done with a new pen and the previous pen needs to be
 // reactivated... with the SAME PEN COMMAND.
 
 // The second parameter is used to change the foreground color
 forecolor=makecol(palette[DrawViewScreen[1]][par[1]][0],
                   palette[DrawViewScreen[1]][par[1]][1],
                   palette[DrawViewScreen[1]][par[1]][2]);
}

void Print(void) // PRINT [expression_list] [<,|;>]
{
 uint i,tempvar_i,array_index;
 double tempvar_f;
 uchar separator=1; // "1" is an invalid value
 uchar ccounter=0; // character counter (how many characters did we print?)
 // This implementation of PRINT only prints immediate numbers,
 // immediate strings and variables (it does not evaluate expressions)
 
 pc++; // We move past the PRINT token
 
 // After the PRINT token, there may or may not be a number of spaces,
 skipspaces();
 
 // acquire_screen();
 // The PRINT instructions accepts multiple parameters, which are
 // checked in a loop until the end of the instruction is reached
 // "separator" is the character that separates each parameter, it can
 // be "+" ";" "," or 0. If it's 0 or ':', the instruction is finished
 while (separator && separator!=':')
 {
  // The first character of a parameter may be:
  // - a letter (so the parameter is a variable)
  // - a digit (so the parameter is an immediate positive number)
  // - the token C65SUB (so the parameter is an immediate negative number)
  // - the character "quote" (so the parameter is an immediate string)
  // - the token SPC (to print that number of spaces)
  // - the token TABC65 (to reach that tab point)
  // - the token CHR$ (to turn an ASCII code into a character)
  if (program[pc]>='A' && program[pc]<='Z')
  {
   // first char is a letter: parameter is a variable
   // First, we must figure out if the variable contains a number or a
   // string. We are on the first char of the name, so if a "$" is
   // present within 2 position, the variable is a string
   if (program[pc+1]=='$' || program[pc+2]=='$')
   {
    stringvarpos=locatevar();
    array_index=getarrayindex();
    for (i=0; stringvars[i][stringvarpos][array_index] && i<256; i++)
    {
     Cout(stringvars[i][stringvarpos][array_index]);
     ccounter++;
     if (ccounter==chperline)
     {
      Enter();
      ccounter=0;
     }
    }
   }
   else
   {
    numvarpos=locatevar();
    array_index=getarrayindex();
    if (numvars[numvarpos][array_index]>=0)
     Cout(' ');
    tempvar_f=round(numvars[numvarpos][array_index]);
    if (numvars[numvarpos][array_index]==tempvar_f)
     printn((ulong)numvars[numvarpos][array_index]);
    else
     printn((double)numvars[numvarpos][array_index]);
   }
   // Then we move past the name of the variable
   while ((program[pc]>='A' && program[pc]<='Z') || program[pc]=='$' || program[pc]=='(' || program[pc]==')' || (program[pc]>='0' && program[pc]<='9'))
    pc++;
  }
  else if (program[pc]>='0' && program[pc]<='9')
  {
   // first char is a digit: parameter is an immediate positive number
   printn((double)getnum());
   Cout(' ');
  }
  else
  {
   switch(program[pc])
   {
    case C65SUB: // first char is the C65SUB token: parameter is an immediate negative number 
         Cout('-');
         pc++;
         printn((double)getnum());
         Cout(' ');
         break;
    case 0x22: // first char is "quotes": parameter is an immediate string
         pc++;
         while (program[pc]!=0x22 && program[pc])
         {
          // if (program[pc]>=' ' || program[pc]<='\'')
          if (program[pc]>=' ' && program[pc]<='@')
           Cout(program[pc++]);
          else
           Cout(program[pc++]+32);
          ccounter++;
          if (ccounter==chperline)
          {
           Enter();
           ccounter=0;
          }
         }
         if (program[pc]==0x22) // we skip the closing quotes
          pc++;
         break;
    case SPC: // first char is the SPC token: we must reach the numeric parameter and read it.
         // NOTE: SPC is only tokenized if immediately followed by an open
         // parenthesis, which is considered part of the instruction and does
         // not appear in the tokenized program. It is not tokenized if the
         // source contains spaces between the word SPC and the open
         // parenthesis.
         pc++;
         skipspaces();
         tempvar_f=getnum();
   
         // We have read the parameter, now we print that many spaces
         for (i=0; i<(uint)tempvar_f; i++)
          Cout(' ');
    
         // We reach the closed parenthesis and skip it
         while (program[pc]!=')')
          pc++;
         pc++;
         break;
    case TABC65:
         // The TAB function (identified in this project as TABC65 to avoid
         // confusion with the TAB character) moves the cursor to the
         // corresponding column during a PRINT statement. As with SPC, TAB
         // is only tokenized if immediately followed by an open parenthesis.
         pc++;
         skipspaces();
         tempvar_f=getnum();
   
         // We have read the parameter, now we move the cursor to the
         // corresponding column.
         CharX=(uint)tempvar_f*(xzoom<<3);
   
         // We reach the closed parenthesis and skip it
         while (program[pc]!=')')
          pc++;
         pc++;
         break;
    case CHR$: // CHR$ command: it translates an ASCII code into its corresponding character
         // We go past the open parenthesis and eventual spaces to reach the
         // parameter, which can be a variable or an expression
         while (program[pc]!='(')
          pc++;
         pc++;
         skipspaces();
         tempvar_f=evaluateexpression();
   
         // We go back to the closed parenthesis         
         while (program[pc]!=')')
          pc--;
         
         if (tempvar_f==((uint)tempvar_f) && tempvar_f>=0 && tempvar_f<=255)
         {
          Cout((uchar)tempvar_f);
          Cout(' ');
         }
         else
          printerror(ILLEGALQUANTITY);
    
         // We reach the closed parenthesis and skip it
         // while (program[pc]!=')')
         // pc++;
         // We are at the closed parenthesis, so we skip it
         pc++;
         break;
    case DO:
    case FN:
    case GO:
    case IF:
    case ON:
    case OR:
    case TO:
         // If the user is attempting to display the value of a variable called 
         // like a two-letter instruction (which can exist in the variable space 
         // but the C65 disallows) we must end the program with a syntax error
         pc++;
         printerror(SYNTAXERROR);
         break;
   }
  }
  // After the parameter there may or may not be a number of spaces,
  // followed by a separator (a ";" or a ",") and more spaces
  // BUT: IF THERE WAS A COMMA OR A DECIMAL POINT, WE SKIPPED IT 
  // ALREADY WITH GETNUM!!!
  skipspaces();
  separator=program[pc];
  
  // This condition is false if we skipped a comma and the "separator"
  // is ANY character that is NOT a comma, a semicolon or the C65ADD token
  if (separator==';' || separator==',' || separator==C65ADD)
  {
   pc++;
   skipspaces();
  }
  if (separator==',' || (separator && separator!=';' && separator!=C65ADD))
  {
   // if the separator WAS a comma (which we might have skipped), the
   // next parameter must be printed at the next tabulation point (a
   // tabulation is 8 spaces wide). The decimal point does NOT count
   // as a separator
   /* if (program[pc-1]=='.')
   {
    BackSpace();
    Cout('.');
    printeddecimalpoint=1;
   }
   else */
    Tab();
  }
 }
 // release_screen(); 
 Enter();
}

void PrintSharp(void) // PRINT# logical_channel_number [,expression_list] [<,|;>]
{
 uint array_index,chars_per_line;
 uchar lfn;
 uchar buffersize;
 uchar buffer[255];
 uchar separator=1; // "1" is an invalid value
 
 for (buffersize=0; buffersize<255; buffersize++)
  buffer[buffersize]=0;
 buffersize=chars_per_line=0;
 
 // We move past the PRINTSHARP token and skip the eventual spaces
 pc++;
 skipspaces();
 
 // Now we are on a numerical value telling us which logical file
 // number to read data from. We read that value.
 lfn=(uchar)getnum();
 
 // If it's not open to write or append, it's useless to parse the rest
 // of the instruction, so we check that first.
 switch (filestatus[lfn])
 {
  case CLOSED: printerror(FILENOTOPEN); break;
  case OPEN_TO_READ: printerror(NOTOUTPUTFILE); break;
  case OPEN_TO_WRITE:
  case OPEN_TO_APPEND:
       // now that we are sure that everything is right, we continue
       // parsing the instruction
       
       // The PRINTSHARP instructions accepts multiple parameters,
       // which are checked in a loop until the end of the instruction
       // is reached
       // "separator" is the character that separates each parameter,
       // it can be ";" "," or 0. If it's 0 or ':', the instruction is
       // finished
       while (separator && separator!=':')
       {
        // The first character of a parameter may be:
        // - a letter (so the parameter is a variable)
        // - a digit (so the parameter is an immediate positive number)
        // - the token C65SUB (so the parameter is an immediate negative number)
        // - the character "quote" (so the parameter is an immediate string)
        if (program[pc]>='A' && program[pc]<='Z')
        {
         // first char is a letter: parameter is a variable
         // First, we must figure out if the variable contains a number
         // or a string. We are on the first char of the name, so if a
         // "$" is present within 2 position, the variable is a string
         if (program[pc+1]=='$' || program[pc+2]=='$')
         {
          stringvarpos=locatevar();
          array_index=getarrayindex();
          
          for (buffersize=0; stringvars[buffersize][stringvarpos][array_index]; buffersize++)
           buffer[buffersize]=stringvars[buffersize][stringvarpos][array_index];
          fwrite (buffer, sizeof(unsigned char), buffersize, cFile[lfn]);
          chars_per_line=buffersize;

          // After writing the string in the file, we terminate it with
          // an ETX (end of text) character. This is done because a
          // string can contain spaces, so we cannot rely on the space
          // character as a string terminator.
          buffer[0]=0x3;
          buffersize=1;
          chars_per_line++;
          fwrite (buffer, sizeof(unsigned char), buffersize, cFile[lfn]);
         }
         else
         {
          numvarpos=locatevar();
          array_index=getarrayindex();
          
          if (numvars[numvarpos][array_index] == round(numvars[numvarpos][array_index]))
           buffersize=sprintf((char *)buffer,"%d",(uint)(numvars[numvarpos][array_index]));
          else
           buffersize=sprintf((char *)buffer,"%f",(double)(numvars[numvarpos][array_index]));
          fwrite (buffer, sizeof(unsigned char), buffersize, cFile[lfn]);
          chars_per_line=buffersize;
         }
         // Then we move past the name of the variable
         while ((program[pc]>='A' && program[pc]<='Z') || program[pc]=='$' || program[pc]=='(' || program[pc]==')' || (program[pc]>='0' && program[pc]<='9'))
          pc++;
        }
        else if ((program[pc]>='0' && program[pc]<='9') || program[pc]==C65SUB)
        {
         // first char is a digit: parameter is an immediate positive number
         // first char is the C65SUB token: parameter is an immediate negative number
         // write immediate number to file
         for (buffersize=0; (program[pc]>='0' && program[pc]<='9') || program[pc]==C65SUB; buffersize++)
          buffer[buffersize]=program[pc++];
         fwrite (buffer, sizeof(unsigned char), buffersize, cFile[lfn]);
         chars_per_line=buffersize;
        }
        else if (program[pc]==0x22)
        {
         // first char is "quotes": parameter is an immediate string
         pc++;
         for (buffersize=0; program[pc]!=0x22; buffersize++)
         {
          if (program[pc]>=' ' && program[pc]<='@')
           buffer[buffersize]=program[pc++];
          else
           buffer[buffersize]=program[pc++]+32;
         }
         fwrite (buffer, sizeof(unsigned char), buffersize, cFile[lfn]);
         chars_per_line=buffersize;
        }
        // After the parameter there may or may not be a number of
        // spaces, followed by a separator (a ";" or a ",") and more
        // spaces, BUT: IF THERE WAS A COMMA OR A DECIMAL POINT, WE
        // SKIPPED IT ALREADY WITH GETNUM!!!
        skipspaces();
        separator=program[pc];
        pc++;
        skipspaces();
        
        // The data layout depends on the separator we just found.
        // If it was a comma, we must add a tab (with the appropriate
        // number of spaces). If it was a semicolon, we must add a
        // space.
        if (separator==',')
        {
         buffersize=0;
         while (chars_per_line%10)
         {
          buffer[buffersize]=' ';
          buffersize++;
          chars_per_line++;
         }
         fwrite (buffer, sizeof(unsigned char), buffersize, cFile[lfn]);
        }
        else if (separator==';')
        {
         buffer[0]=' ';
         buffersize=1;
         chars_per_line++;
         fwrite (buffer, sizeof(unsigned char), buffersize, cFile[lfn]);
        }
       }
       // After we saved the line into the file, we must end it with a
       // newline sequence. This will be CR+LF on Windows, only LF on
       // Un*x operating systems.
#ifdef ALLEGRO_WINDOWS
       buffer[0]=0x0D;
       buffer[1]=0x0A;
       buffersize=2;
#elif defined ALLEGRO_UNIX
       buffer[0]=0x0A;
       buffersize=1;
#endif
       fwrite (buffer, sizeof(unsigned char), buffersize, cFile[lfn]);
       chars_per_line=0;
 } 
}

void Read (void) // READ variable_list
{
 uint aux,i,array_index;
 
 // We advance one step after the READ token
 pc++;
 
 // After the READ token, there may or may not be a number of spaces
 skipspaces();
 do
 {
  // If we are NOT on the end address of the READ instruction (so we
  // are supposed to read more data), but we have reached the end of
  // the program while parsing the data (so there are no more data to
  // read), the execution must terminate with an "out of data" error
  if ((pc!=instruction_end_address) && (!program[dc] && !program[dc+1]))
  {   
   printerror(OUTOFDATA);
   Enter();
#ifdef DEBUG_INSTRUCTIONS
   prints((uchar *)"dc="); printn(dc);
   prints((uchar *)"program["); printn(dc); prints((uchar *)"]="); printn(program[dc]); Enter();
   prints((uchar *)"program["); printn(dc+1); prints((uchar *)"]="); printn(program[dc+1]); Enter();
#endif
  }
  
  // Now we are on the first character of the variable name.
  // We must determine whether the variable contains a number or a string
  aux=pc;
  do
   aux++;
  while (program[aux]!=' ' && program[aux]!=',' && program[aux]!='$');
 
  if (program[aux]=='$') // the variable is a string
  {
   stringvarpos=locatevar();
   array_index=getarrayindex();
  
   // First, we get rid of any eventual content of the string
   for (i=0; i<255; i++)
    stringvars[i][stringvarpos][array_index]=0;
  
   // We advance past the quotes to the first character of the string,
   // and we get the string
   dc++;
   getdatastring();
  
   // Now we are on the quotes that close the string, so we move
   // 1 character past them and past the eventual spaces
   dc++;
   skipdataspaces();
  
   // If we have landed on a comma it means there are more strings, so
   // we move past it, skip the eventual spaces and reach the quotes
   // that mark the beginning of the next string
   if (program[dc]==',')
   {
    dc++;
    skipdataspaces();
   }
  }
  else // the variable is a number
  {
   numvarpos=locatevar(); // now numvarpos contains the correct value
   array_index=getarrayindex();
   numvars[numvarpos][array_index]=getdatanum();
  }
 
  // Now that we have successfully read the data element, there may or
  // may not be other variables. So, we increase the program counter
  // until we either reach the instruction end address (in that case,
  // the execution of the instruction is finished) or a comma (in that
  // case, we reach the next variable)
  do
   pc++;
  while (!(pc==instruction_end_address || program[pc]==','));

  // If we are on a comma, we reach the next variable
  if (program[pc]==',')
  {
   pc++;
   skipspaces();
  }
  
  // After a DATA instruction, there can only be more DATA instructions.
  // We are now supposed to be on the next data element, but if the
  // program[dc] is zero, we are either on the first zero that marks
  // the end of the program (if the next element is zero) or on the
  // instruction end address (if the next element is NOT zero).
  // If we are on the instruction end address, we move the data counter
  // to the next data element.
  // if (program_end_address-dc>5)
  if (!program[dc] && program_end_address-dc>3)
  {
   // First, we reach the next data token
   while (program[dc]!=DATA)
    dc++;
   
   // we move one step forward and skip the eventual spaces
   dc++;
   skipdataspaces();
  }
 }
 while (pc!=instruction_end_address);
}

// This function is only useful if the debug instructions are active.
// Normally, the comments must be just skipped
void Rem(void) // REM comments
{
#ifdef DEBUG_INSTRUCTIONS
 pc++; // We move past the REM token
 
 // After the REM token, there may or may not be a number of spaces,
 // which are not considered part of the comment
 skipspaces();
 
 prints ((uchar *)"rem ");
 
 // The comment is displayed
 while (program[pc]!=0)
 {
  if (program[pc]>='A' && program[pc]<='Z')
   // when the program is tokenized, the lowercase letters are transformed
   // into capital letters. The following instruction transforms them
   // back into lowercase letters
   Cout(program[pc]+32);
  else
   Cout(program[pc]);
  pc++;
 }
#else
 pc=instruction_end_address;
#endif
}

void Restore(void)
{
 uint parameter, scanned_line_number, scanned_instruction_end, aux;
 
 pc++; // We move past the RESTORE token
 
 // After the RESTORE token, there may or may not be a number of spaces
 skipspaces();
 
 // We bring the data counter back to the beginning. If the instruction has
 // no parameters, we're already done.
 initdatacounter();
 
 // If, instead, we aree on an immediate number or on a variable, we read
 //  the parameter and move the data counter on the corresponding line.
 if ((program[pc]>='0' && program[pc]<='9') ||
     (program[pc]>='A' && program[pc]<='Z' && program[pc+1]!='$' && program[pc+2]!='$'))
 {
  parameter=(uint)evaluateexpression();
  
  // Now, we are on the fist datum. But we must start from the beginning of the
  // first DATA LINE! So we go back, first to the DATA token...
  while (program[dc]!=DATA)
   dc--;
  // ...then to the previous instruction end address...
  while (program[dc])
   dc--;
  dc++;
  
  // We're at the beginning of the first data line!
  // So now we look for the correct line
  do
  {
   // We read the instruction end address (tokenized LSB-first = little endian  
   scanned_instruction_end=program[dc++];
   aux=program[dc]-0x20;
   aux<<=8;
   scanned_instruction_end+=aux;
   dc++;
    
   // We read the line number (tokenized LSB-first = little endian)
   scanned_line_number=program[dc++];
   aux=program[dc];
   aux<<=8;
   scanned_line_number+=aux;
   
   // if we're not on the correct line, we move past the instruction end address
   if (scanned_line_number!=parameter)
   {
    dc=scanned_instruction_end;
    dc++;
   }
  }
  while (scanned_line_number<parameter);
  
  // Now that we found the correct line, we advance to the DATA token...
  while (program[dc]!=DATA)
   dc++;
  dc++;
  
  // ...and finally, to the first datum of the line
  while (program[dc]<'0' || program[dc]>'9')
   dc++;
 }
}

void Return(void)
{
 if (gosubnestinglevel)
 {
  pc=instruction_end_address=gosubstack[gosubnestinglevel];
  gosubnestinglevel--;

  if (program[pc]==':')
  {
   found_colon=':';
   // Now instruction_end_address is still containing the end address of the PREVIOUS instruction!
   // We must make it point to the end of the NEXT instruction instead
   for (instruction_end_address=pc; program[instruction_end_address]; instruction_end_address++)
    ;
  }
 }
 else
  printerror (RETURNWITHOUTGOSUB);
}

double Rnd(double val)
{ 
 double i;
 // The parameter of RND can be 0, a negative number, or a positive number.
 // IF ZERO: the random number generator is initialized with the timer variable,
 //          and then a random number is returned.
 // IF NEGATIVE: the random number generator is initialized with the corresponding
 //              positive number, and then a dummy value is returned
 // IF POSITIVE: the parameter is DITCHED, and a new random value is returned
 //              based on the last initialization
 if (val<0)
 {
  srand ((uint)(-val));
  i=0; // dummy value, in the physical C65 is something like 2*10^(-8)
 }
 else
 {
  if (!val)
   srand ((uint)numvars[TI][0]);
  i=rand()/(double)RAND_MAX;
 }
 return i;
}

void Run(uchar from_line)
{
 uchar i;
#ifdef DEBUG_SCREEN
 size_t actual_size;
 FILE *pFile;
 const char base_string[] = "base_string";
 char out_string [65535];
 int number = 123;
#endif
 
 if (program[0]==0x01 && program[1]==0x20) // Only run a program if there is a program to run in the first place!
 {
  if (from_line)
  {
   if (!program_loaded_from_frontend)
    acquire_screen();
   // We initialize the necessary variables
   initdatacounter();
#ifdef DEBUG_SCREEN
   pFile = fopen ("errorlog.txt","a");
   fputs ("Data counter initialized\n",pFile);
   fclose (pFile);
#endif
   pc=0;
   resetc65vars();
#ifdef DEBUG_SCREEN
   pFile = fopen ("errorlog.txt","a");
   fputs ("BASIC variables initialized\n",pFile);
   fclose (pFile);
#endif
 
   // control variables
   null_char_counter=0;
   instruction_end_address=old_instruction_end_address=0;
   found_goto=found_end=found_colon=0;
   inside_if_clause=0;
   bug=0;
   trap=0;
#ifdef DEBUG_SCREEN
   pFile = fopen ("errorlog.txt","a");
   fputs ("Control variables initialized\n",pFile);
   fclose (pFile);
#endif
  
   // loop stack
   for (i=0; i<MAXLOOPS; i++)
   {
    loopstack[i]=0;
    forvarpos[i]=0;
    forstep[i]=0;
    gosubstack[i]=0;
   }
   loopnestinglevel=oldloopnestinglevel=must_exit=gosubnestinglevel=oldgosubnestinglevel=0;
#ifdef DEBUG_SCREEN
   pFile = fopen ("errorlog.txt","a");
   fputs ("Loop stacks initialized\n",pFile);
   fclose (pFile);
#endif
  
   // while (1) // this will NEVER be an infinite loop because a triple sequence of null chars is always present at the end of the program
   while (pc<PROGRAMSIZE)
   {
    if (!program[pc] && !program[pc+1] && !program[pc+2])
     break;
    pc++;
   }
   program_end_address=pc+2;
#ifdef DEBUG_SCREEN
   pFile = fopen ("errorlog.txt","a");
   fputs ("Program end address is ",pFile);
   sprintf(out_string, "%d",program_end_address);
   fputs(out_string,pFile);
   fputs("\n",pFile);
   fclose (pFile);
#endif
 
   pc=2; // the 2 header chars (01 20) are automatically skipped
   if (program_end_address>4)
   {
    do
    {
     interpret();
   
     // if an error has been found and there is no trap line, the program must interrupt
     if (numvars[ER][0]!=-1)
      break;
    }
    while (null_char_counter<3 &&
          (old_instruction_end_address!=instruction_end_address || pc<program_end_address) &&
          instruction_end_address &&
          !bug);
   }
   if (!program_loaded_from_frontend)
    Ready();
#ifdef DEBUG_SCREEN
   pFile = fopen ("errorlog.txt","a");
   if (program_end_address>4)
    fputs ("The program has been executed\n",pFile);
   else
    fputs ("There was no program to execute\n",pFile);
   fputs ("*****************\n",pFile);
   fputs ("* DEBUG LOG END *\n",pFile);
   fputs ("*****************\n\n",pFile);
   fclose (pFile);
#endif
  }
  else
  {
   // there is no reason for wanting to run a program from INSIDE THE
   // SAME PROGRAM! This check is unimplemented in the actual C65, but
   // it only makes the BASIC more robust, with no side effects
   printerror(SYNTAXERROR);
   Ready();
  }
 }
 else
  Ready();
}

void Scnclr(void)
{
 uchar par;
 
 pc++; // We move past the SCNCLR token
 
 // After the SCNCLR token, there may or may not be a number of spaces
 skipspaces();
 
 // The SCNCLR command is used to clear the screen.
 // If we are on the text screen, it is used without a parameter (we
 // clear the screen in blue and move the cursor to the top left.
 // If we are on a graphic screen, we need a parameter, so we set the
 // background color to the color specified in the command.
 
 if (!openscreen && pc<instruction_end_address)
  printerror(NOGRAPHICSAREA);
 else
 {
  if (!openscreen)
  {
   clear_to_color(screen,backcolor);
   CharX=CharY=0;
  }
  else
  {
   par=(uint)getnum();
   backcolor=makecol(palette[DrawViewScreen[1]][par][0],
                     palette[DrawViewScreen[1]][par][1],
                     palette[DrawViewScreen[1]][par][2]);
 
   // acquire_screen();
   // And now we clear the screen
   clear_to_color(screen,backcolor);
   // release_screen();
  }
 }
}

void ScreenClose(void)
{
 uchar par;
 
 pc++; // We move past the CLOSE token
 
 // After the CLOSE token, there may or may not be a number of spaces
 skipspaces();
 
 if (screentype)
  shifted=0;
  
 if (screentype==65)
  startscreen(1);
 else
  alternatescreen();
  
 DrawViewScreen[1]=0;
 openscreen=0;
 
 if (program_loaded_from_frontend)
  release_screen();
}

void ScreenDef(void) // SCREEN DEF screen#, width, height, depth
{
 uint par[4];
 uchar i;
 
 pc++; // We move past the DEF token

 // After the DEF token, there may or may not be a number of spaces
 skipspaces();

 for (i=0; i<4; i++)
  par[i]=(uint)getnum();
  
 graphicscreen[par[0]][0]=par[1];
 graphicscreen[par[0]][1]=par[2];
 graphicscreen[par[0]][2]=par[3];
}

void ScreenOpen(void) // SCREEN OPEN  screen# [,error_variable]
{
 // NOTE: the "error_variable" optional parameter was not implemented
 // in the physical C65. Since the manual does not explain how that
 // would work, we are not implementing it either
 
 pc++; // We move past the OPEN token
 
 // After the OPEN token, there may or may not be a number of spaces
 skipspaces();
 
 openscreen=(uchar)getnum();
}

void ScreenSet(void) // SCREEN SET  DrawScreen#, ViewScreen#
{
 uchar i;
 
 pc++; // We move past the SET token
 
 // After the SET token, there may or may not be a number of spaces
 skipspaces();
 
 for (i=0; i<2; i++)
  DrawViewScreen[i]=(uchar)getnum();
  
 // The X and Y zoom values (for Allegro to draw on the physical screen)
 // must be set according to the parameters of the SCREEN DEF 
 // instruction. Only after a SCREEN SET instruction those parameters
 // have an effect on the C65 screen. For the sake of simplicity,
 // everything will be drawn directly on the viewscreen (DrawViewScreen[1])
 
 switch (screen_width)
 {
  case 1280:
  case 1360:
  case 1440:
  case 1600:
       if (!graphicscreen[DrawViewScreen[1]][0]) // width of viewscreen
        xzoom=4;
       else // if (graphicscreen[DrawViewScreen[1]][0]==1)
        xzoom=2;
       if (!graphicscreen[DrawViewScreen[1]][1]) // height of viewscreen
        yzoom=4;
       else
        yzoom=2;
       break;
  case 1920:
       if (!graphicscreen[DrawViewScreen[1]][0]) // width of viewscreen
        xzoom=6;
       else // if (graphicscreen[DrawViewScreen[1]][0]==1)
        xzoom=3;
       if (!graphicscreen[DrawViewScreen[1]][1]) // height of viewscreen
        yzoom=6;
       else
        yzoom=3;
       break;
  default:
       if (!graphicscreen[DrawViewScreen[1]][0]) // width of viewscreen
        xzoom=2;
       else // if (graphicscreen[DrawViewScreen[1]][0]==1)
        xzoom=1;
       if (!graphicscreen[DrawViewScreen[1]][1]) // height of viewscreen
        yzoom=2;
       else
        yzoom=1;
       break;
 }
   
 // the bitplane depth is ignored (a 32-bit screen can always display
 // any color from a C65 screen)
}

void Sleep(void)
{
 uint par;
 
 pc++; // We move past the SLEEP token
 
 // After the SET token, there may or may not be a number of spaces
 skipspaces();
 
 par=(uint)getnum();
 
 // The SLEEP command of BASIC 10 accepts a parameter expressed in
 // seconds, but the parameter of the function rest() of Allegro is
 // expressed in milliseconds
 rest(par*1000);
}

void Trap(void)
{
 // we move past the TRAP token
 pc++;
 
 // We skip the eventual spaces
 skipspaces();
 
 // we set the line that will trap the errors in the program
 trap=(uint)evaluateexpression();
}
