Pixelate:Issue 14/Scripting

From Allegro Wiki

Jump to: navigation, search
Scripting
Original author: Ram Mehta
some mail
Website:
zip: Script.zip

Contents

Scripting

INTRODUCTION :

This is my first article with pixelate please spare any mistakes. I'll try to be as elaborate as possible. I'm by no means an expert in this field but I have learnt this the HARD way and am just trying to provide as easier route for learners than the one I took. Scripting is a vast and complex subject and we'll cover our ground slowly.

NEED FOR SCRIPTING :

Scripts are files which are loaded by your program code during its execution. They are loaded, compiled and executed during program execution. You have the freedom on designing their syntax and their rules. In my opinion, the most effective scripting systems are those which are extrememly specific and where the syntax closely resembles the task to be performed. The aim of this articles is to write a very simple but flexible and extendable Scripting Language setup.

That still doesn't explain the need for scripts. Suppose you wrote an engine just like Grand Theft Auto : Vice City. You have several files of code, you have a HUGE play area and like a hundred missions. Do you know what it would be like to recompile / relink your code everytime you made a small change to the mission you were trying to design !!?

Thats where scripts come in. Your game engine simply loads the scripts, reads and executes them while your game loads, and all changes can be easily tested as scripts can be easily changed. There is a downside to scripts too, they execute much slower than hardcoded parts of the game.

BEFORE WE BEGIN :

I expect that you're familiar with C++ classes and have a reasonably good understanding of pointers and linked-lists. It would be quite futile to read furthur without knowledge of pointers and linked-lists as they form the basis of the data structures we will discuss.

OUR SCRIPTING LANGUAGE:

The language we'll design through these articles will be fairly simple. Statements will be of the form : INSTRUCTION DATA

We will be covering, loops, conditionals and complex expressions, but the only variable type available will be integers.

Here's a sample program.


var a, b
exp a~10
inp b
if (a > (b-2))
  msg you're not very good at this are you ?
else
  msg bye bye
endif
prn a+(5*b)+2
end

Expression Parsing :

Its time to begin. Lets start by learning a little about expressions. An expression is simply a meaningful combination of numbers, variables and operators (+, - , /, * and ^ in our case).

Parsing means to seperate and read. Its quite difficult for a computer to perform arithmetic calulations the way we do. Why ? Here's an example that might help explain that.

Try to solve the following expression in your head :
(10*(2*5) + (15+5))

The first thing you probably did was randomly look for a bunch of braces, preferrably with small numbers and simple arithmetic. Subsequently proceeding outward. Kinda difficult for a computer to make a decision about where to start and how to proceed.

Anyway, the type of expression shown above is known as an 'INFIX' expression. Infix means that the operator, is in between the operands. If you're lost, I'll explain again.

Operators are the symbols +, -, *, / etc. they are the operations you often perform on numbers. These operations are known as BINARY OPERATIONS, because each of them needs 2 numbers to work on. Like, you always add TWO numbers, like 5+6 = 11. Whereas 5+ means nothing. I hope you're with me so far. Anyway, in the Infix method, we write the operator + between the two numbers 5 and 6. It would also be possible to represent, the numbers first and then the operator like

5 + 6 would be 5 6 +

This is known as the postfix form of an expression. The advantage of this form is that all braces can be eliminated. Here's another example:

Infix -> (10*(2*5) + (15+5)) = 120 Posrfix -> 10 2 5 * * 15 5 + +

Postfix expressions are arranged in the order of operations from left to right. The '+ +' at the end isn't a mistake. It means that the last + is carried out on the results of the preceding operations. You may have a hard time getting used to it but you'll get better as you read on.

Converting INFIX expressions to POSTFIX expressions is not always as easy as it may seem. And we have to write a program to do it. I'll cover this furthur down.

A postfix expression can easily be solved on a stack. Here's how.

Solving postfix Expressions :

