llm
Download
104 lines 3.9 KiBCModified 2026-08-31 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
// Boolean methods/type info
#include <err.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/param.h>

#include "bool.h"
#include "integers.h"
#include "metamethods.h"
#include "optionals.h"
#include "text.h"
#include "util.h"

PUREFUNC public Text_t Bool$as_text(const void *b, bool colorize, const TypeInfo_t *info) {
    (void)info;
    if (!b) return Text("Bool");
    if (colorize) return *(Bool_t *)b ? Text("\x1b[35myes\x1b[m") : Text("\x1b[35mno\x1b[m");
    else return *(Bool_t *)b ? Text("yes") : Text("no");
}

// Accepts "true", "TRUE", and "True", but not scrambled casings like "tRuE".
// `target` is the lowercase form; the uppercase and capitalized forms are
// derived from it, so all three stay in sync from one literal.
//
// This does its own ASCII comparison rather than calling
// Text$equal_ignoring_case(), which does full Unicode case folding. That would
// link libunistring's case-mapping tables (~32KB of tries, plus special-casing
// and soft-dotted data) into every binary to match seven-bit words, since
// Bool's metamethods keep Bool$parse() reachable. Its language-sensitive rules
// are wrong here anyway: these are keywords, not user text, so Turkish dotless
// "\u0131" should not parse as a `1`.
static bool try_parse(Text_t text, const char *target, uint64_t target_len, bool target_value, Text_t *remainder,
                      bool *result) {
    if (text.length < target_len) return false;
    if (remainder == NULL && text.length > target_len) return false;
    bool lower = true, upper = true, capitalized = true;
    TextIter_t state = NEW_TEXT_ITER_STATE(text);
    for (uint64_t i = 0; i < target_len; i++) {
        int32_t g = Text$get_grapheme_fast(&state, (int64_t)i);
        // A negative grapheme is a multi-codepoint cluster, which never equals
        // a lone ASCII character:
        if (g < 0) return false;
        int32_t lo = (int32_t)target[i];
        int32_t up = (lo >= 'a' && lo <= 'z') ? lo - ('a' - 'A') : lo;
        lower = lower && g == lo;
        upper = upper && g == up;
        capitalized = capitalized && g == (i == 0 ? up : lo);
        if (!lower && !upper && !capitalized) return false;
    }
    if (remainder) *remainder = Text$from(text, Int$from_int64((int64_t)target_len + 1));
    *result = target_value;
    return true;
}

#define TRY_PARSE(str, value) try_parse(text, str, sizeof(str) - 1, value, remainder, &result)

public
OptionalBool_t Bool$parse(Text_t text, Text_t *remainder) {
    bool result;
    if (TRY_PARSE("yes", true) || TRY_PARSE("true", true) || TRY_PARSE("on", true) || TRY_PARSE("1", true)
        || TRY_PARSE("no", false) || TRY_PARSE("false", false) || TRY_PARSE("off", false) || TRY_PARSE("0", false))
        return result;
    else return NONE_BOOL;
}

#undef TRY_PARSE

static bool Bool$is_none(const void *b, const TypeInfo_t *info) {
    (void)info;
    return *(OptionalBool_t *)b == NONE_BOOL;
}

static void Bool$serialize(const void *obj, FILE *out, Table_t *pointers, const TypeInfo_t *info) {
    (void)pointers, (void)info;
    fputc(*(bool *)obj ? 1 : 0, out);
}

static void Bool$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_t *info) {
    (void)pointers, (void)info;
    // Only 0 and 1 are booleans. Reading the byte raw would let, e.g., a `2`
    // through, which is the in-memory representation of a `none` `Bool?`.
    int c = fgetc(in);
    if (c != 0 && c != 1) deserialization_failed();
    *(bool *)outval = (bool)c;
}

static void Bool$set_none(void *dest, const TypeInfo_t *type) {
    (void)type;
    *(OptionalBool_t *)dest = NONE_BOOL;
}

public
const TypeInfo_t Bool$info = {
    .size = sizeof(bool),
    .align = __alignof__(bool),
    .metamethods =
        {
            .as_text = Bool$as_text,
            .is_none = Bool$is_none,
            .set_none = Bool$set_none,
            .serialize = Bool$serialize,
            .deserialize = Bool$deserialize,
        },
};