#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "library.c";

/* -------------------------------------------*/

void main()
{
   int iCharacter; 
   int iLineMark = 0;
   int iLineCount = 0;
   int iCharacterCount = 0;
   int iOpenBraceCount = 0;
   int iCloseBraceCount = 0;
   int iTextLength = 0;
   int iTestPointer = 0;
   int iCommand = 0;
   /* pointer into the program.braceStack array */
   int * pBraceStackPointer; 

   char sText[200];

   char sCommandName[20] = "";

   FILE * inputstream = stdin;
   Program program;
   fnInitializeProgram(&program);
   pBraceStackPointer = &program.braceStack[0];
   Instruction * instruction = &program.instructionSet[0];
   
   iCharacter = getc(inputstream);

   while (iCharacter != EOF)
   {
    
      if (program.size >= MAXPROGRAMLENGTH)
             {
               fprintf(stderr, "line %d: the maximum number of script ",
                       iLineCount);
               fprintf(stderr, "statements (%d) is exceeded. This may be solved ",
                       MAXPROGRAMLENGTH);
               fprintf(stderr, "by changing the MAXPROGRAMLENGTH constant and recompiling");
               exit(2);
             }
   
     switch (iCharacter)
     {
       /*-----------------------------------------*/ 
       case '"':
         iLineMark = iLineCount;
         switch (instruction->command)
         {
           case UNDEFINED:
             fprintf(stderr, "misplaced quote (\"): line %d", iLineCount);
             exit(2);
           case CLEAR:
           case POP:
           case PUSH:
           case PUT:
           case GET:
           case INCREMENT:
           case DECREMENT:
           case INCC:
           case DECC:
           case CRASH:
           case TESTIS:
           case TESTBEGINS:
           case TESTCLASS:
           case NOP:
             fnCommandToString(sCommandName, instruction->command);
             fprintf(stderr, "command %s cannot take an argument: line %d", sCommandName, iLineCount);
             exit(2);
         }
         strcpy(sText, "");
         iTextLength = 0;
         iCharacter = getc(inputstream);
         iCharacterCount++;
         if (iCharacter == EOF)
         {
           fprintf(stderr, "stray quote (\") at line %d \n", iLineCount);
           exit(2);
         }

         if (iCharacter == '"')
         {
           fprintf(stderr, "empty quotes (\"\") at line %d \n", iLineCount);
           exit(2);
         }

         while ((iCharacter != EOF) && (iCharacter != '"') && (iTextLength < MAXARGUMENTLENGTH))
         {
           sprintf(sText, "%s%c", sText, iCharacter);
           iTextLength++;
           iCharacter = getc(inputstream);
           if (iCharacter == '\n') 
            { iLineCount++; }
           iCharacterCount++;
         }
         
         if (iCharacter == EOF)
         {
           fprintf(stderr, "unterminated quote (\") at line %d \n", iLineCount);
           exit(2);
         }

         if (iTextLength >= MAXARGUMENTLENGTH)
         {
           fprintf(stderr, "the argument (text between quotes) at line %d \n", iLineCount);
           fprintf(stderr, "is too long. The maximum is %d characters \n", MAXARGUMENTLENGTH);
           exit(2);
         }

         if (iCharacter == '"')
         {
           if (strlen(instruction->argument1) == 0)
             { strcpy(instruction->argument1, sText); }
           else if (strlen(instruction->argument2) == 0)
             { strcpy(instruction->argument2, sText); }
           else
           {
             fprintf(stderr, "The instruction at line %d has too many arguments \n");
             fprintf(stderr, "The maximum permitted is 2. \n");
             exit(2);
           }
         } 
         else
         {  
           fprintf(stderr, "error parsing quoted text at line %d. \n", iLineCount); exit(2); 
           fprintf(stderr, "this error indicates a bug in the code 'compiler.c' \n");
           exit(2); 
         }
         break;
       /*-----------------------------------------*/ 
       case '\'':
         iLineMark = iLineCount;
         switch (instruction->command)
         {
           case UNDEFINED:
             fprintf(stderr, "misplaced quote ('): line %d", iLineCount);
             exit(2);
           case CLEAR:
           case POP:
           case PUSH:
           case PUT:
           case GET:
           case INCREMENT:
           case DECREMENT:
           case INCC:
           case DECC:
           case CRASH:
           case TESTIS:
           case TESTBEGINS:
           case TESTCLASS:
           case NOP:
             fnCommandToString(sCommandName, instruction->command);
             fprintf(stderr, "command %s cannot take an argument: line %d", sCommandName, iLineCount);
             exit(2);
         }
         strcpy(sText, "");
         iTextLength = 0;
         iCharacter = getc(inputstream);
         iCharacterCount++;
         if (iCharacter == EOF)
         {
           fprintf(stderr, "stray quote (') at line %d \n", iLineCount);
           exit(2);
         }

         if (iCharacter == '\'')
         {
           fprintf(stderr, "empty quotes ('') at line %d \n", iLineCount);
           exit(2);
         }

         while ((iCharacter != EOF) && (iCharacter != '\'') && (iTextLength < MAXARGUMENTLENGTH))
         {
           sprintf(sText, "%s%c", sText, iCharacter);
           iTextLength++;
           iCharacter = getc(inputstream);
           if (iCharacter == '\n') 
            { iLineCount++; }
           iCharacterCount++;
         }
         
         if (iCharacter == EOF)
         {
           fprintf(stderr, "unterminated quote (') at line %d \n", iLineCount);
           exit(2);
         }

         if (iTextLength >= MAXARGUMENTLENGTH)
         {
           fprintf(stderr, "the argument (text between quotes) at line %d \n", iLineCount);
           fprintf(stderr, "is too long. The maximum is %d characters \n", MAXARGUMENTLENGTH);
           exit(2);
         }

         if (iCharacter == '\'')
         {
           if (strlen(instruction->argument1) == 0)
             { strcpy(instruction->argument1, sText); }
           else if (strlen(instruction->argument2) == 0)
             { strcpy(instruction->argument2, sText); }
           else
           {
             fprintf(stderr, "The instruction at line %d has too many arguments \n");
             fprintf(stderr, "The maximum permitted is 2. \n");
             exit(2);
           }
         } 
         else
         {  
           fprintf(stderr, "error parsing quoted text at line %d. \n", iLineCount); exit(2); 
           fprintf(stderr, "this error indicates a bug in the code 'compiler.c' \n");
           exit(2); 
         }
         break;
    
    
       /*-----------------------------------------*/ 
       // ignore whitespace
       case '\r':
       case '\t':
       case ' ': break;
       /*-----------------------------------------*/ 
       case '<':
         switch(instruction->command)
         {
           case UNDEFINED:
             break;
           default:
             fprintf(stderr, "Line %d: syntax error before '<' character. \n", iLineCount);
             fprintf(stderr, "(missing semi-colon?)\n");
             exit(2);
         }
         iLineMark = iLineCount;
         strcpy(sText, "");
         iTextLength = 0;
         iCharacter = getc(inputstream);
         iCharacterCount++;
         if (iCharacter == EOF)
         {
           fprintf(stderr, "script ends badly \n");
           exit(2);
         }

         if (iCharacter == '>')
         {
           fprintf(stderr, "empty test '<>' at line %d \n", iLineMark);
           exit(2);
         }

         while ((iCharacter != EOF) && (iCharacter != '>') && (iTextLength < MAXARGUMENTLENGTH))
         {
           /* handle the escape sequence */
           if (iCharacter == '\\')
           {
             iCharacter = getc(inputstream);
             if (iCharacter == EOF)
             {
               fprintf(stderr, "script ends badly: unterminated test, and backslash at line %d", iLineMark);
               exit(2);
             }
           }

           sprintf(sText, "%s%c", sText, iCharacter);
           iTextLength++;
           iCharacter = getc(inputstream);
           if (iCharacter == '\n') 
            { iLineCount++; }
           iCharacterCount++;
         }
         
         if (iCharacter == EOF)
         {
           fprintf(stderr, "unterminated test '<>' at line %d \n", iLineCount);
           exit(2);
         }

         if (iTextLength >= MAXARGUMENTLENGTH)
         {
           fprintf(stderr, "the test '<>' at line %d \n", iLineCount);
           fprintf(stderr, "is too long. The maximum is %d characters \n", MAXARGUMENTLENGTH);
           exit(2);
         }

         if (iCharacter == '>')
         {
           if (strlen(instruction->argument1) == 0)
           {
             instruction->command = TESTBEGINS;
             strcpy(instruction->argument1, sText);
             
             program.size++;
             instruction++;
           }
           else
           {
             fprintf(stderr, "The test '<>' at line %d already has an argument \n");
             fprintf(stderr, " \n");
             exit(2);
           }
         } 
         else
         { 
           fprintf(stderr, "error parsing test at line %d. \n", iLineCount); exit(2); 
           fprintf(stderr, "this error indicates a bug in the code 'compiler.c' \n");
           exit(2); 
         }
         break;
       /*-----------------------------------------*/ 
       case '[': 
         switch(instruction->command)
         {
           case UNDEFINED:
             break;
           default:
             fprintf(stderr, "Line %d: syntax error before '[' character. \n", iLineCount);
             fprintf(stderr, "(missing semi-colon?)\n");
             exit(2);
         }
         iLineMark = iLineCount;
         strcpy(sText, "");
         iTextLength = 0;
         iCharacter = getc(inputstream);
         iCharacterCount++;
         if (iCharacter == EOF)
         {
           fprintf(stderr, "script ends badly \n");
           exit(2);
         }

         if (iCharacter == ']')
         {
           fprintf(stderr, "empty test '[]' at line %d \n", iLineMark);
           exit(2);
         }

         while ((iCharacter != EOF) && (iCharacter != ']') && (iTextLength < MAXARGUMENTLENGTH))
         {
           /* handle the escape sequence */
           if (iCharacter == '\\')
           {
             iCharacter = getc(inputstream);
             if (iCharacter == EOF)
             {
               fprintf(stderr, "script ends badly: unterminated test, and backslash at line %d", iLineMark);
               exit(2);
             }
           }

           sprintf(sText, "%s%c", sText, iCharacter);
           iTextLength++;
           iCharacter = getc(inputstream);
           if (iCharacter == '\n') 
            { iLineCount++; }
           iCharacterCount++;
         }
         
         if (iCharacter == EOF)
         {
           fprintf(stderr, "unterminated test '[]' at line %d \n", iLineCount);
           exit(2);
         }

         if (iTextLength >= MAXARGUMENTLENGTH)
         {
           fprintf(stderr, "the test '[]' at line %d \n", iLineCount);
           fprintf(stderr, "is too long. The maximum is %d characters \n", MAXARGUMENTLENGTH);
           exit(2);
         }

         if (iCharacter == ']')
         {
           if (strlen(instruction->argument1) == 0)
           {
             instruction->command = TESTCLASS;
             strcpy(instruction->argument1, sText); 
             program.size++;
             instruction++;
           }
           else
           {
             fprintf(stderr, "The test '[]' at line %d already has an argument \n");
             fprintf(stderr, " \n");
             exit(2);
           }
         } 
         else
         { 
           fprintf(stderr, "error parsing test at line %d. \n", iLineCount); exit(2); 
           fprintf(stderr, "this error indicates a bug in the code 'compiler.c' \n");
           exit(2); 
         }
         break;
       /*-----------------------------------------*/ 
       case '/':
         switch(instruction->command)
         {
           case UNDEFINED:
             break;
           default:
             fprintf(stderr, "Line %d: syntax error before '/' character. \n", iLineCount);
             fprintf(stderr, "(missing semi-colon?)\n");
             exit(2);
         }

         iLineMark = iLineCount;
         strcpy(sText, "");
         iTextLength = 0;
         iCharacter = getc(inputstream);
         iCharacterCount++;
         if (iCharacter == EOF)
         {
           fprintf(stderr, "The '/' at line %d seems misplaced \n", iLineMark);
           exit(2);
         }

         if (iCharacter == '/')
         {
           fprintf(stderr, "empty test (//) at line %d \n", iLineMark);
           exit(2);
         }

         while ((iCharacter != EOF) && (iCharacter != '/') && (iTextLength < MAXARGUMENTLENGTH))
         {
           /* handle the escape sequence */
           if (iCharacter == '\\')
           {
             iCharacter = getc(inputstream);
             if (iCharacter == EOF)
             {
               fprintf(stderr, "unterminated test, and backslash at line %d", iLineMark);
               exit(2);
             }
           }

           sprintf(sText, "%s%c", sText, iCharacter);
           iTextLength++;
           iCharacter = getc(inputstream);
           if (iCharacter == '\n') 
            { iLineCount++; }
           iCharacterCount++;
         }
         
         if (iCharacter == EOF)
         {
           fprintf(stderr, "unterminated test (//) at line %d \n", iLineCount);
           exit(2);
         }

         if (iTextLength >= MAXARGUMENTLENGTH)
         {
           fprintf(stderr, "the test (//) at line %d \n", iLineCount);
           fprintf(stderr, "is too long. The maximum is %d characters \n", MAXARGUMENTLENGTH);
           exit(2);
         }

         if (iCharacter == '/')
         {
           if (strlen(instruction->argument1) == 0)
           {
             instruction->command = TESTIS;
             strcpy(instruction->argument1, sText); 
             program.size++;
             instruction++;
           }
           else
           {
             fprintf(stderr, "The test (//) at line %d already has an argument \n",
                     iLineCount);
             fprintf(stderr, " \n");
             exit(2);
           }
         } 
         else
         { 
           fprintf(stderr, "error parsing test at line %d. \n", iLineCount); exit(2); 
           fprintf(stderr, "this error indicates a bug in the code 'compiler.c' \n");
           exit(2); 
         }
         break;
       /*-----------------------------------------*/ 
       case '\n':
         iLineCount++;
         break;          
       /*-----------------------------------------*/ 
       case '!': //negations only before test
         switch(instruction->command)
         {
           case UNDEFINED:
             if (instruction->isNegated == TRUE) { instruction->isNegated = FALSE; }
             else if (instruction->isNegated == FALSE) { instruction->isNegated = TRUE; }
             break;
           default:
             fprintf(stderr, "Line %d: syntax error before '!' character. \n", iLineCount);
             fprintf(stderr, "\n");
             exit(2);
         }
         break;
       /*-----------------------------------------*/ 
       case ';':
         switch (instruction->command)
         {
           case UNDEFINED:
             fprintf(stderr, "The semi-colon (;) at line %d seems misplaced. \n", iLineCount);
             exit(2);
           case ADD:
           case WHILE:
           case UNTIL:
             if (strlen(instruction->argument1) == 0)
             {
               fnCommandToString(sCommandName, instruction->command);
               fprintf(stderr, "The command %s requires an argument: line %d \n", sCommandName, iLineCount);
               exit(2);
             }  
           default:
             program.size++;
             instruction++;
         } // switch
         break;
       /*-----------------------------------------*/ 
       case '{':
         // assign jumps
         if (instruction->command != UNDEFINED)
         {  
           fprintf(stderr, "Line %d: syntax error before '{' \n", iLineCount);
           exit(2); 
         }  
         instruction->command = OPENBRACE;
         iOpenBraceCount++;
         if (program.size == 0)
         { 
           fprintf(stderr, "A script cannot start with '{' \n");
           exit(2);
         }  

         instruction--;
         switch (instruction->command)
         {
           case TESTIS:
           case TESTBEGINS:
           case TESTCLASS:
             if (instruction->isNegated)
               { instruction->falseJump = program.size; }
             else
               { instruction->trueJump = program.size; }

             if (program.size == 1)
             {
               *pBraceStackPointer = program.size;
               pBraceStackPointer++;
               program.size++;
               instruction = &program.instructionSet[program.size];
               break;
             }  

             instruction--;
             iTestPointer = program.size - 1;
             while ((instruction->command == TESTIS) || (instruction->command == TESTBEGINS) ||
                    (instruction->command == TESTCLASS))
             {
               if (instruction->isNegated)
               {
                 instruction->falseJump = program.size;  
                 instruction->trueJump = iTestPointer;   
               } 
               else
               {
                 instruction->falseJump = iTestPointer;
                 instruction->trueJump = program.size;
               }
               iTestPointer--;
               if (iTestPointer < 0) { break; }
               instruction--;
             } //-- while

            
             /* load the brace stack for calculated jumps */ 
             *pBraceStackPointer = program.size;
             pBraceStackPointer++;
             program.size++;
             instruction = &program.instructionSet[program.size];

             break;
           default:
             fprintf(stderr, "The '{' character at line %d is not preceded by a test \n", iLineCount);
             exit(2); 
             break;
         } //-- switch    
         break;
       /*-----------------------------------------*/ 
       case '}':
         iCloseBraceCount++;
         if (iCloseBraceCount > iOpenBraceCount)
         {
           fprintf(stderr, "The '}' character at line %d seems misplaced. \n", iLineCount);
           fprintf(stderr, "The are more close braces than open braces \n", iLineCount);
           exit(2); 
         }

         if (instruction->command != UNDEFINED)
         {  
           fnPrintInstruction(*instruction);
           fprintf(stderr, "The '}' character at line %d seems misplaced. \n", iLineCount);
           exit(2); 
         }  
         instruction->command = CLOSEBRACE;
         /* set the jumps for the test of the current brace pair, using the brace stack
          * to find the corresponding open brace */
         pBraceStackPointer--;
         instruction = &program.instructionSet[*pBraceStackPointer - 1];
         if (instruction->isNegated)
         { 
           instruction->trueJump = program.size;
           //instruction->trueJump = *pBraceStackPointer;
         }
         else  
         {  
           //instruction->falseJump = *pBraceStackPointer;
           instruction->falseJump = program.size;
         }  
         program.size++;
         instruction = &program.instructionSet[program.size];
         break;

       /*-----------------------------------------*/ 
       default:
         strcpy(sText, "");
         if (!islower(iCharacter))
         {
           fprintf(stderr, "line %d: illegal character '%c' \n",
                   iLineCount, iCharacter);
           fprintf(stderr, "  this character may only occur between quotes");
           fprintf(stderr, "  or within tests.");
           exit(2);
         }

         while (islower(iCharacter) && (strlen(sText) < MAXARGUMENTLENGTH))
         {
           sprintf(sText, "%s%c", sText, iCharacter);
           iCharacter = getc(inputstream);
           iCharacterCount++;
         } //-- while

         if (strlen(sText) >= MAXARGUMENTLENGTH)
         {
           fprintf(stderr, "unrecognized command %s, line %d", sText, iLineCount);
           exit(2);
         }

         if (iCharacter == EOF)
         {
           fprintf(stderr, "script ends badly");
           exit(2);
         }

         iCommand = -1;
         iCommand = fnCommandFromString(sText);
         if (iCommand == UNKNOWN)
         {
           fprintf(stderr, "line %d: unrecognized command '%s'",
                   iLineCount, sText);
           exit(2);
         }

         if (instruction->command != UNDEFINED)
         {
           fprintf(stderr, "line %d: syntax error before command '%s'",
                   iLineCount, sText);
           exit(2);
         }

         instruction->command = iCommand;
         /* process the character currently in iCharacter */
         continue;
         /* fnPrintInstruction(*instruction); */

     } //-- switch		 


     iCharacter = getc(inputstream);
     iCharacterCount++;

     int bDebug = 0;
     if (bDebug)
     {
       printf("current char=%c \n", iCharacter);
       fnPrintProgram(&program);
     }

   } //-- while	   

   if (iOpenBraceCount != iCloseBraceCount)
   {
     printf("error: unbalanced braces: \n", iLineCount);
     printf("open braces=%d, ", iOpenBraceCount);
     printf("close braces=%d \n", iCloseBraceCount);
   }

   if (instruction->command != UNDEFINED)
   {
     fnCommandToString(sText, instruction->command);
     fprintf(stderr, "line %d: unfinished command '%s'.",
             iLineCount, sText);
     exit(2);
   }

   printf("------------------- \n", iLineCount);
   printf("     Lines parsed: %d \n", iLineCount);
   printf("Characters parsed: %d \n\n", iCharacterCount);
   printf("--Program Listing-- \n");

   fnPrintProgram(&program);

} //-- main
