/** * @file format.h * @brief `PRINT USING` field formatting, and the `PUDEF` characters it fills with. * * Its own translation unit rather than a helper inside the PRINT handler, * because the interesting part is a pure function of a format string and a * value: it can be tested exhaustively without a runtime, a sink or a program, * and field formatting has far more edge cases than a verb usually does. */ #ifndef _AKBASIC_FORMAT_H_ #define _AKBASIC_FORMAT_H_ #include #include #include /** @brief How many characters `PUDEF` can redefine. */ #define AKBASIC_PUDEF_CHARS 4 /** @brief `PUDEF` position 1: what a leading blank in a numeric field is filled with. */ #define AKBASIC_PUDEF_BLANK 0 /** @brief `PUDEF` position 2: the thousands separator. */ #define AKBASIC_PUDEF_COMMA 1 /** @brief `PUDEF` position 3: the decimal point. */ #define AKBASIC_PUDEF_POINT 2 /** @brief `PUDEF` position 4: the currency sign. */ #define AKBASIC_PUDEF_DOLLAR 3 /** * @brief The four characters `PUDEF` redefines. * * Lives on the runtime beside the other verb-group state: it is the program's, * not the sink's, and a host that swaps one output device for another does not * expect the program's punctuation to change. */ typedef struct { char chars[AKBASIC_PUDEF_CHARS]; } akbasic_FormatState; /** * @brief Reset the `PUDEF` characters to their defaults: space, comma, point, dollar. * @param obj Object to initialize, inspect, or modify. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When `obj` is NULL. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_format_state_init(akbasic_FormatState *obj); /** * @brief Redefine the fill characters, up to four of them. * * Positions past the end of @p chars keep whatever they had, so `PUDEF "*"` * changes only the leading-blank character. That is BASIC 7.0's rule. * * @param obj Object to initialize, inspect, or modify. * @param chars Replacement characters, in PUDEF order; at most four are read. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When either argument is NULL. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_format_pudef(akbasic_FormatState *obj, const char *chars); /** * @brief Render one value into one `PRINT USING` field. * * @p format is the whole format string; the *first* field in it is the one used, * and any literal text around that field is copied through. That is how BASIC * 7.0 works -- `PRINT USING "TOTAL: ###.##"; X` prints the label as well. * * Numeric fields are built from `#` digit positions, an optional `.` and * embedded `,` separators, with an optional leading or trailing `$`, `+` or `-`. * String fields are `=` to centre and `>` to right-justify, and a bare run of * `#` left-justifies. * * **A value too wide for its field fills the field with `*`**, which is BASIC * 7.0's answer and is deliberately loud: silently printing more digits than the * field asked for would misalign every column after it. * * @param obj The PUDEF characters to fill with. * @param format The format string. * @param value The value to render. * @param dest Output destination populated by the function. * @param len Capacity of @p dest, terminator included. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When any argument is NULL. * @throws AKBASIC_ERR_BOUNDS When the result does not fit @p dest. * @throws AKBASIC_ERR_SYNTAX When the format string contains no field at all. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_format_using(akbasic_FormatState *obj, const char *format, akbasic_Value *value, char *dest, size_t len); #endif // _AKBASIC_FORMAT_H_