exp-rs 0.2.0

no_std expression parser, compiler, and evaluation engine for math expressions designed for embedded, with qemu examples
Documentation
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
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include "exp_rs.h"
#include "common_allocator.h"

// Helper to check if an error code indicates parse error
int is_parse_error(int32_t code) {
    // Parse errors are typically in the 10-19 range
    return code >= 10 && code <= 19;
}

// Helper to check if an error code indicates evaluation error
int is_eval_error(int32_t code) {
    // Eval errors are typically in the 20-29 range
    return code >= 20 && code <= 29;
}

// Test invalid expressions with new ExprResult API
void test_invalid_expressions() {
    printf("=== Test Invalid Expressions (with ExprResult) ===\n");
    
    // Arena managed internally: 8192);
    ExprBatch* batch = expr_batch_new(8192);
    
    // Test cases for invalid expressions
    struct {
        const char* expr;
        const char* desc;
    } invalid_exprs[] = {
        {"", "Empty expression"},
        {"   ", "Whitespace only"},
        {"2 +", "Missing operand"},
        {"* 3", "Missing left operand"},
        {"2 + + 3", "Double operator"},
        {"(2 + 3", "Unclosed parenthesis"},
        {"2 + 3)", "Extra closing parenthesis"},
        {"((2 + 3)", "Mismatched parentheses"},
        {"2 + * 3", "Adjacent operators"},
        {"sin()", "Empty function arguments"},
        {"sin(1, 2)", "Too many arguments"},
        {"unknown_func(1)", "Unknown function"},
        {"2 / 0", "Division by zero (valid parse, eval error)"},
        {"@#$%", "Invalid characters"},
        {"2..3", "Double decimal"},
        {"2e+", "Incomplete number"},
        {"if(1,2,3)", "Unsupported keyword"},
        {NULL, NULL}
    };
    
    int i = 0;
    while (invalid_exprs[i].expr != NULL) {
        ExprResult result = expr_batch_add_expression(batch, invalid_exprs[i].expr);
        if (result.status != 0) {
            printf("%s rejected:\n", invalid_exprs[i].desc);
            printf("  - Error code: %d\n", result.status);
            printf("  - Error message: %s\n", result.error);
        } else {
            printf("%s was accepted (index: %d)\n", 
                   invalid_exprs[i].desc, result.index);
        }
        i++;
    }
    
    expr_batch_free(batch);
    printf("\n");
}

// Test NULL pointer handling
void test_null_handling() {
    printf("=== Test NULL Pointer Handling ===\n");
    
    // Test batch creation with size 0 (should use default size)
    ExprBatch* batch = expr_batch_new(0);
    assert(batch != NULL);  // 0 means use default size, not an error
    expr_batch_free(batch);
    printf("✓ Batch with size 0 uses default size\n");
    
    // Test NULL batch operations with ExprResult
    ExprResult result = expr_batch_add_expression(NULL, "x + 1");
    assert(result.status != 0);
    printf("✓ NULL batch rejected for add_expression\n");
    printf("  - Error: %s\n", result.error);
    
    result = expr_batch_add_variable(NULL, "x", 1.0);
    assert(result.status != 0);
    printf("✓ NULL batch rejected for add_variable\n");
    printf("  - Error: %s\n", result.error);
    
    result = expr_batch_evaluate_ex(NULL, NULL);
    assert(result.status != 0);
    printf("✓ NULL batch rejected for evaluate_ex\n");
    printf("  - Error: %s\n", result.error);
    
    // Test NULL expression
    // Arena managed internally: 1024);
    batch = expr_batch_new(1024);
    result = expr_batch_add_expression(batch, NULL);
    assert(result.status != 0);
    printf("✓ NULL expression rejected\n");
    printf("  - Error: %s\n", result.error);
    
    // Test NULL parameter name
    result = expr_batch_add_variable(batch, NULL, 1.0);
    assert(result.status != 0);
    printf("✓ NULL parameter name rejected\n");
    printf("  - Error: %s\n", result.error);
    
    expr_batch_free(batch);
    printf("\n");
}

