Jump to content

Aereshaa's Stack-based Calculator.

- - - - -

This topic has been archived. This means that you cannot reply to this topic.
3 replies to this topic

#1
Aereshaa

Aereshaa

    Programming God

  • Members
  • PipPipPipPipPipPipPip
  • 790 posts
TITLE
Aereshaa's Stack-based Calculator.

CHANGE LOG
Sun Aug 31 18:14:58 EDT 2008
First release. Lots of features missing, no error checking.

Issues:
No error checking, therefore misuse of Z or z commands can cause memory leaks or segfaults.
There's no way to put string inside string; will fix this next release.
While loops are shoddy.

LANGUAGE: C
TESTED SYSTEMS: Ubuntu Linux
LICESNSE: Giftware
PRICE: Free


This is a calculator program I've been working on for a bit (like 5 hours over two days). It is fast, and interprets the code on a character, rather than line or word, basis. It uses a simple stack. This code is gift-ware: that is, you are free to do whatever. You may use, modify, redistribute, and generally hack it about in any way you like, and you do not have to give me anything in return. However, questions and comments would be nice!

/*       calcu.c    */
#include "stack.h"
#include "stdio.h"
#include "time.h"
#include "vlstr.h"

#define MD_NORM 'i'
#define MD_CHAR 'c'
#define MD_NPCR 'C'
#define MD_NUMR 'n'
#define MD_XPER 'x'
#define MD_COMM '\\'
#define MD_STRN 's'
#define MD_ESCP 'E'
#define ERR_BADMODE 1
#define ERR_NOCMD 2
int * var;
int * xvar;
int * sta;
int ** s;
char * str;

void execute(int ** st,const char * s);

