「‍」 Lingenic

smoke_test

(⤓.c ◇.c); γ ≜ [2026-07-11T232921.536, 2026-07-11T232921.536] ∧ |γ| = 1

/*-----------------------------------------------------------------------------
 *  Lingenic-Text — C API smoke test
 *
 *  Exercises every module of the C binding with known-answer checks.
 *
 *  Build (from the repository root):
 *    cc tests/c/smoke_test.c -Iinclude -Llib-c -llingenic_text_c \
 *       -L<gnat adalib> -lgnat -o tests/c/smoke_test
 *  Run:
 *    tests/c/smoke_test <ucd dir>
 *---------------------------------------------------------------------------*/

#include <stdio.h>
#include <string.h>

#include "lingenic_text.h"

static int failures = 0;
static int checks   = 0;

#define CHECK(cond) do {                                              \
    checks++;                                                         \
    if (!(cond)) {                                                    \
        failures++;                                                   \
        fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
    }                                                                 \
} while (0)

int main (int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s <ucd dir>\n", argv[0]);
        return 2;
    }

    /* ---- elaboration + init ---------------------------------------- */
    lingenic_text_cinit();

    CHECK(lt_initialized() == 0);
    CHECK(lt_init(argv[1]) == 0);
    CHECK(lt_initialized() == 1);

    /* ---- UTF-8 ------------------------------------------------------ */
    {
        const uint8_t eacute[] = { 0xC3, 0xA9 };            /* U+00E9 */
        uint32_t cp = 0; size_t sl = 0;
        CHECK(lt_utf8_decode(eacute, 2, 0, &cp, &sl) == 1);
        CHECK(cp == 0xE9 && sl == 2);

        const uint8_t bad[] = { 0xFF };
        CHECK(lt_utf8_decode(bad, 1, 0, &cp, &sl) == 0);
        CHECK(sl == 1);

        uint8_t enc[4]; size_t el = 0;
        CHECK(lt_utf8_encode(0xE9, enc, 4, &el) == 0);
        CHECK(el == 2 && enc[0] == 0xC3 && enc[1] == 0xA9);
        CHECK(lt_utf8_encode(0xD800, enc, 4, &el) == LT_ERR);  /* surrogate */
    }

    /* ---- segmentation ------------------------------------------------ */
    {
        /* "e" + COMBINING ACUTE (U+0301): one grapheme cluster */
        const uint8_t g[] = { 'e', 0xCC, 0x81 };
        size_t np = 0;
        CHECK(lt_next_grapheme_break(g, 3, 0, &np) == 0);
        CHECK(np == 3);

        const uint8_t w[] = "hello world";
        CHECK(lt_next_word_break(w, 11, 0, &np) == 0);
        CHECK(np == 5);

        const uint8_t s[] = "One. Two.";
        CHECK(lt_next_sentence_break(s, 9, 0, &np) == 0);
        CHECK(np == 5);

        const uint8_t l[] = "aa bb";
        CHECK(lt_next_line_break(l, 5, 0, &np) == 0);
        CHECK(np > 0 && np <= 5);
    }

    /* ---- normalization ----------------------------------------------- */
    {
        const uint8_t decomp[] = { 'e', 0xCC, 0x81 };        /* e + U+0301 */
        uint8_t out[16]; size_t ol = 0;
        CHECK(lt_normalize(LT_NFC, decomp, 3, out, 16, &ol) == 0);
        CHECK(ol == 2 && out[0] == 0xC3 && out[1] == 0xA9);  /* é */

        const uint8_t comp[] = { 0xC3, 0xA9 };
        CHECK(lt_normalize(LT_NFD, comp, 2, out, 16, &ol) == 0);
        CHECK(ol == 3 && out[0] == 'e' && out[1] == 0xCC && out[2] == 0x81);

        CHECK(lt_quick_check(LT_NFC, comp, 2) == LT_QC_YES);
        CHECK(lt_is_normalized(LT_NFC, comp, 2) == 1);
        CHECK(lt_is_normalized(LT_NFC, decomp, 3) == 0);

        CHECK(lt_ccc(0x301) == 230);
        CHECK(lt_ccc('A') == 0);
    }

    /* ---- case mapping ------------------------------------------------ */
    {
        uint8_t out[16]; size_t ol = 0;

        const uint8_t sharp_s[] = { 0xC3, 0x9F };            /* ß */
        CHECK(lt_uppercase(sharp_s, 2, out, 16, &ol) == 0);
        CHECK(ol == 2 && out[0] == 'S' && out[1] == 'S');

        const uint8_t abc[] = "ABC";
        CHECK(lt_lowercase(abc, 3, out, 16, &ol) == 0);
        CHECK(ol == 3 && memcmp(out, "abc", 3) == 0);

        const uint8_t hello[] = "hello";
        CHECK(lt_titlecase(hello, 5, out, 16, &ol) == 0);
        CHECK(ol == 5 && memcmp(out, "Hello", 5) == 0);

        const uint8_t mixed[] = "AbC";
        CHECK(lt_casefold(mixed, 3, out, 16, &ol) == 0);
        CHECK(ol == 3 && memcmp(out, "abc", 3) == 0);

        /* output buffer too small */
        CHECK(lt_uppercase(sharp_s, 2, out, 1, &ol) == 1);

        CHECK(lt_is_cased('A') == 1);
        CHECK(lt_is_cased('1') == 0);
        CHECK(lt_is_case_ignorable('\'') == 1);
    }

    /* ---- collation --------------------------------------------------- */
    {
        int r = 99;
        CHECK(lt_collate((const uint8_t*)"a", 1, (const uint8_t*)"b", 1,
                         LT_COLLATE_NON_IGNORABLE, &r) == 0);
        CHECK(r == -1);
        CHECK(lt_collate((const uint8_t*)"a", 1, (const uint8_t*)"a", 1,
                         LT_COLLATE_SHIFTED, &r) == 0);
        CHECK(r == 0);

        uint8_t ka[64], kb[64]; size_t la = 0, lb = 0;
        CHECK(lt_sort_key((const uint8_t*)"a", 1, LT_COLLATE_NON_IGNORABLE,
                          ka, 64, &la) == 0);
        CHECK(lt_sort_key((const uint8_t*)"b", 1, LT_COLLATE_NON_IGNORABLE,
                          kb, 64, &lb) == 0);
        size_t m = la < lb ? la : lb;
        int cmp = memcmp(ka, kb, m);
        CHECK(cmp < 0 || (cmp == 0 && la < lb));
    }

    /* ---- bidi -------------------------------------------------------- */
    {
        const uint8_t ltr[] = "abc";
        uint8_t levels[8]; size_t ncp = 0; uint8_t pl = 9;
        CHECK(lt_bidi_resolve(ltr, 3, LT_DIR_AUTO, levels, 8, &ncp, &pl) == 0);
        CHECK(ncp == 3 && pl == 0);
        CHECK(levels[0] == 0 && levels[1] == 0 && levels[2] == 0);

        uint32_t order[8];
        CHECK(lt_bidi_reorder(levels, 3, pl, order, 8) == 0);
        CHECK(order[0] == 0 && order[1] == 1 && order[2] == 2);

        /* Hebrew alef-bet-gimel: RTL paragraph, reversed visual order */
        const uint8_t heb[] = { 0xD7, 0x90, 0xD7, 0x91, 0xD7, 0x92 };
        CHECK(lt_bidi_resolve(heb, 6, LT_DIR_AUTO, levels, 8, &ncp, &pl) == 0);
        CHECK(ncp == 3 && pl == 1);
        CHECK(lt_bidi_reorder(levels, 3, pl, order, 8) == 0);
        CHECK(order[0] == 2 && order[1] == 1 && order[2] == 0);

        CHECK(lt_bidi_needs_mirror('(', 1) == 1);
        CHECK(lt_bidi_needs_mirror('(', 0) == 0);
        CHECK(lt_bidi_mirrored('(') == 1);
        CHECK(lt_bidi_mirrored('A') == 0);
    }

    /* ---- east asian width -------------------------------------------- */
    CHECK(lt_display_width('A') == 1);
    CHECK(lt_display_width(0x4E00) == 2);          /* CJK ideograph */
    CHECK(lt_east_asian_width(0x4E00) == LT_EAW_WIDE);
    CHECK(lt_east_asian_width('A') == LT_EAW_NARROW);

    /* ---- emoji -------------------------------------------------------- */
    {
        CHECK(lt_is_emoji(0x1F600) == 1);          /* grinning face */
        CHECK(lt_is_emoji('A') == 0);
        CHECK(lt_is_emoji_modifier(0x1F3FB) == 1); /* skin tone */
        CHECK(lt_is_emoji_component(0x20E3) == 1); /* keycap */
        CHECK(lt_extended_pictographic(0x1F600) == 1);

        const uint8_t grin[] = { 0xF0, 0x9F, 0x98, 0x80 };   /* U+1F600 */
        CHECK(lt_emoji_classify(grin, 4) == LT_EMOJI_CHARACTER);

        /* U+1F1FA U+1F1F8 = flag US */
        const uint8_t flag[] = { 0xF0, 0x9F, 0x87, 0xBA, 0xF0, 0x9F, 0x87, 0xB8 };
        CHECK(lt_emoji_classify(flag, 8) == LT_EMOJI_FLAG_SEQ);

        CHECK(lt_emoji_classify((const uint8_t*)"hi", 2) == LT_EMOJI_NOT_EMOJI);
    }

    /* ---- identifiers -------------------------------------------------- */
    CHECK(lt_is_identifier_start('A') == 1);
    CHECK(lt_is_identifier_start('1') == 0);
    CHECK(lt_is_identifier_part('1') == 1);
    CHECK(lt_is_identifier_part(' ') == 0);

    /* ---- IDNA ---------------------------------------------------------- */
    {
        /* "bücher.de" -> "xn--bcher-kva.de" */
        const uint8_t dom[] = { 'b', 0xC3, 0xBC, 'c', 'h', 'e', 'r',
                                '.', 'd', 'e' };
        uint8_t out[256]; size_t ol = 0;
        CHECK(lt_idna_to_ascii(dom, 10, LT_IDNA_DEFAULT, out, 256, &ol)
              == LT_IDNA_SUCCESS);
        CHECK(ol == 16 && memcmp(out, "xn--bcher-kva.de", 16) == 0);

        const uint8_t ace[] = "xn--bcher-kva.de";
        CHECK(lt_idna_to_unicode(ace, 16, LT_IDNA_DEFAULT, out, 256, &ol)
              == LT_IDNA_SUCCESS);
        CHECK(ol == 10 && memcmp(out, dom, 10) == 0);
    }

    /* ---- properties ---------------------------------------------------- */
    {
        char name[64];

        int scr = lt_script('A');
        CHECK(scr >= 0);
        CHECK(lt_script_count() > 0 && scr < lt_script_count());
        CHECK(lt_script_name(scr, name, 64) == 5);
        CHECK(strcmp(name, "Latin") == 0);
        CHECK(lt_in_script_extensions('A', scr) == 1);
        CHECK(lt_script_extension_count('A') >= 1);
        CHECK(lt_script_extension('A', 0) >= 0);

        int gc = lt_general_category('A');
        CHECK(gc >= 0);
        CHECK(lt_general_category_count() > 0 && gc < lt_general_category_count());
        CHECK(lt_general_category_name(gc, name, 64) == 2);
        CHECK(strcmp(name, "Lu") == 0);

        CHECK(lt_grapheme_break_property(0x0D) == LT_GBP_CR);
        CHECK(lt_grapheme_break_property(0x200D) == LT_GBP_ZWJ);
        CHECK(lt_word_break_property('a') == LT_WBP_ALETTER);
        CHECK(lt_sentence_break_property('a') == LT_SBP_LOWER);
        CHECK(lt_line_break_property(0x0A) == LT_LBP_LF);
        CHECK(lt_line_break_property(' ') == LT_LBP_SP);
        CHECK(lt_bidi_class('A') == LT_BC_L);
        CHECK(lt_bidi_class(0x05D0) == LT_BC_R);   /* Hebrew alef */
        CHECK(lt_joining_type(0x0628) == LT_JT_D); /* Arabic beh */
        CHECK(lt_joining_type('A') == LT_JT_U);
        CHECK(lt_indic_conjunct_break(0x094D) == LT_INCB_LINKER);
        CHECK(lt_indic_conjunct_break('A') == LT_INCB_NONE);
    }

    /* ---- argument validation ------------------------------------------- */
    CHECK(lt_utf8_decode(NULL, 1, 0, NULL, NULL) == LT_ERR);
    CHECK(lt_normalize(7, (const uint8_t*)"a", 1, NULL, 0, NULL) == LT_ERR);
    CHECK(lt_ccc(0x110000) == LT_ERR);
    CHECK(lt_display_width(0xFFFFFFFFu) == LT_ERR);

    lingenic_text_cfinal();

    printf("%d checks, %d failures\n", checks, failures);
    return failures == 0 ? 0 : 1;
}