File: calculator.c

package info (click to toggle)
cmocka 2.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,624 kB
  • sloc: ansic: 13,134; xml: 226; makefile: 23
file content (398 lines) | stat: -rw-r--r-- 12,181 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/*
 * Copyright 2008 Google Inc.
 * Copyright 2025 Andreas Schneider <asn@cryptomilk.org>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file calculator.c
 * @brief A simple calculator demonstrating the cmocka testing library.
 *
 * This file implements a command-line calculator that performs basic arithmetic
 * operations (addition, subtraction, multiplication, division) on integers.
 * It demonstrates how to structure code for testing with cmocka by using
 * function pointers, modular design, and conditional compilation for test
 * hooks.
 *
 * Example usage:
 *   ./calculator 10 + 5 - 3
 *   Output:
 *     10
 *       + 5 = 15
 *       - 3 = 12
 *     = 12
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef UNIT_TESTING

/**
 * When built for unit testing, redirect printf to a mock function so tests
 * can verify output.
 */
#ifdef printf
#undef printf
#endif
extern int example_test_printf(const char *format, ...);
#define printf example_test_printf

/**
 * When built for unit testing, redirect fprintf to a mock function so tests
 * can verify error messages.
 */
#ifdef fprintf
#undef fprintf
#endif
#define fprintf example_test_fprintf
extern int example_test_fprintf(FILE *const file, const char *format, ...);

/**
 * Redirect assert to mock_assert() so assertions can be caught by cmocka.
 */