// Test parameter errors
void test_parameter_errors() {
    printf("=== Test Parameter Errors ===\n");
    
    // Arena managed internally: 4096);
    ExprBatch* batch = expr_batch_new(8192);
    
    // Add some valid parameters
    expr_batch_add_variable(batch, "x", 1.0);
    expr_batch_add_variable(batch, "y", 2.0);
    
    // Test duplicate parameter
    ExprResult res = expr_batch_add_variable(batch, "x", 3.0);
    if (res.status != 0) {
        printf("✓ Duplicate parameter rejected (error: %d)\n", res.status);
        printf("  - Error message: %s\n", res.error);
    } else {
        printf("✓ Duplicate parameter overwrites value\n");
    }
    
    // Test undefined variable in expression
    res = expr_batch_add_expression(batch, "x + y + z");
    if (res.status == 0) {
        // Expression parsed, now try to evaluate
        ExprContext* ctx = expr_context_new();
        ExprResult eval_res = expr_batch_evaluate_ex(batch, ctx);
        if (eval_res.status != 0) {
            printf("✓ Undefined variable caught at evaluation (error: %d)\n", eval_res.status);
            printf("  - Error message: %s\n", eval_res.error);
        } else {
            Real value = expr_batch_get_result(batch, 0);
            printf("✓ Undefined variable defaults to 0 (result: %.1f)\n", value);
        }
        expr_context_free(ctx);
    } else {
        printf("✓ Undefined variable caught at parse (error: %d)\n", res.status);
        printf("  - Error message: %s\n", res.error);
    }
    
    // Test invalid parameter index
    Real value = expr_batch_get_result(batch, 999);
    printf("✓ Invalid result index returns: %.1f\n", value);
    
    // Test parameter name limits
    char long_name[256];
    memset(long_name, 'a', 255);
    long_name[255] = '\0';
    res = expr_batch_add_variable(batch, long_name, 1.0);
    if (res.status != 0) {
        printf("✓ Very long parameter name rejected (error: %d)\n", res.status);
    } else {
        printf("✓ Very long parameter name accepted\n");
    }
    
    expr_batch_free(batch);
    printf("\n");
}

// Test function implementation
Real test_func(const Real* args, uintptr_t nargs) {
    (void)args; (void)nargs;
    return 42.0;
}

// Test function errors
void test_function_errors() {
    printf("=== Test Function Errors ===\n");
    
    // Arena managed internally: 4096);
    ExprBatch* batch = expr_batch_new(8192);
    ExprContext* ctx = expr_context_new();
    
    // Add test function
    expr_context_add_function(ctx, "test", 1, test_func);
    
    // Test unknown function
    ExprResult result = expr_batch_add_expression(batch, "unknown(1)");
    if (result.status != 0) {
        printf("✓ Unknown function caught at parse (error: %d)\n", result.status);
    } else {
        int32_t eval_result = expr_batch_evaluate(batch, ctx);
        if (eval_result != 0) {
            printf("✓ Unknown function caught at evaluation (error: %d)\n", eval_result);
        } else {
            printf("✗ Unknown function was not caught\n");
        }
    }
    
    // Test wrong number of arguments
    expr_batch_free(batch);
    batch = expr_batch_new(1024);
    result = expr_batch_add_expression(batch, "test(1, 2)"); // test expects 1 arg
    if (result.status != 0) {
        printf("✓ Wrong arg count caught at parse (error: %d)\n", result.status);
    } else {
        int32_t eval_result = expr_batch_evaluate(batch, ctx);
        if (eval_result != 0) {
            printf("✓ Wrong arg count caught at evaluation (error: %d)\n", eval_result);
        } else {
            printf("✗ Wrong arg count was not caught\n");
        }
    }
    
    // Test NULL function pointer
    int32_t func_result = expr_context_add_function(ctx, "null_func", 1, NULL);
    assert(func_result != 0);
    printf("✓ NULL function pointer rejected\n");
    
    // Test NULL function name
    func_result = expr_context_add_function(ctx, NULL, 1, test_func);
    assert(func_result != 0);
    printf("✓ NULL function name rejected\n");
    
    // Test empty function name
    func_result = expr_context_add_function(ctx, "", 1, test_func);
    if (func_result != 0) {
        printf("✓ Empty function name rejected (error: %d)\n", func_result);
    } else {
        printf("✓ Empty function name accepted\n");
    }
    
    expr_batch_free(batch);
    expr_context_free(ctx);
    printf("\n");
}

// Math function implementations
Real sqrt_func(const Real* args, uintptr_t nargs) {
    (void)nargs;
    return sqrt(args[0]);
}

Real log_func(const Real* args, uintptr_t nargs) {
    (void)nargs;
    return log(args[0]);
}

// Test arithmetic errors
void test_arithmetic_errors() {
    printf("=== Test Arithmetic Errors ===\n");
    
    // Arena managed internally: 4096);
    ExprBatch* batch = expr_batch_new(8192);
    ExprContext* ctx = expr_context_new();
    
    // Division by zero
    expr_batch_add_expression(batch, "1.0 / 0.0");
    int32_t result = expr_batch_evaluate(batch, ctx);
    if (result == 0) {
        Real value = expr_batch_get_result(batch, 0);
        if (isinf(value)) {
            printf("✓ Division by zero returns infinity: %f\n", value);
        } else {
            printf("✓ Division by zero returns: %f\n", value);
        }
    } else {
        printf("✓ Division by zero caught (error: %d)\n", result);
    }
    
    // Square root of negative
    expr_batch_free(batch);
    batch = expr_batch_new(1024);
    expr_context_add_function(ctx, "sqrt", 1, sqrt_func);
    
    expr_batch_add_expression(batch, "sqrt(-1.0)");
    result = expr_batch_evaluate(batch, ctx);
    if (result == 0) {
        Real value = expr_batch_get_result(batch, 0);
        if (isnan(value)) {
            printf("✓ sqrt(-1) returns NaN\n");
        } else {
            printf("✓ sqrt(-1) returns: %f\n", value);
        }
    } else {
        printf("✓ sqrt(-1) caught (error: %d)\n", result);
    }
    
    // Log of zero/negative
    expr_batch_free(batch);
    batch = expr_batch_new(1024);
    expr_context_add_function(ctx, "log", 1, log_func);
    
    expr_batch_add_expression(batch, "log(0.0)");
    result = expr_batch_evaluate(batch, ctx);
    if (result == 0) {
        Real value = expr_batch_get_result(batch, 0);
        if (isinf(value) && value < 0) {
            printf("✓ log(0) returns negative infinity\n");
        } else {
            printf("✓ log(0) returns: %f\n", value);
        }
    } else {
        printf("✓ log(0) caught (error: %d)\n", result);
    }
    
    expr_batch_free(batch);
    expr_context_free(ctx);
    printf("\n");
}

