Combine basic programming concepts to create a command-line calculator.
As an aspiring C programmer, I want to implement a command-line calculator tool to assist with various calculations as described below.
- General usage:
./calc <command> <argument(s)>
. - Possible commands are:
add
: Add all the arguments together.sub
: Subtract all the arguments from each other.mul
: Multiply all the arguments together.div
: Divide all the arguments by each other.f
: Load instructions from a file, perform the calculations, and display the results on the screen.
sub 1 2 3 4 4
add 5 6 7
mul 23 44
NOTE: Each line's result will be used as the first parameter of the next line, and the result of the last line will be printed out on the screen.
- Use a multiple file structure with a proper
Makefile
. - The
Makefile
uses 3 different folder for better file organization.src
for source filesobj
for object filesbuild
for executables and build artifact- as always I've used
.exe
only for making theMakefile
work easier on windows too.
I would like to implement this program using command pattern, by first looking at the problem I could see the repetitive form of command, + 1 2 3
is same as f file.clc
as the first token is the command and the rest is argument(s).
Each command must be a structure with the following members, I would call it Command
type:
char*
for command name orcommand
for shortchar*
for command help orhelp
for shortinvoke
which is a function pointer to the definition of a function acceptingint argc, char* argv[]
and returningint
I would also define all the commands in a static
commands array that holds a couple of Command
s.
I have refactored all the codes to commands.h
, commands.c
and colors.h
.
I also have created various macros to make my programming easier but I haven't forget to add comments for them!
At this phase all the main operations are finished
I would like to load the text file line by line and create a fake argc
, argv
and run the process for each line, getting the result and
add it at the top of the next line parameter lists.
I might need to change the invoker functions implementations to return actual result and manage failures using exit
function instead.
To have access to pointer to commands
and command_size
I've added these arguments to invoke
field and also the the def_invoke_fn_as
macro.
At this moment the code is working and finished and I would like to take a break and then review the code and make some improvements
The result for the input below (input.clc
):
sub 1 2 3 4 4
add 5 6 7
mul 23 44
Is:
CLI Calculator
Processing calculations from 'input.clc':
line 1) 1.00 - 2.00 - 3.00 - 4.00 - 4.00 = -12.00
line 2) -12.00 + 5.00 + 6.00 + 7.00 = 6.00
line 3) 6.00 x 23.00 x 44.00 = 6072.00
result is made running make run_input
or ./build/calc.exe f input.clc
manually.
One of the most obvious parts which needs memory management is the f
command.
If you go through commits you could see at first to just make things work I went through a naive way of using malloc
/free
in each
iteration. Then soon I thought "well, that's not good, memory allocation/de-allocation is expensive", so I've decided to pre-allocate a space
in memory as a preserved one-time memory allocation and re-use it over and over and free it all only once I'm done with memory at the end.
Now I'm thinking even this can be removed, what if I have a 2 dimensional array for tokens per line, then I don't even need to allocate memory as I know I'm dealing with limited amount of tokens and characters per token in each line. The problem is that then I need to have fixed amount of temp_args all over the code definitions.
So I think with this problem I'm gonna keep the second approach.
UPDATE: The third method worked as expected, here is the breakdown of what happened in the code and why?
First of all, when you know why and how your program is written and must be used always apply limitations, never scared of telling your users there are limitations because there are!.
In the other hand, we're no longer living in Commodore 64
era, meaning we do have enough memory, trust me we do.
So, combined together, what we can do is to have a preserved array say for each line which can have 52 tokens that its first token is a dummy token we need to make things work
in a generic way (a place holder for the executable name) the second one is the command name (version
, add
, sub
, mul
, div
and f
) and the rest are the arguments (numbers or file name) to the command.
we can also limit the number of character per token as we already know the maximum number of characters might be for the executable path based on how user calls it, then version
command and the rest are 3 to 4 characters
plus null terminator character '\0'
.
So I could came up with this:
#define MAX_TOK_PER_LINE 52
#define MAX_TOK_SIZE 50
#define MAX_LINE_SIZE (MAX_TOK_PER_LINE * MAX_TOK_SIZE)
You might want to change them, go ahead, that's no problem.
I've also added some size checking with proper error messages as well and refactored them into two function macros (check_argc_size
and check_token_size
). They are pretty much straight forward and self-explanatory.
Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) License.
Please refer to LICENSE file.