Move through the postfix expression from left to right. Pushing any number or variable onto the stack. If an operator is encountered, pop 2 numbers from the stack, perform the operation and push the result back onto the stack. So moving step by step through the above expression:

 # represents an empty stack.
 
 Consider the expression :
 Infix -> (2*5)*10 + (15+5)
 Postfix -> 2 5 * 10 * 15 5 + +
 
 Step | Expression           | Stack
 -----------------------------------------
 1.   | 2 5 * 10 * 15 5 + +  |  #
 2.   | 5 * 10 * 15 5 + +    |  # 2
 3.   | * 10 * 15 5 + +      |  # 2 5
 4.   | 10 * 15 5 + +        |  # 10
 5.   | * 15 5 + +           |  # 10 10
 6.   | 15 5 + +             |  # 100
 7.   | 5 + +                |  # 100 15
 8.   | + +                  |  # 100 15 5
 9.   | +                    |  # 100 20
 10.  |                      |  # 120
 ------------------------------------------
 

Simple huh ? All you need to solve a postfix expression is a loop and a stack. Here are a few for you to try. Answers are given below the questions. Convert the expressions to postfix form:


(0)     5+(7*8)
(1)     (20/2)*(9-2)
(2)     (a + (b^c)/d)*e

Answers


(0)     7 8 * 5 +
(1)	20 2 / 9 2 - *
(2)	a b c ^ d / + e *

All expressions in our language will first be converted to the POSTFIX form and then, evaluated on a stack. Here's how to convert an INFIX expression to a POSTFIX expression.

Here is a simple algorithm that uses a stack to convert INFIX expressions to POSTFIX expressions. A complete list of algorithms and other information on expression parsing can be found at http://www.cis.temple.edu/~pwang/223-DS/Lecture/Expression.htm

The Scripting Engine:

Now we start coding our scripting language. These are the keywords/instructions/commands in our language.


var name1, name2     -> declares a variable
prn (expression)     -> outputs the RESULT of the expression
msg (data)           -> outputs the data without any change
exp (expression)     -> the preceding expression is evaluated on the stack
inp name1, name2     -> variable values are taken from the keyboard

We'll add more commands after we finish with these. To facilitate variables we'll need a 'VARIABLE TABLE' which really is like a table which stores the names of variables and their values. The table will be in the form of a linked-list as follows :


//class which represents a single variable
class VAR
{
  private:
   int value;			// value of the variable
   char name;			// name of the variable. Remember, I said single letter variables
  public:
   VAR *next;			//pointer for the linked list
   VAR(int v, char n) { value = v; name = n; next = NULL; }
   char givename() { return name; }
   int givevalue() { return value; }

  friend class VARTABLE;	// made friend to reduce some headaches. Not necessary, and definately poor code.
};

class VARTABLE
{
  private:
    VAR *varlist;               // the beginning of linked list
    VAR *search;                // pointer used to scan through linked-list
  public:
    VARTABLE() { varlist = NULL; search = NULL; }
    void new_var(int, char);	// add variable to table
    int return_val(char);	// return value of specified variable
    void change_val(int, char);	// change value of specified variable
    void destroy();		// destroy variable table
};

void VARTABLE :: destroy()
{
  search = varlist;
  VAR *v;
  while(search != NULL)
  {
    v = search->next;
    delete search;
    search = v;
  }
}

void VARTABLE :: new_var(int v, char n)
    {
      if(varlist == NULL) { varlist = new VAR(v, n); search = varlist; }
      else
      {
	search = varlist;
	while(search->next != NULL) search = search->next;
	search->next = new VAR(v, n);
	search = search->next;
      }
    }

int VARTABLE :: return_val(char n)
    {
      search = varlist;
      while(search != NULL && search->givename() != n)
	search = search->next;

      if(search == NULL)
       { cout << n << " Not Defined."; return(0); }
      return(search->givevalue());
    }

void VARTABLE :: change_val(int val, char n)
    {
      search = varlist;
      while(search != NULL && search->givename() != n)
	search = search->next;

      if(search == NULL)
       { cout << n << " Not Defined."; }
      search->value = val;
    }

Now, lets write a class to store instructions, again a linked-list, but this time, a doubly linked-list.