int interpret(int ** s,const int cmd){
 int ret = 0;
 if(cmd == EOF)ret = 'x';
 if(var[1] == MD_NUMR){
  if(cmd >= '0' && cmd <= '9'){
   int x = cmd - '0';
   int y = st_pop(s);
   x += (y * 10);
   st_push(s,x);
   goto end;
  }else{
   var[1] = xvar[0];
  }
 }
 if(var[1] == MD_NORM){
  if(cmd == 'q')ret = 'x';
  else if(cmd >= '0' && cmd <= '9'){
   int x = cmd - '0';
   st_push(s,x);
   xvar[0] = var[1];
   var[1] = MD_NUMR;
  }
  else if(cmd == '+'){
   int a = st_pop(s);
   int b = st_pop(s);
   st_push(s, a + b);
  }
  else if(cmd == '-'){
   int b = st_pop(s);
   int a = st_pop(s);
   st_push(s, a - b);
  }
  else if(cmd == '*'){
   int a = st_pop(s);
   int b = st_pop(s);
   st_push(s, a * b);
  }
  else if(cmd == '/'){
   int b = st_pop(s);
   int a = st_pop(s);
   st_push(s, a / b);
  }
  else if(cmd == '%'){
   int a = st_pop(s);
   int b = st_pop(s);
   st_push(s, a % b);
  }
  else if(cmd == 'e'){
   int a = st_pop(s);
   int b = st_pop(s);
   st_push(s, a == b);
  }
  else if(cmd == '='){
   int a = st_pop(s);
   int b = st_pop(s);
   var[a] = b;
  }
  else if(cmd == '~'){
   int x = st_pop(s);
   st_push(s,var[x]);
  }
  else if(cmd == 'p'){
   int x = st_top(s);
   printf("%d\n",x);
  }
  else if(cmd == 'Z'){
   str = (char *) st_top(s);
   free(str);
   st_drop(s);
  }
  else if(cmd == 'z'){
   st_drop(s);
  }
  else if(cmd == 'd'){
   int a = st_top(s);
   st_push(s,a);
  }
  else if(cmd == 'f'){
   int a = st_pop(s);
   int b = st_pop(s);
   st_push(s,a);
   st_push(s,b);
  }
  else if(cmd == 'h'){
   if(var[3] == 0)printf("No error occured.\n");
   if(var[3] == ERR_BADMODE)printf("The context was invalid: '%c'\n", xvar[2]);
   if(var[3] == ERR_NOCMD)printf("'%c' is not a valid command.\n", xvar[2]);
   var[3] = 0;
  }
  else if(cmd == '\''){
   xvar[0] = var[1];
   var[1] = MD_CHAR;
  }
  else if(cmd == '{'){
   vl_start(&str);
   xvar[0] = var[1];
   var[1] = MD_STRN;
  }
  else if(cmd == 'P'){
   char * strng = (char *) st_top(s);
   puts(strng);
  }
  else if(cmd == 'x'){
   char * strng = (char *) st_pop(s);
   execute(s,strng);
   st_push(s,(int)strng);
  }
  else if(cmd == 'w'){
   int c = st_pop(s);
   switch(c){
    case 'q':
     puts("q: Quits the interpreter."); break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
     puts("0 to 9: Puts that number onto the stack.\nA sequence of numbers like 234 is used to put\nlarger numbers onto the stack."); break;
    case '+':
     puts("+: Performs addition.\nPops two numbers, Pushes their sum."); break;
    case '-':
     puts("-: Performs subtraction.\nA way to show how this works is that\n{21-pz} will print 1."); break;
    case '*':
     puts("*: Performs multiplication.\nPops two numbers, pushes their product."); break;
    case '/':
     puts("/: Performs division.\nThis is inaccurate due to the integer\nnature of the values.\noperand order is the same as subtraction."); break;
    case '%':
     puts("/: Performs modulus.\noperand order is the same as subtraction."); break;
    case '=':
     puts("=: Sets a variable to a value.\n{13'd=} sets variable 'd to 13."); break;
    case 'e':
     puts("e: Equality test.\nPops two numbers, tests their equality,\nand puts 1 on the stack if they are, 0 if not."); break;
    case '~':
     puts("~: Gets the value of a variable.\n{'d~} pushes the value of variable 'd ."); break;
    case 'p':
     puts("p: Prints the number at the top of a stack.\nDoes not pop any numbers.\nUse P for strings."); break;
    case 'd':
     puts("d: Duplicates the number at the top of the stack.\nDo not use on strings, or weird stuff wil happen."); break;
    case 'w':
     puts("w: Prints documentation for a command.\nPops one number (the commmand.)"); break;
    case 'f':
     puts("f: Reverses the order of the two numbers\nat the top of the stack.\nOkay for use on strings."); break;
    case 'h':
     puts("h: Prints an explanation of the last error.\nNot as useful as w."); break;
    case '\'':
     puts("\': Pushes the ascii value of following character."); break;
    case '{':
     puts("{: Begins a string, which continues until the matching }."); break;
    case 'P':
     puts("P: Prints a string.\nDoes not pop anything."); break;
    case 'z':
     puts("z: Pops a number off the stack.\nDo not use on strings, unless\nyou want a memory leak."); break;
    case 'Z':
     puts("Z: Pops a string and frees it.\nDo not use on integers, unless\nyou want a segfault."); break;
    case 'x':
     puts("x: Pops a string, executes it, and pushes it back on."); break;
    default:
     puts("   Either I haven't written documentation for\nthat command, or it doesn't exist."); break;
   }
  }
  else if(cmd == '?'){
   char * strng = (char *) st_pop(s);
   if(var[4])execute(s,strng);
   st_push(s,(int)strng);
  }
  else if(cmd == '@'){
   char * strng = (char *) st_pop(s);
   int a = st_top(s);
   while(var[4])execute(s,strng);
   st_push(s,(int)strng);
  }
  else if(cmd == '\n')xvar[1] = 1;
  else if(cmd == '\\'){
   xvar[0] = var[1];
   var[1] = MD_COMM;
  }
  else if(cmd == ' '); 
  else{
   putchar('?');
   xvar[2] = cmd;
   var[3] = ERR_NOCMD;
  }
  goto end;
 }
 if(var[1] == MD_CHAR){
  st_push(s,cmd);
  var[1] = xvar[0];
  goto end;
 }
 if(var[1] == MD_STRN){
  if(cmd == '}'){
   var[1] = xvar[0];
   xvar[1] = 1;
   st_push(s,(int)str);
  }else{
   vl_add(&str,cmd);
  }
  goto end;
 }
 if(var[1] == MD_COMM){
  if(cmd == '\n'){
   var[1] = xvar[0];
   xvar[1] = 1;
  }
  goto end;
 }
 putchar('?');// Bad Mode? WTF.
 var[3] = ERR_BADMODE;
 xvar[2] = var[1];
 var[1] = MD_NORM;
 end:
 return ret;
}

