Mercurial > ~darius > hgwebdir.cgi > modulator
comparison q/t.c @ 14:388074ff9474
Add fixed point code
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Tue, 25 Feb 2025 13:28:29 +1030 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
13:032acb7fbc04 | 14:388074ff9474 |
---|---|
1 /**@file t.c | |
2 * @brief Q-Number (Q16.16, signed) library test bench | |
3 * @copyright Richard James Howe (2018) | |
4 * @license MIT | |
5 * @email howe.r.j.89@gmail.com | |
6 * @site <https://github.com/howerj/q> | |
7 * | |
8 * This file contains a wrapper for testing the Q Number library, | |
9 * it includes a command processor and some built in tests. View | |
10 * the help string later on for more information */ | |
11 #include "q.h" | |
12 #include <assert.h> | |
13 #include <ctype.h> | |
14 #include <errno.h> | |
15 #include <stdbool.h> | |
16 #include <stdio.h> | |
17 #include <stdlib.h> | |
18 #include <string.h> | |
19 | |
20 #define N (16) | |
21 #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) | |
22 | |
23 static int qprint(FILE *out, const q_t p) { | |
24 assert(out); | |
25 char buf[64 + 1] = { 0, }; | |
26 const int r = qsprint(p, buf, sizeof buf); | |
27 return r < 0 ? r : fprintf(out, "%s", buf); | |
28 } | |
29 | |
30 static int printq(FILE *out, q_t q, const char *msg) { | |
31 assert(out); | |
32 assert(msg); | |
33 if (fprintf(out, "%s = ", msg) < 0) | |
34 return -1; | |
35 if (qprint(out, q) < 0) | |
36 return -1; | |
37 if (fputc('\n', out) != '\n') | |
38 return -1; | |
39 return 0; | |
40 } | |
41 | |
42 static int print_sincos(FILE *out, const q_t theta) { | |
43 assert(out); | |
44 q_t sine = qinfo.zero, cosine = qinfo.zero; | |
45 qsincos(theta, &sine, &cosine); | |
46 if (qprint(out, theta) < 0) | |
47 return -1; | |
48 if (fputc(',', out) != ',') | |
49 return -1; | |
50 if (qprint(out, sine) < 0) | |
51 return -1; | |
52 if (fputc(',', out) != ',') | |
53 return -1; | |
54 if (qprint(out, cosine) < 0) | |
55 return -1; | |
56 if (fputc('\n', out) != '\n') | |
57 return -1; | |
58 return 0; | |
59 } | |
60 | |
61 static int print_sincos_table(FILE *out) { | |
62 assert(out); | |
63 const q_t tpi = qdiv(qinfo.pi, qint(20)); | |
64 const q_t end = qmul(qinfo.pi, qint(2)); | |
65 const q_t start = qnegate(end); | |
66 if (fprintf(out, "theta,sine,cosine\n") < 0) | |
67 return -1; | |
68 for (q_t i = start; qless(i, end); i = qadd(i, tpi)) | |
69 if (print_sincos(out, i) < 0) | |
70 return -1; | |
71 return 0; | |
72 } | |
73 | |
74 static int qinfo_print(FILE *out, const qinfo_t *qi) { | |
75 assert(out); | |
76 assert(qi); | |
77 if (fprintf(out, "Q%u.%u Info\n", (unsigned)qi->whole, (unsigned)qi->fractional) < 0) return -1; | |
78 if (printq(out, qi->bit, "bit") < 0) return -1; | |
79 if (printq(out, qi->one, "one") < 0) return -1; | |
80 if (printq(out, qi->zero, "zero") < 0) return -1; | |
81 if (printq(out, qi->pi, "pi") < 0) return -1; | |
82 if (printq(out, qi->e, "e") < 0) return -1; | |
83 if (printq(out, qi->sqrt2, "sqrt2") < 0) return -1; | |
84 if (printq(out, qi->sqrt3, "sqrt3") < 0) return -1; | |
85 if (printq(out, qi->ln2, "ln2") < 0) return -1; | |
86 if (printq(out, qi->ln10, "ln10") < 0) return -1; | |
87 if (printq(out, qi->min, "min") < 0) return -1; | |
88 if (printq(out, qi->max, "max") < 0) return -1; | |
89 if (printq(out, qcordic_circular_gain(-1), "circular-gain") < 0) return -1; | |
90 if (printq(out, qcordic_hyperbolic_gain(-1), "hyperbolic-gain") < 0) return -1; | |
91 return 0; | |
92 } | |
93 | |
94 static int qconf_print(FILE *out, const qconf_t *qc) { | |
95 assert(out); | |
96 assert(qc); | |
97 if (fprintf(out, "Q Configuration\n") < 0) return -1; | |
98 const char *bounds = "unknown"; | |
99 qbounds_t bound = qc->bound; | |
100 if (bound == qbound_saturate) | |
101 bounds = "saturate"; | |
102 if (bound == qbound_wrap) | |
103 bounds = "wrap"; | |
104 if (fprintf(out, "overflow handler: %s\n", bounds) < 0) return -1; | |
105 if (fprintf(out, "input/output radix: %u (0 = special case)\n", qc->base) < 0) return -1; | |
106 if (fprintf(out, "decimal places: %d\n", qc->dp) < 0) return -1; | |
107 return 0; | |
108 } | |
109 | |
110 static FILE *fopen_or_die(const char *file, const char *mode) { | |
111 assert(file && mode); | |
112 FILE *h = NULL; | |
113 errno = 0; | |
114 if (!(h = fopen(file, mode))) { | |
115 (void)fprintf(stderr, "file open \"%s\" (mode %s) failed: %s\n", file, mode, strerror(errno)); | |
116 exit(EXIT_FAILURE); | |
117 } | |
118 return h; | |
119 } | |
120 | |
121 typedef enum { | |
122 EVAL_OK_E, | |
123 EVAL_COMMENT_E, | |
124 EVAL_ERROR_SCAN_E, | |
125 EVAL_ERROR_TYPE_E, | |
126 EVAL_ERROR_CONVERT_E, | |
127 EVAL_ERROR_OPERATION_E, | |
128 EVAL_ERROR_ARG_COUNT_E, | |
129 EVAL_ERROR_UNEXPECTED_RESULT_E, | |
130 EVAL_ERROR_LIMIT_MODE_E, | |
131 | |
132 EVAL_ERROR_MAX_ERRORS_E, /**< not an error, but a count of errors */ | |
133 } eval_errors_e; | |
134 | |
135 static const char *eval_error(int e) { | |
136 if (e < 0 || e >= EVAL_ERROR_MAX_ERRORS_E) | |
137 return "unknown"; | |
138 const char *msgs[EVAL_ERROR_MAX_ERRORS_E] = { | |
139 [EVAL_OK_E] = "ok", | |
140 [EVAL_COMMENT_E] = "(comment)", | |
141 [EVAL_ERROR_SCAN_E] = "invalid input line", | |
142 [EVAL_ERROR_TYPE_E] = "unknown function type", | |
143 [EVAL_ERROR_CONVERT_E] = "numeric conversion failed", | |
144 [EVAL_ERROR_OPERATION_E] = "unknown operation", | |
145 [EVAL_ERROR_ARG_COUNT_E] = "incorrect argument count", | |
146 [EVAL_ERROR_LIMIT_MODE_E] = "unknown limit mode ('|' or '%' allowed)", | |
147 [EVAL_ERROR_UNEXPECTED_RESULT_E] = "unexpected result", | |
148 }; | |
149 return msgs[e] ? msgs[e] : "unknown"; | |
150 } | |
151 | |
152 static int eval_unary_arith(q_t (*m)(q_t), q_t expected, q_t bound, q_t arg1, q_t *result) { | |
153 assert(m); | |
154 assert(result); | |
155 const q_t r = m(arg1); | |
156 *result = r; | |
157 if (qwithin_interval(r, expected, bound)) | |
158 return 0; | |
159 return -1; | |
160 } | |
161 | |
162 static int eval_binary_arith(q_t (*f)(q_t, q_t), q_t expected, q_t bound, q_t arg1, q_t arg2, q_t *result) { | |
163 assert(f); | |
164 assert(result); | |
165 const q_t r = f(arg1, arg2); | |
166 *result = r; | |
167 if (qwithin_interval(r, expected, bound)) | |
168 return 0; | |
169 return -1; | |
170 } | |
171 | |
172 static int comment(char *line) { | |
173 assert(line); | |
174 const size_t length = strlen(line); | |
175 for (size_t i = 0; i < length; i++) { | |
176 if (line[i] == '#') | |
177 return 1; | |
178 if (!isspace(line[i])) | |
179 break; | |
180 } | |
181 for (size_t i = 0; i < length; i++) { | |
182 if (line[i] == '#') { | |
183 line[i] = 0; | |
184 return 0; | |
185 } | |
186 } | |
187 return 0; | |
188 } | |
189 | |
190 static void bounds_set(char method) { | |
191 assert(method == '|' || method == '%'); | |
192 if (method == '|') | |
193 qconf.bound = qbound_saturate; | |
194 qconf.bound = qbound_wrap; | |
195 } | |
196 | |
197 static int eval(char *line, q_t *result) { | |
198 assert(line); | |
199 assert(result); | |
200 *result = qinfo.zero; | |
201 if (comment(line)) | |
202 return EVAL_COMMENT_E; | |
203 char operation[N] = { 0 }, expected[N] = { 0 }, bounds[N], arg1[N] = { 0 }, arg2[N] = { 0 }; | |
204 char limit = '|'; | |
205 const int count = sscanf(line, "%15s %15s +- %15s %c %15s %15s", operation, expected, bounds, &limit, arg1, arg2); | |
206 if (limit != '|' && limit != '%') | |
207 return -EVAL_ERROR_LIMIT_MODE_E; | |
208 bounds_set(limit); | |
209 if (count < 5) | |
210 return -EVAL_ERROR_SCAN_E; | |
211 const qoperations_t *func = qop(operation); | |
212 if (!func) | |
213 return -EVAL_ERROR_OPERATION_E; | |
214 q_t e = qinfo.zero, b = qinfo.zero, a1 = qinfo.zero, a2 = qinfo.zero; | |
215 const int argc = count - 4; | |
216 if (func->arity != argc) | |
217 return -EVAL_ERROR_ARG_COUNT_E; | |
218 if (qconv(&e, expected) < 0) | |
219 return -EVAL_ERROR_CONVERT_E; | |
220 if (qconv(&b, bounds) < 0) | |
221 return -EVAL_ERROR_CONVERT_E; | |
222 if (qconv(&a1, arg1) < 0) | |
223 return -EVAL_ERROR_CONVERT_E; | |
224 switch (func->arity) { | |
225 case 1: if (eval_unary_arith(func->eval.unary, e, b, a1, result) < 0) | |
226 return -EVAL_ERROR_UNEXPECTED_RESULT_E; | |
227 break; | |
228 case 2: if (qconv(&a2, arg2) < 0) | |
229 return -EVAL_ERROR_CONVERT_E; | |
230 if (eval_binary_arith(func->eval.binary, e, b, a1, a2, result) < 0) | |
231 return -EVAL_ERROR_UNEXPECTED_RESULT_E; | |
232 break; | |
233 default: | |
234 return -EVAL_ERROR_TYPE_E; | |
235 } | |
236 return EVAL_OK_E; | |
237 } | |
238 | |
239 static void trim(char *s) { | |
240 assert(s); | |
241 const int size = strlen(s); | |
242 for (int i = size - 1; i >= 0; i--) { | |
243 if (!isspace(s[i])) | |
244 break; | |
245 s[i] = 0; | |
246 } | |
247 } | |
248 | |
249 static int eval_file(FILE *input, FILE *output) { | |
250 assert(input); | |
251 assert(output); | |
252 char line[256] = { 0 }; | |
253 int rv = 0; | |
254 while (fgets(line, sizeof(line) - 1, input)) { | |
255 q_t result = 0; | |
256 const int r = eval(line, &result); | |
257 if (r == EVAL_COMMENT_E) | |
258 continue; | |
259 if (r < 0) { | |
260 const char *msg = eval_error(-r); | |
261 trim(line); | |
262 if (fprintf(output, "error: %s = ", line) < 0) return -1; | |
263 if (qprint(output, result) < 0) return -1; | |
264 if (fprintf(output, " : %s/%d\n", msg, r) < 0) return -1; | |
265 rv = -1; | |
266 continue; | |
267 } | |
268 trim(line); | |
269 char rstring[64 + 1] = { 0, }; | |
270 if (qsprint(result, rstring, sizeof(rstring) - 1) < 0) return -1; | |
271 if (fprintf(output, "ok: %s | (%s)\n", line, rstring) < 0) return -1; | |
272 memset(line, 0, sizeof line); | |
273 } | |
274 return rv; | |
275 } | |
276 | |
277 /* --- Unit Test Framework --- */ | |
278 | |
279 typedef struct { | |
280 unsigned passed, | |
281 run; | |
282 } unit_test_t; | |
283 | |
284 static inline unit_test_t _unit_test_start(const char *file, const char *func, unsigned line) { | |
285 assert(file); | |
286 assert(func); | |
287 unit_test_t t = { .run = 0, .passed = 0 }; | |
288 fprintf(stdout, "Start tests: %s in %s:%u\n\n", func, file, line); | |
289 return t; | |
290 } | |
291 | |
292 static inline void _unit_test_statement(const char *expr_str) { | |
293 assert(expr_str); | |
294 if (fprintf(stdout, " STATE: %s\n", expr_str) < 0) | |
295 abort(); | |
296 } | |
297 | |
298 static inline void _unit_test(unit_test_t *t, int failed, const char *expr_str, const char *file, const char *func, unsigned line, int die) { | |
299 assert(t); | |
300 assert(expr_str); | |
301 assert(file); | |
302 assert(func); | |
303 if (failed) { | |
304 fprintf(stdout, " FAILED: %s (%s:%s:%u)\n", expr_str, file, func, line); | |
305 if (die) { | |
306 fputs("VERIFY FAILED - EXITING\n", stdout); | |
307 exit(EXIT_FAILURE); | |
308 } | |
309 } else { | |
310 fprintf(stdout, " OK: %s\n", expr_str); | |
311 t->passed++; | |
312 } | |
313 t->run++; | |
314 } | |
315 | |
316 static inline int unit_test_finish(unit_test_t *t) { | |
317 assert(t); | |
318 fprintf(stdout, "Tests passed/total: %u/%u\n", t->passed, t->run); | |
319 if (t->run != t->passed) { | |
320 (void)fputs("[FAILED]\n", stdout); | |
321 return -1; | |
322 } | |
323 if (fputs("[SUCCESS]\n", stdout) < 0) | |
324 return -1; | |
325 return 0; | |
326 } | |
327 | |
328 #define unit_test_statement(T, EXPR) do { (void)(T); EXPR; _unit_test_statement(( #EXPR)); } while (0) | |
329 #define unit_test_start() _unit_test_start(__FILE__, __func__, __LINE__) | |
330 #define unit_test(T, EXPR) _unit_test((T), 0 == (EXPR), (# EXPR), __FILE__, __func__, __LINE__, 0) | |
331 #define unit_test_verify(T, EXPR) _unit_test((T), 0 == (EXPR), (# EXPR), __FILE__, __func__, __LINE__, 1) | |
332 | |
333 static int test_sanity(void) { | |
334 unit_test_t t = unit_test_start(); | |
335 q_t t1 = 0, t2 = 0; | |
336 unit_test_statement(&t, t1 = qint(1)); | |
337 unit_test_statement(&t, t2 = qint(2)); | |
338 unit_test(&t, qtoi(qadd(t1, t2)) == 3); | |
339 return unit_test_finish(&t); | |
340 } | |
341 | |
342 static int test_pack(void) { | |
343 unit_test_t t = unit_test_start(); | |
344 q_t p1 = 0, p2 = 0; | |
345 char buf[sizeof(p1)] = { 0 }; | |
346 unit_test_statement(&t, p1 = qnegate(qinfo.pi)); | |
347 unit_test_statement(&t, p2 = 0); | |
348 unit_test(&t, qunequal(p1, p2)); | |
349 unit_test(&t, qpack(&p1, buf, sizeof p1 - 1) < 0); | |
350 unit_test(&t, qpack(&p1, buf, sizeof buf) == sizeof(p1)); | |
351 unit_test(&t, qunpack(&p2, buf, sizeof buf) == sizeof(p2)); | |
352 unit_test(&t, qequal(p1, p2)); | |
353 return unit_test_finish(&t); | |
354 } | |
355 | |
356 static int test_fma(void) { | |
357 unit_test_t t = unit_test_start(); | |
358 | |
359 q_t a, b, c, r; | |
360 const q_t one_and_a_half = qdiv(qint(3), qint(2)); | |
361 | |
362 /* incorrect, but expected, result due to saturation */ | |
363 unit_test_statement(&t, a = qinfo.max); | |
364 unit_test_statement(&t, b = one_and_a_half); | |
365 unit_test_statement(&t, c = qinfo.min); | |
366 unit_test_statement(&t, r = qadd(qmul(a, b), c)); | |
367 unit_test(&t, qwithin_interval(r, qint(0), qint(1))); | |
368 | |
369 /* correct result using Fused Multiply Add */ | |
370 unit_test_statement(&t, a = qinfo.max); | |
371 unit_test_statement(&t, b = one_and_a_half); | |
372 unit_test_statement(&t, c = qinfo.min); | |
373 unit_test_statement(&t, r = qfma(a, b, c)); | |
374 unit_test(&t, qwithin_interval(r, qdiv(qinfo.max, qint(2)), qint(1))); | |
375 | |
376 return unit_test_finish(&t); | |
377 } | |
378 | |
379 static inline int test_filter(void) { | |
380 unit_test_t t = unit_test_start(); | |
381 qfilter_t lpf = { .raw = 0 }, hpf = { .raw = 0 }; | |
382 const q_t beta = qdiv(qint(1), qint(3)); | |
383 qfilter_init(&lpf, qint(0), beta, qint(0)); | |
384 qfilter_init(&hpf, qint(0), beta, qint(0)); | |
385 for (int i = 0; i < 100; i++) { | |
386 char low[64 + 1] = { 0, }, high[64 + 1] = { 0, }; | |
387 const q_t step = qdiv(qint(i), qint(100)); | |
388 const q_t input = qint(1); | |
389 qfilter_low_pass(&lpf, step, input); | |
390 qfilter_high_pass(&hpf, step, input); | |
391 if (qsprint(qfilter_value(&lpf), low, sizeof(low) - 1ull) < 0) return -1; | |
392 if (qsprint(qfilter_value(&hpf), high, sizeof(high) - 1ull) < 0) return -1; | |
393 if (fprintf(stdout, "%2d: %s\t%s\n", i, low, high) < 0) return -1; | |
394 } | |
395 return unit_test_finish(&t); | |
396 } | |
397 | |
398 static int qmatrix_print(FILE *out, const q_t *m) { | |
399 assert(out); | |
400 assert(m); | |
401 const size_t alloc = qmatrix_string_length(m); | |
402 char *ms = malloc(alloc + 1); | |
403 if (!ms) | |
404 return -1; | |
405 int r = qmatrix_sprintb(m, ms, alloc + 1, 10); | |
406 if (r >= 0) | |
407 r = fprintf(out, "%s\n", ms); | |
408 free(ms); | |
409 return r; | |
410 } | |
411 | |
412 #define QMATRIX(ROW, COLUMN, ...) { 0, ROW * COLUMN, ROW, COLUMN, __VA_ARGS__ } | |
413 #define QMATRIXZ(ROW, COLUMN) QMATRIX((ROW), (COLUMN), 0) | |
414 #define QMATRIXSZ(ROW, COLUMN) ((((ROW)*(COLUMN)) + 4)*sizeof(q_t)) | |
415 | |
416 static int test_matrix(void) { | |
417 unit_test_t t = unit_test_start(); | |
418 FILE *out = stdout; | |
419 q_t a[] = QMATRIX(2, 3, | |
420 QINT(1), QINT(2), QINT(3), | |
421 QINT(4), QINT(5), QINT(6), | |
422 ); | |
423 q_t b[] = QMATRIX(3, 2, | |
424 QINT(2), QINT(3), | |
425 QINT(4), QINT(5), | |
426 QINT(6), QINT(7), | |
427 ); | |
428 const q_t abr[] = QMATRIX(2, 2, | |
429 QINT(28), QINT(34), | |
430 QINT(64), QINT(79), | |
431 ); | |
432 const q_t abrp[] = QMATRIX(2, 2, | |
433 QINT(28), QINT(64), | |
434 QINT(34), QINT(79), | |
435 ); | |
436 q_t ab[QMATRIXSZ(2, 2)] = QMATRIXZ(2, 2); | |
437 q_t abp[QMATRIXSZ(2, 2)] = QMATRIXZ(2, 2); | |
438 unit_test_verify(&t, 0 == qmatrix_mul(ab, a, b)); | |
439 unit_test_verify(&t, 0 == qmatrix_transpose(abp, ab)); | |
440 unit_test(&t, qmatrix_equal(ab, abr)); | |
441 unit_test(&t, qmatrix_equal(ab, abrp)); | |
442 qmatrix_print(out, a); | |
443 qmatrix_print(out, b); | |
444 qmatrix_print(out, ab); | |
445 qmatrix_print(out, abp); | |
446 return unit_test_finish(&t); | |
447 } | |
448 | |
449 static int test_matrix_trace(void) { | |
450 unit_test_t t = unit_test_start(); | |
451 q_t a[] = QMATRIX(2, 2, | |
452 QINT(1), QINT(2), | |
453 QINT(4), QINT(5), | |
454 ); | |
455 q_t b[] = QMATRIX(2, 2, | |
456 QINT(2), QINT(3), | |
457 QINT(4), QINT(5), | |
458 ); | |
459 q_t ta[sizeof(a) / sizeof(a[0])] = QMATRIX(2, 2, 0); | |
460 q_t tb[sizeof(b) / sizeof(b[0])] = QMATRIX(2, 2, 0); | |
461 q_t apb[sizeof(a) / sizeof(a[0])] = QMATRIX(2, 2, 0); | |
462 BUILD_BUG_ON(sizeof a != sizeof ta); | |
463 BUILD_BUG_ON(sizeof a != sizeof tb); | |
464 BUILD_BUG_ON(sizeof a != sizeof b); | |
465 BUILD_BUG_ON(sizeof a != sizeof apb); | |
466 unit_test_verify(&t, 0 == qmatrix_transpose(ta, a)); | |
467 unit_test_verify(&t, 0 == qmatrix_transpose(tb, b)); | |
468 unit_test_verify(&t, 0 == qmatrix_add(apb, a, b)); | |
469 unit_test(&t, qequal(qmatrix_trace(a), QINT(6))); | |
470 unit_test(&t, qequal(qmatrix_trace(b), QINT(7))); | |
471 unit_test(&t, qequal(qmatrix_trace(a), qmatrix_trace(ta))); | |
472 unit_test(&t, qequal(qmatrix_trace(apb), qadd(qmatrix_trace(a), qmatrix_trace(b)))); | |
473 printq(stdout, qmatrix_determinant(a), "det(a)"); | |
474 return unit_test_finish(&t); | |
475 } | |
476 | |
477 static q_t qid(q_t x) { return x; } | |
478 static q_t qsq(q_t x) { return qmul(x, x); } | |
479 | |
480 static int test_simpson(void) { | |
481 unit_test_t t = unit_test_start(); | |
482 unit_test(&t, qwithin_interval(qsimpson(qid, QINT(0), QINT(10), 100), QINT(50), QINT(1))); // (x^2)/2 + C | |
483 unit_test(&t, qwithin_interval(qsimpson(qsq, qnegate(QINT(2)), QINT(5), 100), QINT(44), QINT(1))); // (x^3)/3 + C | |
484 return unit_test_finish(&t); | |
485 } | |
486 | |
487 static int internal_tests(void) { | |
488 typedef int (*unit_test_t)(void); | |
489 unit_test_t tests[] = { | |
490 test_sanity, | |
491 test_pack, | |
492 test_fma, | |
493 // test_filter, | |
494 test_matrix, | |
495 test_matrix_trace, | |
496 test_simpson, | |
497 NULL | |
498 }; | |
499 for (size_t i = 0; tests[i]; i++) | |
500 if (tests[i]() < 0) | |
501 return -1; | |
502 return 0; | |
503 } | |
504 | |
505 static int help(FILE *out, const char *arg0) { | |
506 assert(out); | |
507 assert(arg0); | |
508 const char *h = "\n\ | |
509 Program: Q-Number (Q16.16, signed) library testbench\n\ | |
510 Author: Richard James Howe (2018)\n\ | |
511 License: MIT\n\ | |
512 E-mail: howe.r.j.89@gmail.com\n\ | |
513 Site: https://github.com/howerj/q\n\n\ | |
514 Options:\n\ | |
515 \t-s\tprint a sine-cosine table\n\ | |
516 \t-h\tprint this help message and exit\n\ | |
517 \t-i\tprint library information\n\ | |
518 \t-t\trun internal unit tests\n\ | |
519 \t-v\tprint version information and exit\n\ | |
520 \tfile\texecute commands in file\n\n\ | |
521 This program wraps up a Q-Number library and allows it to be tested by\n\ | |
522 giving it commands, either from stdin, or from a file. The format is\n\ | |
523 strict and the parser primitive, but it is only meant to be used to\n\ | |
524 test that the library is doing the correct thing and not as a\n\ | |
525 calculator.\n\n\ | |
526 Commands evaluated consist of an operator with the require arguments\n\ | |
527 (either one or two arguments) and compare the result with an expected\n\ | |
528 value, which can within a specified bounds for the test to pass. If\n\ | |
529 the test fails the program exits, indicating failure. The format is:\n\ | |
530 \n\ | |
531 \toperator expected +- allowance | arg1 arg2\n\n\ | |
532 operators include '+', '-', '/', 'rem', '<', which require two\n\ | |
533 arguments, and unary operators like 'sin', and 'negate', which require\n\ | |
534 only one. 'expected', 'allowance', 'arg1' and 'arg2' are all fixed\n\ | |
535 numbers of the form '-12.45'. 'expected' is the expected result,\n\ | |
536 'allowance' the +/- amount the result is allowed to deviated by, and\n\ | |
537 'arg1' and 'arg2' the operator arguments.\n\ | |
538 \n\n"; | |
539 if (fprintf(out, "usage: %s -h -s -i -v -t -c file\n", arg0) < 0) return -1; | |
540 if (fputs(h, out) < 0) return -1; | |
541 return 0; | |
542 } | |
543 | |
544 int main(int argc, char **argv) { | |
545 bool ran = false; | |
546 for (int i = 1; i < argc; i++) { | |
547 if (!strcmp("-h", argv[i])) { | |
548 if (help(stdout, argv[0]) < 0) | |
549 return 1; | |
550 return 0; | |
551 } else if (!strcmp("-s", argv[i])) { | |
552 print_sincos_table(stdout); | |
553 ran = true; | |
554 } else if (!strcmp("-v", argv[i])) { | |
555 fprintf(stdout, "version 1.0\n"); | |
556 return 0; | |
557 } else if (!strcmp("-t", argv[i])) { | |
558 if (internal_tests() < 0) | |
559 return 1; | |
560 ran = true; | |
561 } else if (!strcmp("-i", argv[i])) { | |
562 if (qinfo_print(stdout, &qinfo) < 0) | |
563 return 1; | |
564 if (qconf_print(stdout, &qconf) < 0) | |
565 return 1; | |
566 ran = true; | |
567 } else { | |
568 FILE *input = fopen_or_die(argv[i], "rb"); | |
569 FILE *output = stdout; | |
570 const int r = eval_file(input, output); | |
571 ran = true; | |
572 fclose(input); | |
573 if (r < 0) | |
574 return 1; | |
575 } | |
576 } | |
577 if (!ran) | |
578 return eval_file(stdin, stdout); | |
579 return 0; | |
580 } | |
581 |