

Show HN: Calc, a simple command-line calulator in C - vivekkannan
https://github.com/vivekannan/calc

======
SamReidHughes
Very cool. But let me warn you: Your method of code organization won't scale
to anything whose #include graph doesn't form a tree. You should learn how to
put your declarations into include-guarded header files and your
implementations into C files.

Also, abs has signature int abs(int x). I think you want to use fabs.

Edit: Also, it is quite peculiar to have the program print out null
characters. The output for ./calc '2' is four characters: {'2', '\0', '\0',
'\n'}. In places where you're using %c and printing '\0', you could use %s and
print "" instead.

~~~
maq123
Regarding code organization ... Any learning sources where some approaches are
being explained ? I am sysadmin and sometimes I find it hard to understand how
source files I just downloaded connected to each other. Thx

~~~
SamReidHughes
Any book on C. The early chapters of an online C tutorial.

~~~
dagw
I just checked the first 5 C tutorials the showed up in Google and non of them
talked about code organization in anything but the most superficial sense.

~~~
kevinnk
Try
[http://www.tutorialspoint.com/cprogramming/c_header_files.ht...](http://www.tutorialspoint.com/cprogramming/c_header_files.htm)

------
pwg
How is this different from the existing command-line calculator 'bc'?

    
    
        echo "3+4" | bc 
        7
    

Other than not needing equations to be piped to it?

Note also that bc allows defining functions (among other features), and is
also arbitrary precision (factorial of 200 below):

    
    
        $ echo "define f (x) { if (x <= 1) return (1); return (f(x-1) * x); }; f(200)"| bc
        78865786736479050355236321393218506229513597768717326329474253324435\
        94499634033429203042840119846239041772121389196388302576427902426371\
        05061926624952829931113462857270763317237396988943922445621451664240\
        25403329186413122742829485327752424240757390324032125740557956866022\
        60319041703240623517008587961789222227896237038973747200000000000000\
        00000000000000000000000000000000000

~~~
vivekkannan
Obviously, bc is much more capable than calc. However, calc was not created to
replace or even compete with bc or any other awesome command-line calculators.
calc is my way of learning a bunch of stuffs (shunting yard).

~~~
nemoniac
First learning tip:

.h files are not the place for definitions.

~~~
vivekkannan
duly noted. will change it ASAP.

------
jhallenworld
It's weird that most of the code is in the header files.. also do you check
for divide by zero?

Try to rewrite the whole thing as a single recursive function with two
arguments (rest of input string, precedence of left side). You could use
longjmp/setjmp to abort when there are errors.

~~~
vivekkannan
I checked division by zero initially, but then realised that 1/0 causes double
to represent inf. Thought that this could be useful(?) in some situations. For
example, atan(1/0) --> 1.57... instead of error.

~~~
jhallenworld
I was thinking to catch errors which could crash the program. Actually you
should install a signal handler for SIGFPE to take care of this.

------
ihuk
So being barely able to write C is now Hacker News-worthy?

------
userbinator
I assume you posted this looking for feedback, so here is my advices:

\- A 16-line main.c with a single function that does almost nothing is
annoying to say the least; this application is simple enough that I think it
really shouldn't be more than a single file.

\- You really don't need to use dynamic allocation for this. Try a simpler
algorithm like recursive-descent/precedence climbing, and tokenise+evaluate as
you go.

\- isSymbol()/getSymbol() should be merged into one function - you are doing
duplicate work here since once you know that it is one of those special
identifiers, you should be able to return the value immediately instead of
doing another set of comparisons.

This is an almost-complete C interpreter in roughly the same number of lines
of code as your calculator, but its structure is actually much simpler. It's
probably a bit far toward the side of terseness, but good to study in any
case:

[https://github.com/rswier/c4/blob/master/c4.c](https://github.com/rswier/c4/blob/master/c4.c)

For understanding precedence climbing/recursive descent, this is a good
article:

[https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm](https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)

One thing I see with a lot of beginners is a tendency to overcomplicate
solutions, writing far more code than necessary. It takes practice to avoid
this, but giving them some _extremely_ minimal implementations to study can
help them to better understand the essence of the problem and its solution; to
a beginner, it is often simpler than they seem to be at first glance.

------
mct
I've been using a perl-based calc for a number of years now. It started as a
one liner eval-ing expressions on the command line. My favorite feature is
that if symlinked as "hex", "oct", or "bin", it prints the output in
hexadecimal, octal, or binary. And, because the evaluation is done by perl,
hexadecimal, octal, and binary numbers can be entered using "0x", "0", and
"0b" prefixes. A minor improvement was the substitution of commas with
underscores, which perl allows in numbers for easier readability. Entering
"1,000,000" is much nicer on the eyes than carefully counting the number of
trailing zeros.

Two weeks ago, after becoming fed up with having to copy and paste results
from one line to the next, I wrapped it in a repl and added bc(1)-like support
for "." expanding to the last value. Rather embarrassingly, I started to
implement bc(1)-style variables, but then realized this was incredibly silly
as you can simply use perl variables directly. :-)

[https://github.com/mct/junkdrawer/commits/master/bin/calc](https://github.com/mct/junkdrawer/commits/master/bin/calc)

I fully support writing other calc implementations, especially in languages
such as C! It's a great exercise, and can become very complex very quickly.
bc(1) supports arbitrary precision (try: echo "scale=500; 4 _a(1)_ 42" | bc
-l), which is super awesome. In C, I've only implemented RPN calculators,
never infix, which would be fun to try.

~~~
jhallenworld
Actually strtod will convert 0x to hex.. It would be nice if it had an option
to ignore commas.

------
harunurhan
Several months ago, I was looking for something like this. Then I started
using bc but wasn't enough for me then I thought why not Python? It has tons
of math and statistics libraries/functions and easier than shell script with
bc.

So I started using Python by simply typing Python and do my stuff. It is fast
enough and super productive.

~~~
bnegreve
FWIW, I use awk, which is POSIX standard

    
    
        $ awk 'BEGIN{print sqrt(2)+4;}'; 
        5.41421
    

bc is also standard but for some arbitrary reason I don't like it.

------
enesunal
Suggestion: Move your business logic into .c files. Headers is there for other
reasons.

Take a look at, [http://embeddedgurus.com/barr-code/2010/11/what-belongs-
in-a...](http://embeddedgurus.com/barr-code/2010/11/what-belongs-in-a-c-h-
header-file/)

------
RhysU
Nice. Mucked about with Boost Spirit's examples once to do something similar
in one grammar-driven file: [http://agentzlerich.blogspot.com/2011/06/using-
boost-spirit-...](http://agentzlerich.blogspot.com/2011/06/using-boost-
spirit-21-to-evaluate.html)

Final production version with better error reporting is under "expr*" files at
[https://github.com/RhysU/suzerain/tree/master/suzerain](https://github.com/RhysU/suzerain/tree/master/suzerain)

------
jbert
Someone did a nice calculator hack a little while back, based upon an old
reddit comment of mine:

[http://tlrobinson.net/blog/2007/12/presenting-gccalc-a-
horri...](http://tlrobinson.net/blog/2007/12/presenting-gccalc-a-horribly-
awesome-abuse-of-gcc/)

[http://www.reddit.com/r/programming/comments/62v70/first_cla...](http://www.reddit.com/r/programming/comments/62v70/first_class_functions_in_c/c02nqcu)

------
agentultra
Nice!

Suggestion: you could probably use `asprintf` to malloc the input expression
string and use the Stopif variadic macro to return an error if the input takes
up more than the available memory. Unless of course your target platform
doesn't have the GNU libraries in which case you could write a test macro and
insert your own implementation using `vsnprintf`. :)

You might also want to check your input lengths for reasonable size before
allocating memory for them.

My first stop learning a new language is usually a project like this or a
guess-the-number game. I hope it's been helpful to you.

------
freefouran
Interesting, this is basically a very simple interpreter with a REPL. You
should look into interpreters if this is something you enjoyed creating, there
are many efficient and simple algorithms you could use to make this program a
lot simpler and even more advanced too. For instance, recursive descent
parsing, and the shunting yard algorithm.

------
powercf
Your value of pi is incorrect in the code, comments and readme. pi is
3.1415926... (and not 3.14151926...).

~~~
vivekkannan
changed it. thanks.

------
jhallenworld
For your amusement: a calculator written in BASIC:

[https://github.com/jhallen/joes-
sandbox/blob/master/snippets...](https://github.com/jhallen/joes-
sandbox/blob/master/snippets/parse.bas)

------
joshbaptiste
FYI, a Go version was announced not too long ago also:
[https://github.com/alfredxing/calc](https://github.com/alfredxing/calc)

------
leni536
I prefer explicit parenthesis for functions. That way you won't bump into much
trouble if you want to implement functions with multiple arguments.

------
deutronium
Please don't put the functions themselves in .h files, only the prototypes

------
sbrk
Been done, a long time ago.

[http://www.isthe.com/chongo/tech/comp/calc/index.html](http://www.isthe.com/chongo/tech/comp/calc/index.html)

