From b86ba42811b56cfe3a24b4322b2dfb7439cf3fb2 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Sat, 3 Oct 2026 12:21:30 -0700 Subject: [PATCH] c api header file --- include/tally.h | 159 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 include/tally.h diff --git a/include/tally.h b/include/tally.h new file mode 100644 index 0000000..a5cfc65 --- /dev/null +++ b/include/tally.h @@ -0,0 +1,159 @@ +/* Tally calculation engine - C ABI. + * + * Hand-written rather than generated: Zig no longer emits reliable C headers, and this + * file is the contract a Swift, C or JNI consumer compiles against. `c_abi_test.c` + * links against the real library through this header, so the two cannot drift apart + * without a build failure. + * + * Three rules: + * + * 1. A session owns the memory. There is no per-result free function; freeing the + * session releases everything, including any result still borrowed from it. + * 2. A result is borrowed. The bytes stay valid until the next call on the same + * session. Copy them before calling again. + * 3. One session, one thread at a time. Sessions share nothing, so a thread may hold + * its own. This library takes no locks. + * + * Results are JSON. On failure the payload is {"ok":false,"error":"..."}, worded by the + * engine so every frontend says the same thing. + */ + +#ifndef TALLY_H +#define TALLY_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Value of tally_abi_version() this header was written against. Compare at startup: + * a prebuilt library can fall out of step with the code calling it. */ +#define TALLY_ABI_VERSION 5u + +/* How a call ended. Evaluation failing is a normal outcome (TALLY_EVAL_ERROR) with the + * reason in the JSON, not an exceptional one. + * + * For every call that hands back a result: TALLY_OK and TALLY_EVAL_ERROR leave JSON in + * *out_ptr / *out_len. TALLY_OUT_OF_MEMORY leaves an empty, NUL-terminated string there + * - never the previous call's result - and the session stays usable. A call refused + * with TALLY_INVALID_ARGUMENT may leave the out-parameters untouched; read nothing. */ +typedef enum { + TALLY_OK = 0, + TALLY_EVAL_ERROR = 1, + TALLY_OUT_OF_MEMORY = 2, + TALLY_INVALID_ARGUMENT = 3 +} tally_status; + +/* Which evaluator runs, chosen per call. */ +typedef enum { + TALLY_MODE_STANDARD = 0, + TALLY_MODE_PROGRAMMER = 1 +} tally_mode; + +/* Everything a frontend decides about interpretation and display. The display fields + * are the caller's to choose: the engine holds no digit budget of its own, because a + * phone screen, a terminal and a clipboard want different ones. + * + * Zero-initialising this struct is NOT the same as the defaults. Read the current + * configuration with tally_session_config() and modify that. */ +typedef struct { + uint16_t bits; /* 8, 16, 32, 64 or 128; anything else is refused */ + uint8_t is_signed; /* read programmer values as two's complement */ + uint8_t big_endian; /* byte order of the hex and ASCII rows */ + uint8_t separators; /* thousands separators in rendered integers */ + uint8_t never_abbreviate; /* ignore max_integer_digits; print every digit */ + uint16_t fraction_digits; + uint16_t max_integer_digits; + uint16_t significant_digits; + uint16_t scientific_below_exponent; +} tally_config; + +/* A calculator that remembers things: variables, the last answer, and one buffer for + * whatever it last said. Opaque. */ +typedef struct tally_session tally_session; + +/* Create a session, or NULL when there is no memory for one. */ +tally_session *tally_session_new(void); + +/* Release a session and everything it owns. NULL is accepted. */ +void tally_session_free(tally_session *session); + +/* Replace the configuration. Refuses a width that is not one of the five supported. */ +tally_status tally_session_configure(tally_session *session, const tally_config *config); + +/* Read the configuration, including defaults never set by the caller. */ +tally_status tally_session_config(tally_session *session, tally_config *out); + +/* Evaluate an expression. Strings are (pointer, length); nothing is null-terminated. + * + * On TALLY_OK and TALLY_EVAL_ERROR, *out_ptr and *out_len describe JSON borrowed from the + * session until its next call; see tally_status for the other two. The length is + * authoritative; the bytes also carry a NUL just past it, so a caller whose next step + * wants a C string (printf, JNI's NewStringUTF) does not have to copy them first. */ +tally_status tally_eval(tally_session *session, + const uint8_t *expr_ptr, + size_t expr_len, + int mode, + const uint8_t **out_ptr, + size_t *out_len); + +/* What tally_eval would answer, changing nothing: an assignment is not stored and Ans + * keeps the last committed answer. For a screen that answers as the user types. Same + * arguments, same JSON, same borrowing rule. Added in ABI version 2. */ +tally_status tally_preview(tally_session *session, + const uint8_t *expr_ptr, + size_t expr_len, + int mode, + const uint8_t **out_ptr, + size_t *out_len); + +/* The unit categories and their units, as JSON borrowed like any other result. Fill a + * picker from this rather than retyping the engine's tables. Each unit carries its + * canonical "name", a display "label" ("Meter per second"; added in ABI version 4), its + * "aliases", and whether it converts "exact"ly. */ +tally_status tally_unit_catalog(tally_session *session, + const uint8_t **out_ptr, + size_t *out_len); + +/* A value in every unit of unit's category: {"ok":true,"input":"100","from":"C", + * "category":"temperature","results":[{"unit":"F","display":"212","exact":true,...}]}. + * The value is an expression, previewed, so nothing in the session changes; a row that + * cannot be converted carries its own "error". Borrowed like any other result. Added in + * ABI version 3. */ +tally_status tally_convert(tally_session *session, + const uint8_t *value_ptr, + size_t value_len, + const uint8_t *unit_ptr, + size_t unit_len, + const uint8_t **out_ptr, + size_t *out_len); + +/* The session's variables and Ans, exactly, as JSON borrowed like any other result: + * exact values as fractions with every digit, inexact ones as their IEEE-754 bits. Give + * it to tally_session_load - in this process or a later one - to put them back. The + * configuration is not included; it is the caller's to set. Added in ABI version 5. */ +tally_status tally_session_save(tally_session *session, + const uint8_t **out_ptr, + size_t *out_len); + +/* Replace the session's variables and Ans with a saved state. All or nothing: on any + * status but TALLY_OK the session is unchanged. Text that is not a saved state, or that + * names a variable an assignment could not have made, is TALLY_INVALID_ARGUMENT. Any + * result still borrowed from the session is invalidated. Added in ABI version 5. */ +tally_status tally_session_load(tally_session *session, + const uint8_t *state_ptr, + size_t state_len); + +/* The product version. Static storage; do not free. out_len may be NULL. */ +const uint8_t *tally_version(size_t *out_len); + +/* The ABI version, which moves independently of the product version. */ +uint32_t tally_abi_version(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TALLY_H */