/* 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 6u /* 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. * * A programmer-mode result carries "rows" (every base, for reading) and, since ABI * version 6, "pattern": the value masked to the width as lowercase hex with no prefix * or grouping, for a caller that holds the value rather than displaying it. */ 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 */