void execute(int ** st,const char * s){
 int i = 0;
 int len = strlen(s);
 while(i < len){
  interpret(st,s[i]);
  i++;
 }
}

int main(){
 s = &sta;
 st_make(s);
 st_test(s);
 var = malloc(256 * sizeof(int));
 xvar = malloc(16 * sizeof(int));
 var[0] = 256;        // size of var array
 var[1] = MD_NORM;   // interpretation mode
 var[2] = '\\';     // prompt
 var[3] = 0;       // error code
 var[4] = 1;      // conditionals
 xvar[1] = 1;    // for prompting
 int cmd = 0, ret = 0;
 while(ret != 'x'){
  if(var[2] && xvar[1]){
   putchar(var[2]);
   xvar[1] = 0;
  }
  cmd = getchar();
  ret = interpret(s,cmd);
 }
 st_test(s);
 st_dest(s);
}

/*     stack.h   */
#include "stdio.h"
#include "stdlib.h"

#define slen **s
#define stop *(*s + slen * sizeof(int))

void st_make(int ** s){
 *s = malloc(sizeof(int));
 slen = 1;
}

int  st_size(int ** s){
 return slen;
}

void st_push(int ** s, int i){
 *s = realloc(*s,sizeof(int) * (slen + 1));
 slen = slen + 1;
 stop = i;
}

int  st_top (int ** s){
 return stop;
}

void st_drop(int ** s){
 *s = realloc(*s,sizeof(int) * (slen - 1));
 slen = slen - 1;
}

int  st_pop (int ** s){
 int r = st_top(s);
 st_drop(s);
 return r;
}

void st_dest(int ** s){
 free(*s);
}

#undef slen
#undef stop

int st_test(int ** stack){
 st_push(stack, 1234);
 int ret = 1234 == st_top(stack);
 st_drop(stack);
 return ret;
}

/*   vlstr.h    */
#include "string.h"
#include "stdlib.h"

void vl_start(char ** s){
 *s = malloc(1);
 (*s)[0] = 0;
}

void vl_add(char ** s, char c){
 int len = strlen(*s);
 *s = realloc(*s,len + 2);
 (*s)[len] = c;
 (*s)[len + 1] = 0;
}

Attached Files



#2
John

John

    Writes binary right handed and hex left handed

  • Moderators
  • 6,321 posts
Nice. Thanks for sharing.

#3
MeTh0Dz

MeTh0Dz

    Writes binary right handed and hex left handed

  • Members
  • PipPipPipPipPipPipPipPipPip
  • 2,119 posts
Why the long if statement as opposed to switch-case?

#4
Aereshaa

Aereshaa

    Programming God

  • Members
  • PipPipPipPipPipPipPip
  • 790 posts
Because with switch statements I often forget the "break;"s.

Also, I have finished a new version, which corrects a lot of errors. Only calcu.c is different:
#include "stack.h"

#include "stdio.h"

#include "time.h"

#include "vlstr.h"


#define MD_NORM 'i'

#define MD_CHAR 'c'

#define MD_NPCR 'C'

