tally/include/tally.h

163 lines
7.5 KiB
C

/* 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 <stddef.h>
#include <stdint.h>
#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 */