class INSTR
{
 public:
  OP_CODE op;			// the type OP_CODE will be an enum storing or instructions
  char data[40];                // this array will store the arguments given to the instruction
  INSTR *next, *prev;		// pointers to make a doubly-linked list.
  INSTR() { next = NULL; prev = NULL; }
};

Our SCRIPT class will simply make linked-list of INSTR's. The reason we are linking it both ways is because we plan to allow control statements like loops. At the end of the loop we'll have to move back to the beginning of the loop. Anyway, here's our SCRIPT class.


class SCRIPT
{
  private:
   INSTR *sc;			// beginning of list
   INSTR *cur;			// pointer used to move through list
   VARTABLE vartable;		// each script obviously has its OWN variable table
   STACK stk;			// each script has a stack to parse expressions
  public:
   SCRIPT() { sc = NULL; }
   int execute();               // execute the script
   void kill();			// destroy the linked-list

   //a few functions to add instructions overloaded with different types
   void add_instr(OP_CODE OP, char *DATA)
   {
    if(sc == NULL) { sc = new INSTR; cur = sc;}
    else {
       cur->next = new INSTR;
       cur->next->prev = cur;
       cur = cur->next;
    }
    cur->op = OP;
    strcpy(cur->data, DATA);
   }

   void add_instr(INSTR &inst)
   {
     if(sc == NULL) { sc = new INSTR; cur = sc;}
     else {
	cur->next = new INSTR;
	cur->next->prev = cur;
	cur = cur->next;
     }
     cur->op = inst.op;
     strcpy(cur->data, inst.data);
   }
};

void SCRIPT :: kill()
   {
     INSTR *inst;
     cur = sc;
     while(cur != NULL)
     {
       inst = cur->next;
       delete cur;
       cur = inst;
     }
     vartable.destroy();
   }

We'll write the execute function later. First lets finish with our stack. Our stack will be used to solve all kinds of expressions. It will be a stack which can store both numbers as well as variable. The way we implement this is very very loose but it'll work. You'd probably want to store the address of the variable rather than the its name but, we'll be storing the name.

This works fine because we have only one type of variable in our language. Extending this isn't particularly difficult but would take quite some extra code. This is not the best way to do this heck this is just plain bad. Damn straight. But, its simple and it works.

Read through the following code slowly and try to understand what we are doing. '-' is the a member of on the stack which is not a variable, i.e. its a constant.


// node in the stack. It has two parts, the interger value on the stack and the corresponding
// variable.
class NODE
{
 public:
  int value;		// value on stack
  char var;             // name of variable the value refers to
  NODE *prev;
  NODE()
  {
	prev = NULL;
        var = '-';      // by default, the value is a constant
  }
};


class STACK
{
  private:
    NODE *top;		// a stack similar to the one we implemented
    int status;
  public:
    STACK() { top = NULL; status = 1; }
    ~STACK();

    // a few overloaded functions to push stuff onto the stack.

    // function for constant.
    void push(int val)
    {
      NODE *n = new NODE;
      n->value = val;
      n->prev = top;
      top = n;
    }

    // function for variable and corresponding value
    void push(int val, char var)
    {
      NODE *n = new NODE;
      n->value = val;
      n->var = var;
      n->prev = top;
      top = n;
    }

    // pop function for only the value
    int pop()
    {
      NODE *n = top->prev;
      int val = top->value;
      delete(top);
      top = n;
      return(val);
    }

    // pop function including variable being popped off stack.
    int pop(char &c)
    {
      NODE *n = top->prev;
      int val = top->value;
      c = top->var;
      delete(top);
      top = n;
      return(val);
    }

    int check_status() { return (status); }
};

STACK :: ~STACK()
    {
      NODE *n;
      while(top != NULL)
      {
       n = top->prev;
       delete(top);
       top = n;
      }
    }

Our implementation is very loose but easy to follow. Our STACK class is not tied with our VARTABLE class in anyway and we therefore always need both the name and the corresponding value. If we pushed 10, 12, a, 2, b; where a = 23 and b = 5, this is what our stack would look like:


STACK
value     var
  5        b
  2        -
  23       a
  12       -
  10       -

Get the picture ?

We'll now start writing the part of our SCRIPT compiler that parses expressions. To begin with, we'll make a new class for a stack, this time through arrays. We'll use this stack ONLY for converting infix expressions to postfix ones. This is almost exactly the same as what we did above. This code is independant of the other classes. We'll also restrict our operators to single characters. This will lead to a slight problems between the assignment operator('=' in C++) and equals to operator('==' in C++). To fix this, our language will use the '~' symbol as the assignment operator and the '=' as the equals to operator. Oh yeah, and the '!' represents 'not equal to' operator ('!=' in C++).

Now, read through the following code VERY carefully and make sure you understand everything


// this is class for a stack implemented through an array of characters
// we'll be using this stack to store operators and braces from the
// the infix expression.
class OPSTACK
{
  public:
    char stk[80];
    int top;
    OPSTACK() { top = -1; }
    void push(char c) { stk[++top] = c; }
    char pop() { return(stk[top--]); }
    int empty() { if(top == -1) return(1); return(0); }
};

// this functions assigns a number as precedence to different operators.
// assignment operators and comparision operators are given LOWEST precedence
// this is because they are to be executed after both sides of the expression
// are completely parsed.
int precedence(char op)			// the argument is the operator
{
  switch(op)
  {
    case '^' : return (7);
    case '/' : return (6);
    case '*' : return (6);
    case '+' : return (5);
    case '-' : return (5);

    case '=' :
    case '>' :
    case '<' :
    case '~' :
    case '!' : return(1);
  }
  return(0);
}

// this function converts a string of an infix expression consisting of single character
// variable names, single character operators and numeric constants, to a string in the postfix
// form. It uses the algorithm discussed in the INFIX TO POSTFIX section above.
char* infix_to_postfix(char *infix)
{
  int ind = 0;
  char postfix[80], temp;
  OPSTACK op;
  for(int pos = 0;pos < (signed int)strlen(infix);pos++)
  {
    // check for a constant
    if(infix[pos] >= '0' && infix[pos] <= '9')
    {
     while(infix[pos] >= '0' && infix[pos] <= '9')
       postfix[ind++] = infix[pos++];
     pos--;
     postfix[ind++] = ' ';
    }

    // check for a variable name
    if( (infix[pos] >= 'a' && infix[pos] <= 'z') || (infix[pos] >= 'A' && infix[pos] <= 'Z'))
     postfix[ind++] = infix[pos];


    // this part checks for operators, and pushes or pops of the stack according to the
    // algorithm discussed above
    switch(infix[pos])
    {
      case '(' :  op.push('(');
		  break;
      case ')' :  while(!op.empty())
		  {
		     temp = op.pop();
		     if(temp == '(') break;
		     else postfix[ind++] = temp;
		  }
		  break;
      case '^' :
      case '/' :
      case '*' :
      case '+' :
      case '-' :
      case '=' :
      case '>' :
      case '<' :
      case '~' :
      case '!' : if(op.empty()) op.push (infix[pos]);
		 else
		 {
		   while(!op.empty())
		   {
		     temp = op.pop();
		     if(temp == '(')
		     {
		       op.push('(');
		       break;
		     }
		     if(precedence(temp) > precedence(infix[pos]))
		       postfix[ind++] = temp;
		     else
		     {
		       op.push(temp);
		       break;
		     }
		   }
		   op.push(infix[pos]);
		 }
		 break;
    }
   }
   while(!op.empty()) postfix[ind++] = op.pop();
   postfix[ind] = '\0';
   strcpy(infix, postfix);
   return(infix);
}

Now we write a function to SOLVE a postfix expression on a given stack using a given variable table to get information regarding variables. This is one of the most important parts of our script compiler. It is very very important that you understand this code. It relies on the classes STACK and VARTABLE. The code is not too complex.

The function takes 3 arguments. First, the infix form of the expression, then the stack and lastly the variable table. We'll immidiately convert the infix for to the postfix form using the function we defined above. We'll then move through the expression from left to right, pushing constants and variables onto the stack and performing corresponding operations when operators are encountered.