#define MD_NUMR 'n'

#define MD_XPER 'x'

#define MD_COMM '\\'

#define MD_STRN 's'

#define MD_STRX 'S'

#define MD_ESCP 'E'

#define ERR_BADMODE 1

#define ERR_NOCMD 2

int * var;

int * xvar;

int * sta;

int ** s;

char * str;


void execute(int ** st,const char * s);


int interpret(int ** s,const int cmd){

 int ret = 0;

 if(cmd == EOF)ret = 'x';

 if(var[1] == MD_NUMR){

  if(cmd >= '0' && cmd <= '9'){

   int x = cmd - '0';

   int y = st_pop(s);

   x += (y * 10);

   st_push(s,x);

   goto end;

  }else{

   var[1] = xvar[0];

  }

 }

 if(var[1] == MD_NORM){

  if(cmd == 'q')ret = 'x';

  else if(cmd >= '0' && cmd <= '9'){

   int x = cmd - '0';

   st_push(s,x);

   xvar[0] = var[1];

   var[1] = MD_NUMR;

  }

  else if(cmd == '+'){

   int a = st_pop(s);

   int b = st_pop(s);

   st_push(s, a + b);

  }

  else if(cmd == '-'){

   int b = st_pop(s);

   int a = st_pop(s);

   st_push(s, a - b);

  }

  else if(cmd == '*'){

   int a = st_pop(s);

   int b = st_pop(s);

   st_push(s, a * b);

  }

  else if(cmd == '/'){

   int b = st_pop(s);

   int a = st_pop(s);

   st_push(s, a / b);

  }

  else if(cmd == '%'){

   int a = st_pop(s);

   int b = st_pop(s);

   st_push(s, a % b);

  }

  else if(cmd == 'e'){

   int a = st_pop(s);

   int b = st_pop(s);

   st_push(s, a == b);

  }

  else if(cmd == '='){

   int a = st_pop(s);

   int b = st_pop(s);

   var[a] = b;

  }

  else if(cmd == '~'){

   int x = st_pop(s);

   st_push(s,var[x]);

  }

  else if(cmd == 'p'){

   int x = st_top(s);

   printf("%d\n",x);

  }

  else if(cmd == 'Z'){

   str = (char *) st_top(s);

   free(str);

   st_drop(s);

  }

  else if(cmd == 'z'){

   st_drop(s);

  }

  else if(cmd == 'd'){

   int a = st_top(s);

   st_push(s,a);

  }

  else if(cmd == 'f'){

   int a = st_pop(s);

   int b = st_pop(s);

   st_push(s,a);

   st_push(s,b);

  }

  else if(cmd == 'h'){

   if(var[3] == 0)printf("No error occured.\n");

   if(var[3] == ERR_BADMODE)printf("The context was invalid: '%c'\n", xvar[2]);

   if(var[3] == ERR_NOCMD)printf("'%c' is not a valid command.\n", xvar[2]);

   var[3] = 0;

  }

  else if(cmd == '\''){

   xvar[0] = var[1];

   var[1] = MD_CHAR;

  }

  else if(cmd == '{'){

   vl_start(&str);

   xvar[0] = var[1];

   var[1] = MD_STRN;

  }

  else if(cmd == '['){

   vl_start(&str);

   xvar[0] = var[1];

   var[1] = MD_STRX;

  }

  else if(cmd == 'P'){

   char * strng = (char *) st_top(s);

   fputs(strng,stdout);

  }

  else if(cmd == 'R'){

   int c = getchar();

   vl_start(&str);

   while(c != '\n'){

    vl_add(&str,c);

    c = getchar();

   }

   st_push(s,(int)str);

  }

  else if(cmd == 'x'){

   char * strng = (char *) st_pop(s);

   execute(s,strng);

   st_push(s,(int)strng);

  }

  else if(cmd == 'w'){

   int c = st_pop(s);

   switch(c){

    case 'q':

     puts("q: Quits the interpreter."); break;

    case '0':

    case '1':

    case '2':

    case '3':

    case '4':

    case '5':

    case '6':

    case '7':

    case '8':

    case '9':

     puts("0 to 9: Puts that number onto the stack.\nA sequence of numbers like 234 is used to put\nlarger numbers onto the stack."); break;

    case '+':

     puts("+: Performs addition.\nPops two numbers, Pushes their sum."); break;

    case '-':

     puts("-: Performs subtraction.\nA way to show how this works is that\n{21-pz} will print 1."); break;

    case '*':

     puts("*: Performs multiplication.\nPops two numbers, pushes their product."); break;

    case '/':

     puts("/: Performs division.\nThis is inaccurate due to the integer\nnature of the values.\noperand order is the same as subtraction."); break;

    case '%':

     puts("/: Performs modulus.\noperand order is the same as subtraction."); break;

    case '=':

     puts("=: Sets a variable to a value.\n{13'd=} sets variable 'd to 13."); break;

    case 'e':

     puts("e: Equality test.\nPops two numbers, tests their equality,\nand puts 1 on the stack if they are, 0 if not."); break;

    case '~':

     puts("~: Gets the value of a variable.\n{'d~} pushes the value of variable 'd ."); break;

    case 'p':

     puts("p: Prints the number at the top of a stack.\nDoes not pop any numbers.\nUse P for strings."); break;

    case 'd':

     puts("d: Duplicates the number at the top of the stack.\nDo not use on strings, or weird stuff wil happen."); break;

    case 'w':

     puts("w: Prints documentation for a command.\nPops one number (the commmand.)"); break;

    case 'f':

     puts("f: Reverses the order of the two numbers\nat the top of the stack.\nOkay for use on strings."); break;

    case 'h':

     puts("h: Prints an explanation of the last error.\nNot as useful as w."); break;

    case '\'':

     puts("\': Pushes the ascii value of following character."); break;

    case '{':

     puts("{: Begins a string, which continues until the matching }."); break;

    case 'P':

     puts("P: Prints a string.\nDoes not pop anything."); break;

    case 'R':

     puts("R: Gets a line from standard input, and pushes it onto the stack."); break;

    case 'z':

     puts("z: Pops a number off the stack.\nDo not use on strings, unless\nyou want a memory leak."); break;

    case 'Z':

     puts("Z: Pops a string and frees it.\nDo not use on integers, unless\nyou want a segfault."); break;

    case 'x':

     puts("x: Pops a string, executes it, and pushes it back on."); break;

    case '?':

     puts("?: Pops the name of a variable, and executes a string if it is not 0."); break;

    case '@':

     puts("@: Pops the name of a variable, and executes a string until it is 0."); break;

    case '!':

     puts("!: Reverses the boolean value of the top number."); break;

    default:

     puts("   Either I haven't written documentation for\nthat command, or it doesn't exist."); break;

   }

  }

  else if(cmd == '?'){

   int a = st_pop(s);

   char * strng = (char *) st_pop(s);

   if(var[a])execute(s,strng);

   st_push(s,(int)strng);

  }

  else if(cmd == '@'){

   int a = st_pop(s);

   char * strng = (char *) st_pop(s);

   while(var[a])execute(s,strng);

   st_push(s,(int)strng);

  }

  else if(cmd == '!'){

   int a = st_pop(s);

   st_push(s,!a);

  }

  else if(cmd == '\n')xvar[1] = 1;

  else if(cmd == '\\'){

   xvar[0] = var[1];

   var[1] = MD_COMM;

  }

  else if(cmd == ' '); 

  else{

   putchar('?');

   xvar[2] = cmd;

   var[3] = ERR_NOCMD;

  }

  goto end;

 }

 if(var[1] == MD_CHAR){

  st_push(s,cmd);

  var[1] = xvar[0];

  goto end;

 }

 if(var[1] == MD_STRN){

  if(cmd == '}'){

   var[1] = xvar[0];

   xvar[1] = 1;

   st_push(s,(int)str);

  }else{

   if(cmd == '\\'){

    var[1] = MD_ESCP;

    goto end;

   }

   vl_add(&str,cmd);

  }

  goto end;

 }

 if(var[1] == MD_STRX){

  if(cmd == ']'){

   var[1] = xvar[0];

   xvar[1] = 1;

   st_push(s,(int)str);

  }else if(cmd == '('){

   vl_add(&str,'[');

  }else if(cmd == ')'){

   vl_add(&str,')');

  }else{

   vl_add(&str,cmd);

  }

  goto end;

 }

 if(var[1] == MD_ESCP){

  if(cmd == 'n')

   vl_add(&str,'\n');

  else if(cmd == 'a')

   vl_add(&str,'\a');

  else if(cmd == 't')

   vl_add(&str,'\t');

  else if(cmd == 'b')

   vl_add(&str,'\b');

  else if(cmd == 'r')

   vl_add(&str,'\r');

  else vl_add(&str,cmd);

  var[1] = MD_STRN;

  goto end;

 }

 if(var[1] == MD_COMM){

  if(cmd == '\n'){

   var[1] = xvar[0];

   xvar[1] = 1;

  }

  goto end;

 }

 putchar('?');// Bad Mode? WTF.

 var[3] = ERR_BADMODE;

 xvar[2] = var[1];

 var[1] = MD_NORM;

 end:

 return ret;

}


