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.
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
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.
* A list of function signatures, data structures it provides, etc
* Any constant definition specific to the module
Another "feature" of separating the header file from the implementation is that it guards against recursive includes, as the commenter above suggested.
The header file should not have the includes that the implementation uses. It should have the minimal necessary to achieve its purposes as a header file.
It is leaking includes without any reason, there is no need for the user of given module to know what dependencies the implementation is using. Place to list dependencies is at the beginning of the source file of given module, and as far as I know this is the convention.
Main problem with dependencies in the headers is that if any header of your library include headers from the different libraries the user will have have to (1) have those headers, (2) probably configure their build to use them, even if they bring zero useful information for them.
They're not there for "no reason". For example, if I use a type defined in some external header, I definitely need to include that in the .h, so I might as well just include everything else.
Besides, in my mind it's the logical place to put it: the header file is almost like the "spec" or the "coversheet" of a .c. It includes the name, the authors, possibly a description of what it does, a list of functions and datatypes, so why not a list of dependencies as well?
The header role is to define the interface of the module, that's why I believe the leaner the header is the better. When I'm using a module I do not care what the implementation is using, the only thing I care about is what functions are exposed and what data structures are required by the functions. All other details are irrelevant. If any of the functions are using any types from the external header that are passed by value (for pointers forward declaration is the way to go), then that headers also become part of the interface and have to be #included in header.
For C, I'd say the biggest benefit is that you can absorb a clearer picture of how parts of your project are using other parts, because your .c files have to directly include the headers they use. It's not perfect, because you won't be perfectly disciplined and can't use pointer indirection for every single type, but it is often very useful if you can immediately see a short list of .c files that could possibly access the interface exposed by a particular header file.
Another reason is namespace pollution (if only for better autocomplete), and another is that you don't walk yourself into a situation where editing one header file suddenly gives you a zillion name lookup errors (because that's mildly annoying).
In C++, the biggest and most substantial benefit can be the effect on compile times.
>For C, I'd say the biggest benefit is that you can absorb a clearer picture of how parts of your project are using other parts, because your .c files have to directly include the headers they use.
It may just be that this is the specific way in which my mind works, but I find it easier to get a clearer picture of the file if I just read the .h. It would have a summary (in terms of comments and code, like function declarations) and a list of includes.
>it is often very useful if you can immediately see a short list of .c files that could possibly access the interface exposed by a particular header file.
If you wanted to see which files use the interface in that header file directly, you can scan your headers for ones that include it explicitly, instead of working backward from the header. And a similar thing with the autocomplete problem: only load the header files mentioned in thing.h (assuming you're editing thing.c).
The increased compilation time is a good argument. I don't know any good way to mitigate it, but all these three problems sound like engineering problems to me, rather than a problem intrinsic to this approach.
> If you wanted to see which files use the interface in that header file directly, you can scan your headers for ones that include it explicitly, instead of working backward from the header.
Your .c files that use the interface will often be including it implicitly though, projects very quickly drift in that direction.
He is referring to the lack of guards in your header files(#ifdef NAME, #define NAME, #endif). That will prevent the preprocessor to include the file twice so your compiler can be happy.
Writing your own calculator is a nice programming exercise (touches a lot of different problems and is still a small, manageable task), especially for languages you're not familiar with. Of course there's better ones for "production" use.
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).
That function syntax is a nice example of C-isms bleeding through. If I want to write some arithmetic, I want something more declarative and succinct than C.
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.
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.
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:
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.
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. :-)
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; 4a(1) 42" | bc -l), which is super awesome. In C, I've only implemented RPN calculators, never infix, which would be fun to try.
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.
Another option would be to use the language called "R". I'm fairly new to it but its pretty much a programming language for math. Lots of libraries. Great programmatic graphing capability (ggplot2). Easy to dump file data into it.
In my lab a lot of biologists run there data with it. I think something like 80% of the graphs they publish are created in it. Better then excel in my opinion.
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.
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.
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.