// 3 arguments like I said
void parse(char *exp, STACK &stk, VARTABLE &vtable)
{
  int num, arg1, arg2;
  char var;
  infix_to_postfix(exp);		// convert our infix expression to postfix form
  for(int pos = 0; pos < (signed int)strlen(exp); pos++)	// move through postfix form from
  {										// left to right
     num = 0;

     // numeric constant encountered
     if(exp[pos] >= '0' && exp[pos] <= '9')
     {
       while(exp[pos] >= '0' && exp[pos] <= '9')
       {
	 num *= 10;
	 num += exp[pos] - '0';
	 pos++;
       }

       // push on the stack
       stk.push(num);
     }

     // variable encountered
     if( (exp[pos] >= 'a' && exp[pos] <= 'z') || (exp[pos] >= 'A' && exp[pos] <= 'Z'))
     {
       // push onto stack
       stk.push(vtable.return_val(exp[pos]), exp[pos]);
     }

     // operator encountered
     // pop operands off the stack and perform operations
     // For operations like '/' and '-', its always arg2/arg1 and arg2-arg1 not arg1-arg2
     // after performing operation, simply push result back onto the stack
     switch(exp[pos])
     {
       // our arithmetic operators
       case '^': arg1 = stk.pop();
		 arg2 = stk.pop();
		 stk.push(arg2^arg1);
		 break;

       case '/': arg1 = stk.pop();
		 arg2 = stk.pop();
		 stk.push(arg2/arg1);
		 break;

       case '*': arg1 = stk.pop();
		 arg2 = stk.pop();
		 stk.push(arg2*arg1);
		 break;

       case '+': arg1 = stk.pop();
		 arg2 = stk.pop();
		 stk.push(arg2+arg1);
		 break;

       case '-': arg1 = stk.pop();
		 arg2 = stk.pop();
		 stk.push(arg2-arg1);
		 break;

       // the assignment operator.
       // the second variable represented by the second
       // argument is being assigned the value of the first one popped off the stack.
       case '~': arg1 = stk.pop();
		 arg2 = stk.pop(var);
		 vtable.change_val(arg1, var);
		 break;

       // comparsion operators.
       // After operation, they push TRUE or FALSE result of operation onto stack
       case '!': arg1 = stk.pop();
		 arg2 = stk.pop();
		 if(arg2 != arg1) stk.push (1);
		 else stk.push(0);
		 break;

       case '<': arg1 = stk.pop();
		 arg2 = stk.pop();
		 if(arg2 < arg1) stk.push (1);
		 else stk.push(0);
		 break;

       case '>': arg1 = stk.pop();
		 arg2 = stk.pop();
		 if(arg2 > arg1) stk.push (1);
		 else stk.push(0);
		 break;

       case '=': arg1 = stk.pop();
		 arg2 = stk.pop();
		 if(arg2 == arg1) stk.push (1);
		 else stk.push(0);
		 break;
     }
  }
}

We have now finished the most important parts of our compiler. All we have left now is defining our operations and telling our scripts what to do when each one is encountered. Look back at our intruction class, INSTR. One member of it is of type OP_CODE. Place the following code above the INSTR class. It is a list of our instructions with corresponding names.


enum OP_CODE
{
 var, prn, msg, input, exp, loop, endloop, brk, cont, ifstate, elseifstate, elsestate, endif, end, num_codes
};

char TOKEN[num_codes][30] = {
			      "var",
			      "prn",
			      "msg",
			      "inp",
			      "exp",
			      "loop",
			      "endloop",
			      "break",
			      "continue",
			      "if",
			      "elseif",
			      "else",
			      "endif",
			      "end"
			     };

Notice, that the order of operations in the enum OP_CODE is the same as their corresponding words in char TOKEN[][]. By doing this, the index of strings in TOKEN will match with the corresponding number of OP_CODE. Thus TOKEN[var] would be "var" and TOKEN[prn] would be "prn". To add a new keyword, we simply add it to the enum OP_CODE and to the same place in TOKEN.

