pemdas-calculator/calc.c

407 lines
12 KiB
C

#include "calc.h"
/*
Tesses Calculator a PEMDAS calculator (with mod)
Copyright (C) 2023 Mike Nolan
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/>.
*/
typedef struct node node_t;
typedef enum {
ADD,
SUB,
TIMES,
DIVIDE,
MOD,
NEG,
POW,
NUMBER,
PAREN
} node_type_t;
struct node {
node_type_t type;
union
{
struct {
node_t* left;
node_t* right;
} two;
node_t* node;
double number;
} data;
};
void node_free(node_t* n)
{
if(n == NULL)
{
return;
}
switch(n->type)
{
case ADD:
case SUB:
case TIMES:
case DIVIDE:
case MOD:
case POW:
node_free(n->data.two.left);
node_free(n->data.two.right);
break;
case PAREN:
case NEG:
node_free(n->data.node);
break;
default:
break;
}
free(n);
}
typedef struct lextokenlist lextokenlist_t;
typedef struct string string_t;
struct string
{
int cap;
int len;
char* text;
};
struct lextokenlist
{
string_t** tokens;
int cap;
int len;
int i;
};
string_t* string_create()
{
string_t* str = (string_t*)malloc(sizeof(string_t));
str->cap = 16;
str->len = 0;
str->text = (char*)calloc(str->cap + 1,sizeof(char));
return str;
}
void string_appendc(string_t* str,char c)
{
if(str->len >= str->cap)
{
str->cap = str->len+16;
str->text = (char*)realloc(str,str->cap + 1);
}
str->text[str->len++] = c;
str->text[str->len] = 0;
}
void string_free(string_t* str)
{
free(str->text);
free(str);
}
lextokenlist_t* lextokenlist_create()
{
lextokenlist_t* lt = (lextokenlist_t*)malloc(sizeof(lextokenlist_t));
lt->cap = 16;
lt->len = 0;
lt->i=0;
lt->tokens = (string_t**)calloc(lt->cap,sizeof(string_t*));
return lt;
}
void lextokenlist_flush(lextokenlist_t* ls,string_t** str)
{
string_t* str2 = *str;
if(str2->len > 0)
{
if(ls->len >= ls->cap)
{
ls->cap = ls->len+16;
ls->tokens = (string_t**)realloc(ls->tokens,ls->cap * sizeof(string_t*));
}
ls->tokens[ls->len++] = str2;
*str = string_create();
}
}
lextokenlist_t* calc_lex(const char* data)
{
string_t* str = string_create();
lextokenlist_t* ls=lextokenlist_create();
while(*data != '\0')
{
switch(*data)
{
case '+':
case '-':
case '*':
case '/':
case '%':
case '^':
case '(':
case ')':
lextokenlist_flush(ls,&str);
string_appendc(str,*data);
lextokenlist_flush(ls,&str);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
string_appendc(str,*data);
break;
default:
lextokenlist_flush(ls,&str);
break;
}
data++;
}
lextokenlist_flush(ls,&str);
return ls;
}
node_t* calc_parse_expression(lextokenlist_t* tokens);
node_t* calc_parse_value(lextokenlist_t* tokens)
{
if(tokens->i>=tokens->len)
{
return NULL;
}
if(strncmp(tokens->tokens[tokens->i]->text,"-",(size_t)tokens->tokens[tokens->i]->text)==0)
{
tokens->i++;
if(tokens->i>=tokens->len)
{
return NULL;
}
node_t* n = (node_t*)calloc(1,sizeof(node_t));
n->data.node = calc_parse_expression(tokens);
n->type = NEG;
return n;
}
else
if(strncmp(tokens->tokens[tokens->i]->text,"(",(size_t)tokens->tokens[tokens->i]->text)==0)
{
tokens->i++;
if(tokens->i>=tokens->len)
{
return NULL;
}
node_t* n = (node_t*)calloc(1,sizeof(node_t));
n->data.node = calc_parse_expression(tokens);
n->type = PAREN;
if(tokens->i>=tokens->len)
{
node_free(n);
return NULL;
}
if(strncmp(tokens->tokens[tokens->i]->text,")",(size_t)tokens->tokens[tokens->i]->text)!=0)
{
node_free(n);
return NULL;
}
tokens->i++;
return n;
}
node_t* n = (node_t*)calloc(1,sizeof(node_t));
n->type = NUMBER;
n->data.number = strtod(tokens->tokens[tokens->i]->text,NULL);
tokens->i++;
return n;
}
node_t* calc_parse_pow(lextokenlist_t* tokens)
{
node_t* node = calc_parse_value(tokens);
if(node == NULL) return NULL;
while(tokens->i<tokens->len && (strncmp(tokens->tokens[tokens->i]->text,"^",(size_t)tokens->tokens[tokens->i]->text)==0))
{
if(tokens->i<tokens->len && strncmp(tokens->tokens[tokens->i]->text,"^",(size_t)tokens->tokens[tokens->i]->text)==0)
{
tokens->i++;
node_t* left = node;
node = (node_t*)calloc(1,sizeof(node_t));
node->type = POW;
node->data.two.left = left;
node->data.two.right = calc_parse_value(tokens);
if(node->data.two.right == NULL)
{
free(node->data.two.left);
free(node);
return NULL;
}
}
}
return node;
}
node_t* calc_parse_factor(lextokenlist_t* tokens)
{
node_t* node = calc_parse_pow(tokens);
if(node == NULL) return NULL;
while(tokens->i<tokens->len && (strncmp(tokens->tokens[tokens->i]->text,"*",(size_t)tokens->tokens[tokens->i]->text)==0 || strncmp(tokens->tokens[tokens->i]->text,"/",(size_t)tokens->tokens[tokens->i]->text) == 0 || strncmp(tokens->tokens[tokens->i]->text,"%",(size_t)tokens->tokens[tokens->i]->text) == 0))
{
if(tokens->i<tokens->len && strncmp(tokens->tokens[tokens->i]->text,"*",(size_t)tokens->tokens[tokens->i]->text)==0)
{
tokens->i++;
node_t* left = node;
node = (node_t*)calloc(1,sizeof(node_t));
node->type = TIMES;
node->data.two.left = left;
node->data.two.right = calc_parse_pow(tokens);
if(node->data.two.right == NULL)
{
free(node->data.two.left);
free(node);
return NULL;
}
}
if(tokens->i<tokens->len && strncmp(tokens->tokens[tokens->i]->text,"/",(size_t)tokens->tokens[tokens->i]->text)==0)
{
tokens->i++;
node_t* left = node;
node = (node_t*)calloc(1,sizeof(node_t));
node->type = DIVIDE;
node->data.two.left = left;
node->data.two.right = calc_parse_pow(tokens);
if(node->data.two.right == NULL)
{
free(node->data.two.left);
free(node);
return NULL;
}
}
if(tokens->i<tokens->len && strncmp(tokens->tokens[tokens->i]->text,"%",(size_t)tokens->tokens[tokens->i]->text)==0)
{
tokens->i++;
node_t* left = node;
node = (node_t*)calloc(1,sizeof(node_t));
node->type = MOD;
node->data.two.left = left;
node->data.two.right = calc_parse_pow(tokens);
if(node->data.two.right == NULL)
{
free(node->data.two.left);
free(node);
return NULL;
}
}
}
return node;
}
node_t* calc_parse_expression(lextokenlist_t* tokens)
{
node_t* node = calc_parse_factor(tokens);
if(node == NULL) return NULL;
while(tokens->i<tokens->len && (strncmp(tokens->tokens[tokens->i]->text,"+",(size_t)tokens->tokens[tokens->i]->text)==0 || strncmp(tokens->tokens[tokens->i]->text,"-",(size_t)tokens->tokens[tokens->i]->text) == 0))
{
if(tokens->i<tokens->len && strncmp(tokens->tokens[tokens->i]->text,"+",(size_t)tokens->tokens[tokens->i]->text)==0)
{
tokens->i++;
node_t* left = node;
node = (node_t*)calloc(1,sizeof(node_t));
node->type = ADD;
node->data.two.left = left;
node->data.two.right = calc_parse_factor(tokens);
if(node->data.two.right == NULL)
{
node_free(node);
return NULL;
}
}
if(tokens->i<tokens->len && strncmp(tokens->tokens[tokens->i]->text,"-",(size_t)tokens->tokens[tokens->i]->text)==0)
{
tokens->i++;
node_t* left = node;
node = (node_t*)calloc(1,sizeof(node_t));
node->type = SUB;
node->data.two.left = left;
node->data.two.right = calc_parse_factor(tokens);
if(node->data.two.right == NULL)
{
node_free(node);
return NULL;
}
}
}
return node;
}
double calc_execute_expression(node_t* n,bool* error);
double calc_execute_expression(node_t* n,bool* error)
{
if(n == NULL) return 0;
if(error != NULL && *error) return 0;
switch(n->type)
{
case ADD:
return calc_execute_expression(n->data.two.left,error) + calc_execute_expression(n->data.two.right,error);
case SUB:
return calc_execute_expression(n->data.two.left,error) - calc_execute_expression(n->data.two.right,error);
case TIMES:
return calc_execute_expression(n->data.two.left,error) * calc_execute_expression(n->data.two.right,error);
case DIVIDE:
{
double left=calc_execute_expression(n->data.two.left,error);
double right = calc_execute_expression(n->data.two.right,error);
if(right == 0){ if(error != NULL) *error = true; return 0;}
return left / right;
}
// if(n->data.two.right) { if(error != NULL) *error = true; return 0;}
case MOD:
return fmod(calc_execute_expression(n->data.two.left,error),calc_execute_expression(n->data.two.right,error));
case POW:
return pow(calc_execute_expression(n->data.two.left,error),calc_execute_expression(n->data.two.right,error));
case PAREN:
return calc_execute_expression(n->data.node,error);
case NEG:
return -calc_execute_expression(n->data.node,error);
case NUMBER:
return n->data.number;
}
}
double calc(const char* data,bool* error)
{
lextokenlist_t* tokens = calc_lex(data);
node_t* expr= calc_parse_expression(tokens);
double exprVal = calc_execute_expression(expr,error);
if(expr == NULL)
{
if(error != NULL)
*error=true;
}else
{
node_free(expr);
}
if(tokens != NULL)
{
int i;
for(i=0;i<tokens->len;i++)
{
string_free(tokens->tokens[i]);
}
free(tokens->tokens);
free(tokens);
}
return exprVal;
}