// Test memory limits
void test_memory_limits() {
    printf("=== Test Memory Limits ===\n");
    
    // Test very small arena
    ExprBatch* batch = expr_batch_new(64); // Very small arena
    
    // Try to add expressions until we run out of memory
    int count = 0;
    ExprResult result = {0};
    while (result.status == 0 && count < 100) {
        char expr[32];
        snprintf(expr, sizeof(expr), "x + %d", count);
        result = expr_batch_add_expression(batch, expr);
        if (result.status == 0) {
            count++;
        } else {
        }
    }
    
    if (result.status != 0) {
        printf("✓ Arena memory exhausted after %d expressions (error: %d)\n", 
               count, result.status);
    } else {
        printf("✓ Added %d expressions to tiny arena\n", count);
    }
    
    expr_batch_free(batch);
    
    // Test expression complexity limit
    // Arena managed internally: 8192);
    batch = expr_batch_new(1024);
    
    // Build a deeply nested expression
    char nested[1024] = "1";
    for (int i = 0; i < 50; i++) {
        char temp[1024];
        snprintf(temp, sizeof(temp), "(%s + 1)", nested);
        strncpy(nested, temp, sizeof(nested) - 1);
    }
    
    ExprResult nested_result = expr_batch_add_expression(batch, nested);
    if (nested_result.status != 0) {
        printf("✓ Deeply nested expression rejected (error: %d)\n", nested_result.status);
    } else {
        printf("✓ Deeply nested expression accepted\n");
    }
    
    expr_batch_free(batch);
    printf("\n");
}

// Test boundary conditions
void test_boundary_conditions() {
    printf("=== Test Boundary Conditions ===\n");
    
    // Arena managed internally: 4096);
    ExprBatch* batch = expr_batch_new(8192);
    ExprContext* ctx = expr_context_new();
    
    // Test very large numbers
    expr_batch_add_expression(batch, "1e308 + 1e308");
    int32_t result = expr_batch_evaluate(batch, ctx);
    if (result == 0) {
        Real value = expr_batch_get_result(batch, 0);
        if (isinf(value)) {
            printf("✓ Overflow returns infinity\n");
        } else {
            printf("✓ Large number result: %g\n", value);
        }
    }
    
    // Test very small numbers
    expr_batch_free(batch);
    batch = expr_batch_new(1024);
    expr_batch_add_expression(batch, "1e-308 * 1e-308");
    result = expr_batch_evaluate(batch, ctx);
    if (result == 0) {
        Real value = expr_batch_get_result(batch, 0);
        printf("✓ Underflow result: %g\n", value);
    }
    
    // Test zero expressions
    expr_batch_free(batch);
    batch = expr_batch_new(1024);
    result = expr_batch_evaluate(batch, ctx); // No expressions added
    if (result != 0) {
        printf("✓ Evaluating empty batch caught (error: %d)\n", result);
    } else {
        printf("✓ Evaluating empty batch succeeds\n");
    }
    
    // Test maximum parameters
    expr_batch_free(batch);
    batch = expr_batch_new(1024);
    int max_params = 0;
    for (int i = 0; i < 1000; i++) {
        char name[16];
        snprintf(name, sizeof(name), "p%d", i);
        ExprResult var_result = expr_batch_add_variable(batch, name, (Real)i);
        if (var_result.status != 0) {
            break;
        }
        max_params++;
    }
    printf("✓ Maximum parameters accepted: %d\n", max_params);
    
    expr_batch_free(batch);
    expr_context_free(ctx);
    printf("\n");
}


int main() {
    init_memory_tracking();
    printf("\n==== Expression Error Handling Tests ====\n\n");
    
    test_invalid_expressions();
    test_null_handling();
    test_parameter_errors();
    test_function_errors();
    test_arithmetic_errors();
    test_memory_limits();
    test_boundary_conditions();
    
    printf("==== All Error Handling Tests Completed ====\n\n");
    return 0;
}