Now, our programs will read textfiles, so all the data we read will be of the 'char' type. But, it would be rather inconvenient to continuously the match strings we read to strings of the instructions. We'll therefore assign a number to every instruction and match the string we read to that number. We'll now write a function to match the strings we read from the file to a corresponding number from the enum above.


OP_CODE match_code(char *str)
{
 OP_CODE op = end;
 for(int i = 0;i < num_codes;i++)
 {
   if(strcmp(str, TOKEN[i]) == 0) { op = (OP_CODE)i; break ;}
 }
 return(op);
}

Now, ALL we have left is writing the function int SCRIPT :: execute(); I'm going to break this function into parts.


int SCRIPT :: execute()
   {
     cur = sc;			// 'cur' is pointer used to browse through the linked list,
				// 'sc' is pointer to first instruction. Set our pointer to 'sc'.
     char buffer[80];		// just a buffer used for misc purposes
     int temp;			// a temporary variable


     while(cur->op != end)					// loop upto last instruction
     {
	 switch(cur->op)					// switch according to OP_CODE
	 {

The code that actually does something for each instruction comes now. This probably what you've been waiting for, for a LONG time. Anyway, each instruction will be a seperate case. The easiest will be the instruction 'msg' which just empties its argument onto the screen so :


	   // sample
	   // msg Welcome to the Rock...
	   case msg: cout << endl << cur->data;
		     cur = cur->next;
		     break;

Simple enough !?

Next we'll take the 'prn' instruction. The argument will first be evaluated on a stack and the result will be displayed. The function parse takes an infix expression string which is precisely what the argument of the instruction 'prn' is.


	   // sample
	   // prn (5+a)*10-2
	   case prn : parse(cur->data, stk, vartable);
		      cout << stk.pop();
		      cur = cur->next;
		      break;

The instruction exp is even simpler. Remember, all our instructions are the form:
INSTRUCTION DATA
what if all you wanted to do was assign the variable 'a', the value 10. What would be the INSTRUCTION part of your statement? We therefore use the exp OP_CODE, which simply evaluates the argument part as an expression on the stack. The above statement would now be:

exp a~10

Remember, '~' is our assignment operator.


	   // sample
	   // exp a~10
	   case exp : parse(cur->data, stk, vartable);
		      cur = cur->next;
		      break;

The next 2 instructions we'll handle are 'var' and 'inp'. Both take similar arguments. Example:
var a, b
declares 2 variables 'a' and 'b' and
inp a, b
gets their values from the keyboard.

the data part is of the form
v1, v2, v3 ...
so we'll just move through the data skipping all spaces and comma's, if we come across another character, we create a variable with that name or input a value for that variable respectively.


	   // sample
	   // var a, b
	   case var:
		   strcpy(buffer, cur->data);
		   for(i = 0;i < (signed int)strlen(buffer);i++)
		   {
			   // scan through the argument list skipping all spaces and commas
                     // is another character is encountered, it is a variable name
                     // create the corresponding variable in the VARTABLE
			   if( buffer[i] != ' ' && buffer[i] != ',')
				   vartable.new_var(0, buffer[i]);
		   }
		   cur = cur->next;
		   break;

	   // sample
	   // inp a, b
	   case input:
               strcpy(buffer, cur->data);
		   int j;
		   j = strlen(buffer);
		   for(i = 0;i < j;i++)
		   {
			if(buffer[i] != ' ' && buffer[i] != ',')
			{
			 cout << "\n:";
			 cin >> temp;
			 vartable.change_val(temp, buffer[i]);
			}
		   }
		   cur = cur->next;
		   break;

So now, all we have left is conditional statements and loops. The implementation is fairly simple and you'll probably get it if you read through the following code slowly. 'loop' is a 'while' style loop. 'break' and 'continue' perform the same function as in C and C++. Every 'if' must end with an 'endif' and every 'loop' with an 'endloop'.

Eg.


var a
inp a
if a > 5
	msg a is big
elseif a > 20
	msg a is VERY big
else
	msg a is small
endif

is VALID.

The variables thisif, if_flag and thisloop, loop_flag are used to keep track of nested loops and nested if's. I promise to comment this code soon.


	   case ifstate :
		// the if_flag variable is used to keep track of nested if's.
		// it tells us how deep we are.
		   if_flag++;
		// thisif keeps track of the current level
		   thisif = if_flag;

		// check the condition
		   parse(cur->data, stk, vartable);

		// if the condition is NOT satisfied
		// move down looking for CORRESPONDING 'else', 'elseif' or 'endif'
		// for every 'if' we encounter while looking, skip one 'endif'
		   if(!stk.pop())
		   {
			   while(1)
			   {
				   cur = cur->next;

				   if(cur->op == ifstate)
					   if_flag++;
				   if(cur->op == endif)
				   {
					   if_flag--;
					   if(if_flag < thisif)
						   break;
				   }

				   if(cur->op == elseifstate)
				   {
					   if(if_flag == thisif)
					   {
						   parse(cur->data, stk, vartable);
						   if(stk.pop()) break;
					   }
				   }

				   if(cur->op == elsestate)
				   {
					   if(if_flag == thisif)
						   break;
				   }
			   }
		   }
		   cur = cur->next;
		   break;

	// We'll use a similar setup for loops in order to keep track of nested loops.
	   case loop :
		   loop_flag++;
		   thisloop = loop_flag;
		   parse(cur->data, stk, vartable);
		   if(!stk.pop())
		   {
			   while(1)
			   {
				   cur = cur->next;

				   if(cur->op == loop)
					   loop_flag++;
				   if(cur->op == endloop)
				   {
					   loop_flag--;
					   if(loop_flag < thisloop)
						   break;
				   }
			   }
		   }
		   cur = cur->next;
		   break;

	// At the end of the loop move back to the top to keep the execution going.
	   case endloop:
		   loop_flag--;
		   i = 1;

		   while(1)
		   {
			   cur = cur->prev;
			   if(cur->op == endloop)
				   i++;
			   if(cur->op == loop)
			   {
				   i--;
				   if(!i)
					   break;
			   }
		   }
		   break;

	// In case of a 'break' just move down past the next endloop
	// fairly simple
	   case brk :
		   while(cur->prev->op != endloop)
			   cur = cur->next;
		   break;

	// In case of a 'continue' don't move past the endloop but move to it
	// this way control is transferred back to the loop.
	   case cont :
		   while(cur->op != endloop)
			   cur = cur->next;
		   break;

	   case elseifstate :
	   case elsestate :
		   while(cur->op != endif)
			   cur = cur->next;
		   break;

	   case endif: cur = cur->next;

	   case end: return(0);
	 }
     }
     return(0);
   }

Here is some code to read text and convert it to instructions. Its fairly simple, but I've placed some comments here and there to help you. To get input from a file, simply redirect 'cin' to that file stream and make changes to the 'QUIT' part of the code.


void main()
{
  char action[20];      // buffer to read operation
  char data[80];        // buffer to read arguments

  SCRIPT scr;           // the script to which data is being read
  INSTR inst;           // currently read instruction

  while(1)              // continues loop till QUIT is read
                        // if you redirect to file, change upto EOF
  {
    cin >> action;      // get the operation
    inst.op = match_code(action);  // convert it to a number

    if(strcmp(action,"QUIT") == 0) // if operation in input stream is
        break;                     // 'QUIT' then stop reading

    cin.getline(data, 40);      // get the argument list
    strcpy(inst.data, data);    // put it in the data part of the instruction
    scr.add_instr(inst);        // add the instruction to the script
  }

  cout << "\n\n";
  scr.execute();
  cout << "\n";
  getch();
  scr.kill();
}

Limitations:

  • Little or no error detection.
  • Unary minus is not allowed and not treated seperately.
  • Poor stack implementation.

Conclusion:

Ram Mehta

I hope this document helped somebody. I put in a decent amount of time into it. You can contact me at

Personal tools