Skip to content

Instantly share code, notes, and snippets.

@grissiom
Last active August 29, 2015 14:00
Show Gist options
  • Save grissiom/d0f3077140953595f2ae to your computer and use it in GitHub Desktop.
Save grissiom/d0f3077140953595f2ae to your computer and use it in GitHub Desktop.
Super simple calculator
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <limits.h>
enum RET {
RET_OK = 0,
RET_OVERFLOW = -1,
RET_NODIGIT = -2,
};
static char* _skip_space(char *str)
{
while (isspace(*str))
str++;
return str;
}
static enum RET _get_num(const char *str,
char **endptr,
long int *res)
{
long int cur_val;
cur_val = strtol(str, endptr, 0);
/* Check overflow. */
if (cur_val == LONG_MAX || cur_val == LONG_MIN)
{
printf("overflow at: %s\n", str);
return RET_OVERFLOW;
}
/* No digits found. */
if (*endptr == str)
{
printf("no digit at: %s\n", str);
return RET_NODIGIT;
}
*res = cur_val;
return RET_OK;
}
/* return the calculate status. 0 is OK.
*
* A block is a formula surrounded by ().
*/
static enum RET _calc_block(char *str,
char **endptr,
long int *res)
{
enum RET ret;
long int cur_val;
long int next_val;
str = _skip_space(str);
if (*str == '(')
{
/* We get here because the last node is +- or the first char is '('. So
* we need to proceed parsing as there may be other operations behind.
* In the a*(b+c)-d condition, the following operation is handled by
* the handler of *. */
ret = _calc_block(str + 1, endptr, &cur_val);
if (ret != RET_OK)
return ret;
goto _parse_next;
}
ret = _get_num(str, endptr, &cur_val);
if (ret != RET_OK)
{
return ret;
}
_parse_next:
str = _skip_space(*endptr);
switch (*str) {
case '+': {
/* a + b [+/-*] c is equal to a + (b [+/-*] c) */
ret = _calc_block(str+1, endptr, &next_val);
if (ret != 0)
{
return ret;
}
*res = cur_val + next_val;
return RET_OK;
}
break;
case '-': {
/* a - b [+/-*] c is equal to a + (b [-/+*] c) */
char *p;
for (p = str + 1; *p != '\0'; p++)
{
if (*p == '-')
*p = '+';
else if (*p == '+')
*p = '-';
}
ret = _calc_block(str+1, endptr, &next_val);
if (ret != 0)
{
return ret;
}
*res = cur_val - next_val;
return RET_OK;
}
break;
case '*': case '/': {
/* a * b [+-] c is not equal to a * (b [+-] c). So we have to handle it in line. */
/* Store current operator as we need to modify @str. */
char op = *str;
str = _skip_space(str + 1);
if (*str == '(')
ret = _calc_block(str + 1, endptr, &next_val);
else
ret = _get_num(str, endptr, &next_val);
if (ret != 0)
{
return ret;
}
if (op == '*')
cur_val *= next_val;
else
cur_val /= next_val;
/* Continue parsing. */
goto _parse_next;
}
break;
case '\0': case ')':
*res = cur_val;
*endptr = (char*)(str + 1);
return RET_OK;
default:
printf("unkown token at: %s\n", str);
break;
};
return -1;
}
int calc(char *str, long int *res)
{
char *ptr;
return _calc_block(str, &ptr, res);
}
#include <string.h>
int main(int argc, char * argv[])
{
int ret;
long int res = 0;
char *ostr = strdup(argv[1]);
ret = calc(argv[1], &res);
printf("calc %s: %ld\n", ostr, res);
return ret;
}
#!/bin/sh
set -e
gcc -Wall calc.c
test() {
res=$(echo "$1" | bc)
echo "calc $1: $res"
./a.out "$1"
}
test '1+1'
test '1-1'
test '1*1'
test '1+(1+4)'
test '1-1-4'
test '1-1*4'
test '1-1*4-9'
test '1-1*4-9+7'
test '(1+1)*7'
test '1+1*7'
test '8/1/2/2'
test '8*(1+3)+9'
test '1*(1+2)+9'
test '1*(1*2)*9'
test '8*(1+3)*9'
test '8+(1+3)*9'
test '8*(1+3)+9'
test '1+(1+2)+9'
test '1+1+(2+9)'
test '3*(1+2+9)'
test '(3+1)*2+9'
@grissiom
Copy link
Author

grissiom commented May 5, 2014

暂时不支持 ()。暂时只支持整形(strtof 的错误处理稍微麻烦些,就没有做)。

@grissiom
Copy link
Author

grissiom commented May 5, 2014

添加了十几行代码,支持 () 了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment