407 lines
12 KiB
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;
|
|
} |