Skip to content

Instantly share code, notes, and snippets.

@grissiom
Last active August 29, 2015 14:00
Show Gist options
  • Select an option

  • Save grissiom/d0f3077140953595f2ae to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
Author

grissiom commented May 5, 2014

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

@grissiom
Copy link
Copy Markdown
Author

grissiom commented May 5, 2014

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

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