#ifdef assert
#undef assert
#endif
#define assert(expression) \
    mock_assert((int)(expression), #expression, __FILE__, __LINE__)
void mock_assert(const int result,
                 const char *expression,
                 const char *file,
                 const int line);

/**
 * Rename main to example_main so the test suite can define its own main.
 */
int example_main(int argc, char *argv[]);
#define main example_main

/**
 * Expose static functions to tests by removing the static keyword.
 */
#define static

#endif /* UNIT_TESTING */

/**
 * @typedef BinaryOperator
 * @brief Function pointer type for binary arithmetic operations.
 *
 * @param a First operand
 * @param b Second operand
 * @return Result of the operation
 */
typedef int (*BinaryOperator)(int a, int b);

/**
 * @struct OperatorFunction
 * @brief Maps operator symbols (e.g., "+", "-") to their implementation
 * functions.
 */
struct OperatorFunction {
    const char *operator;    /**< Operator symbol (e.g., "+", "-") */
    BinaryOperator function; /**< Function implementing the operation */
};

/* Function declarations */
BinaryOperator find_operator_function_by_string(
    const size_t number_of_operator_functions,
    const struct OperatorFunction *const operator_functions,
    const char *const operator_string);

int perform_operation(int number_of_arguments,
                      const char *const *arguments,
                      const size_t number_of_operator_functions,
                      const struct OperatorFunction *const operator_functions,
                      size_t *const number_of_intermediate_values,
                      int **const intermediate_values,
                      int *const error_occurred);

static int add(int a, int b);
static int subtract(int a, int b);
static int multiply(int a, int b);
static int divide(int a, int b);

/**
 * @brief Lookup table mapping operator symbols to functions.
 */
static struct OperatorFunction operator_function_map[] = {
    {"+", add},
    {"-", subtract},
    {"*", multiply},
    {"/", divide},
};

/**
 * @brief Adds two integers.
 *
 * @param a First operand
 * @param b Second operand
 * @return Sum of a and b
 */
static int add(int a, int b)
{
    return a + b;
}

/**
 * @brief Subtracts one integer from another.
 *
 * @param a First operand (minuend)
 * @param b Second operand (subtrahend)
 * @return Difference (a - b)
 */
static int subtract(int a, int b)
{
    return a - b;
}

/**
 * @brief Multiplies two integers.
 *
 * @param a First operand
 * @param b Second operand
 * @return Product of a and b
 */
static int multiply(int a, int b)
{
    return a * b;
}

/**
 * @brief Divides one integer by another.
 *
 * @param a Dividend
 * @param b Divisor
 * @return Quotient (a / b)
 *
 * @note Asserts if b is zero to prevent division by zero.
 */
static int divide(int a, int b)
{
    assert(b); /* Check for divide by zero */
    return a / b;
}

/**
 * @brief Finds the function associated with an operator symbol.
 *
 * Searches through the operator_functions array to find a function matching
 * the specified operator string.
 *
 * @param number_of_operator_functions Size of the operator_functions array
 * @param operator_functions Array of OperatorFunction structures to search
 * @param operator_string Operator symbol to find (e.g., "+", "-")
 * @return Function pointer if found, NULL otherwise
 *
 * @pre operator_functions must be non-NULL if number_of_operator_functions > 0
 * @pre operator_string must be non-NULL
 */
BinaryOperator find_operator_function_by_string(
    const size_t number_of_operator_functions,
    const struct OperatorFunction *const operator_functions,
    const char *const operator_string)
{
    size_t i;

    assert(!number_of_operator_functions || operator_functions);
    assert(operator_string != NULL);

    for (i = 0; i < number_of_operator_functions; i++) {
        const struct OperatorFunction *const operator_function =
            &operator_functions[i];
        if (strcmp(operator_function->operator, operator_string) == 0) {
            return operator_function->function;
        }
    }
    return NULL;
}

/**
 * @brief Performs a sequence of binary arithmetic operations.
 *
 * Evaluates an expression given as an array of strings in the format:
 * number operator number operator number ...
 * Operations are performed left-to-right with no operator precedence.
 *
 * Example: ["10", "+", "5", "*", "2"] evaluates as ((10 + 5) * 2) = 30
 *
 * @param number_of_arguments Number of strings in the arguments array
 * @param arguments Array of strings representing the expression
 * @param number_of_operator_functions Size of operator_functions array
 * @param operator_functions Array mapping operator symbols to functions
 * @param[out] number_of_intermediate_values Number of operations performed
 * @param[out] intermediate_values Allocated array of intermediate results
 * @param[out] error_occurred Set to 1 if an error occurs, 0 otherwise
 * @return Final result of the calculation, or 0 if an error occurred
 *
 * @note The caller is responsible for freeing the intermediate_values array.
 * @note intermediate_values is set to NULL if an error occurs.
 *
 * @pre arguments must be non-NULL if number_of_arguments > 0
 * @pre operator_functions must be non-NULL if number_of_operator_functions > 0
 * @pre error_occurred, number_of_intermediate_values, and intermediate_values
 *      must be non-NULL
 */
int perform_operation(int number_of_arguments,
                      const char *const *arguments,
                      const size_t number_of_operator_functions,
                      const struct OperatorFunction *const operator_functions,
                      size_t *const number_of_intermediate_values,
                      int **const intermediate_values,
                      int *const error_occurred)
{
    char *end_of_integer;
    int value;
    int i;

    assert(!number_of_arguments || arguments);
    assert(!number_of_operator_functions || operator_functions);
    assert(error_occurred != NULL);
    assert(number_of_intermediate_values != NULL);
    assert(intermediate_values != NULL);

    *error_occurred = 0;
    *number_of_intermediate_values = 0;
    *intermediate_values = NULL;

    if (!number_of_arguments) {
        return 0;
    }

    /* Parse the first value */
    value = (int)strtol(arguments[0], &end_of_integer, 10);
    if (end_of_integer == arguments[0]) {
        fprintf(stderr,
                "Unable to parse integer from argument %s\n",
                arguments[0]);
        *error_occurred = 1;
        return 0;
    }

    /* Allocate array for intermediate results */
    *intermediate_values = malloc(((number_of_arguments - 1) / 2) *
                                  sizeof(**intermediate_values));
    if (*intermediate_values == NULL) {
        fprintf(stderr, "Failed to allocate memory for intermediate values\n");
        *error_occurred = 1;
        return 0;
    }

    /* Process operator-operand pairs */
    i = 1;
    while (i < number_of_arguments) {
        int other_value;
        const char *const operator_string = arguments[i];
        const BinaryOperator function = find_operator_function_by_string(
            number_of_operator_functions, operator_functions, operator_string);
        int *const intermediate_value = &(
            (*intermediate_values)[*number_of_intermediate_values]);

        (*number_of_intermediate_values)++;

        if (!function) {
            fprintf(stderr,
                    "Unknown operator %s, argument %d\n",
                    operator_string,
                    i);
            *error_occurred = 1;
            break;
        }
        i++;

        if (i == number_of_arguments) {
            fprintf(stderr,
                    "Binary operator %s missing argument\n",
                    operator_string);
            *error_occurred = 1;
            break;
        }

        other_value = (int)strtol(arguments[i], &end_of_integer, 10);
        if (end_of_integer == arguments[i]) {
            fprintf(stderr,
                    "Unable to parse integer %s of argument %d\n",
                    arguments[i],
                    i);
            *error_occurred = 1;
            break;
        }
        i++;

        /* Perform the operation and store the result */
        *intermediate_value = function(value, other_value);
        value = *intermediate_value;
    }

    if (*error_occurred) {
        free(*intermediate_values);
        *intermediate_values = NULL;
        *number_of_intermediate_values = 0;
        return 0;
    }

    return value;
}

/**
 * @brief Main entry point for the calculator program.
 *
 * Processes command-line arguments as an arithmetic expression and displays
 * the intermediate steps and final result.
 *
 * @param argc Number of command-line arguments
 * @param argv Array of command-line argument strings
 * @return 0 on success, non-zero on error
 */
int main(int argc, char *argv[])
{
    int return_value;
    size_t number_of_intermediate_values;
    int *intermediate_values;

    /* Perform the operation */
    const int result = perform_operation(argc - 1,
                                         (const char *const *)&argv[1],
                                         sizeof(operator_function_map) /
                                             sizeof(operator_function_map[0]),
                                         operator_function_map,
                                         &number_of_intermediate_values,
                                         &intermediate_values,
                                         &return_value);

    /* Display the result if no errors occurred */
    if (return_value == 0 && argc > 1) {
        size_t intermediate_value_index = 0;
        size_t i;

        printf("%s\n", argv[1]);
        for (i = 2; i < (size_t)argc; i += 2) {
            assert(intermediate_value_index < number_of_intermediate_values);
            printf("  %s %s = %d\n",
                   argv[i],
                   argv[i + 1],
                   intermediate_values[intermediate_value_index++]);
        }
        printf("= %d\n", result);
    }

    if (intermediate_values) {
        free(intermediate_values);
    }

    return return_value;
}