void execute(int ** st,const char * s){

 int i = 0;

 int len = strlen(s);

 while(i < len){

  interpret(st,s[i]);

  i++;

 }

}


int main(int argc,char ** argv){

 FILE * f;

 if(argc > 1){

  f = fopen(argv[1],"r");

 }else{

  f = stdin;

 }

 s = &sta;

 st_make(s);

 st_test(s);

 var = malloc(256 * sizeof(int));

 xvar = malloc(16 * sizeof(int));

 var[0] = 256;        // size of var array

 var[1] = MD_NORM;   // interpretation mode

 var[2] = 0;        // prompt

 var[3] = 0;       // error code

 var[4] = 1;      // conditionals

 xvar[1] = 1;    // for prompting

 int cmd = 0, ret = 0;

 while(ret != 'x'){

  if(var[2] && xvar[1]){

   putchar(var[2]);

   xvar[1] = 0;

  }

  cmd = getc(f);

  ret = interpret(s,cmd);

 }

 st_test(s);

 printf("\n");

 st_dest(s);

}
\Hello world in calcu

{Hello, World!\n}PZq
So, yesterday I didn't feel non-lazy enough to explain the syntax, so I'll do that now. Consider the following code fragment:
[d*p]'q=
This is the equivalent of a function declaration. It makes a string, using the non-\escaping form [], and stores it in the variable 'q. The string in question is in fact the series of commands for squaring the top of the stack, and printing it. Thus, one may now get the square of 5 by the commands:
'q~xz
So, you use the = command for storing stuff in variables, and the ~ command for getting it out. Also, the z command you just saw drops the top of the stack.
So, next are the I/O commands.

/cat in calcu

1'a=[RPZ]'a@

q

This is a stdin|stdout program, which outputs its input.
The R command reads a line into a string, the P command prints it, and the Z command frees it. Pretty simple, huh?
So, here's a more complicated program, one which allows calcu to act as a shell, sort of:

{This is calcu as a shell, coded entirely in calcu.\n}PZ

{The q command doesn't work because of a bug,\nso just set 'i to 0 to exit\n}PZ

1'i=

[{\\}PZRxZ]'i@

q

By combining commands that you've seen before, this program displays the prompt (a backslash), reads a line, and executes it.
[S]Questions?[\S]No, wait, that sounds stupid.

Edited by Aereshaa, 01 September 2008 - 06:31 PM.