From 42bfb6506db5bf77ca2bf0774a957c8c9391e1f9 Mon Sep 17 00:00:00 2001 From: Pierre MARTIN Date: Mon, 2 Nov 2026 13:56:29 +0100 Subject: [PATCH] =?UTF-8?q?Couche=20de=20compatibilit=C3=A9=20:=20shim=20G?= =?UTF-8?q?Lib=20et=20en-t=C3=AAtes=20r=C3=A9duits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit engine/compat/ remplace ce que le cœur de gnubg attend du reste de gnubg et de GLib : config.h écrit à la main (sans threads, C générique, NEON prévu), shim GLib minimal (dont un analyseur XML pour les tables d'équité et mmap pour les bases de fin de partie), en-têtes réduits et fonctions de support. Co-Authored-By: Claude Opus 5.5 --- engine/compat/backgammon.h | 23 + engine/compat/config.h | 58 +++ engine/compat/external.h | 79 +++ engine/compat/format.h | 14 + engine/compat/glib-shim.c | 658 +++++++++++++++++++++++++ engine/compat/glib-shim/glib.h | 212 ++++++++ engine/compat/glib-shim/glib/gi18n.h | 20 + engine/compat/glib-shim/glib/gprintf.h | 14 + engine/compat/glib-shim/glib/gstdio.h | 18 + engine/compat/gnubg-support.c | 64 +++ engine/compat/gnubg-support.h | 14 + engine/compat/host-tools-support.c | 38 ++ engine/compat/multithread.h | 64 +++ engine/compat/output.h | 21 + engine/compat/util.h | 25 + 15 files changed, 1322 insertions(+) create mode 100644 engine/compat/backgammon.h create mode 100644 engine/compat/config.h create mode 100644 engine/compat/external.h create mode 100644 engine/compat/format.h create mode 100644 engine/compat/glib-shim.c create mode 100644 engine/compat/glib-shim/glib.h create mode 100644 engine/compat/glib-shim/glib/gi18n.h create mode 100644 engine/compat/glib-shim/glib/gprintf.h create mode 100644 engine/compat/glib-shim/glib/gstdio.h create mode 100644 engine/compat/gnubg-support.c create mode 100644 engine/compat/gnubg-support.h create mode 100644 engine/compat/host-tools-support.c create mode 100644 engine/compat/multithread.h create mode 100644 engine/compat/output.h create mode 100644 engine/compat/util.h diff --git a/engine/compat/backgammon.h b/engine/compat/backgammon.h new file mode 100644 index 0000000..c9c6b78 --- /dev/null +++ b/engine/compat/backgammon.h @@ -0,0 +1,23 @@ +/* + * backgammon.h -- en-tête réduit remplaçant celui de GNU Backgammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * Le backgammon.h d'origine déclare l'état complet de l'application de + * bureau (match en cours, commandes, réglages d'interface). Le cœur repris + * dans engine/gnubg/ n'en utilise qu'une petite partie, déclarée ici avec + * les mêmes noms et les mêmes types que dans l'original (commit 14b6798). + * Les définitions correspondantes sont dans engine/compat/gnubg-support.c. + */ + +#ifndef BACKGAMMON_H +#define BACKGAMMON_H + +#include "common.h" +#include "output.h" +#include "eval.h" + +#include + +#endif diff --git a/engine/compat/config.h b/engine/compat/config.h new file mode 100644 index 0000000..d51714b --- /dev/null +++ b/engine/compat/config.h @@ -0,0 +1,58 @@ +/* + * config.h -- configuration de compilation du moteur embarqué d'Ustagammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * Remplace le config.h que produit le configure de GNU Backgammon. Seules les + * macros lues par les fichiers repris dans engine/gnubg/ sont définies ; les + * valeurs suivent ce que configure.ac choisit sur une Debian récente avec gcc. + */ + +#ifndef USTAGAMMON_CONFIG_H +#define USTAGAMMON_CONFIG_H + +#define VERSION "1.08.003" +#define PACKAGE "gnubg" +#define GETTEXT_PACKAGE "gnubg" + +/* Compilateur : gcc ou clang, qui comprennent tous deux ces extensions. */ +#define HAVE___ATTRIBUTE__ 1 +#define HAVE_FUNC_ATTRIBUTE_CONST 1 +#define HAVE_FUNC_ATTRIBUTE_FALLTHROUGH 1 +#define HAVE_FUNC_ATTRIBUTE_FORMAT 1 +#define HAVE_FUNC_ATTRIBUTE_PURE 1 +#define HAVE_FUNC_ATTRIBUTE_UNUSED 1 +#define HAVE___BUILTIN_EXPECT 1 +#define HAVE___BUILTIN_CLZ 1 + +/* Bibliothèque C POSIX (glibc, bionic d'Android, musl d'Emscripten). */ +#define HAVE_UNISTD_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_POSIX_MEMALIGN 1 + +/* + * Pas de threads : une seule instance du moteur, appels sérialisés + * (ADR 0005). USE_MULTITHREAD reste donc non défini. + */ + +/* + * Instructions SIMD. Par défaut, C générique partout (x86 compris), ce qui + * est aussi le seul chemin possible en WebAssembly. + * + * Chemin NEON prévu pour arm64 (Android), activé en compilant avec + * -DUSTAGAMMON_NEON ; il reprend les définitions que configure produit avec + * --enable-simd=neon. Le test d'instructions à l'exécution est inutile : + * NEON est obligatoire sur arm64. + */ +#if defined(USTAGAMMON_NEON) +#if !defined(__aarch64__) +#error "USTAGAMMON_NEON n'est prévu que pour arm64" +#endif +#define USE_SIMD_INSTRUCTIONS 1 +#define USE_NEON 1 +#define HAVE_NEON 1 +#define DISABLE_SIMD_TEST 1 +#endif + +#endif /* USTAGAMMON_CONFIG_H */ diff --git a/engine/compat/external.h b/engine/compat/external.h new file mode 100644 index 0000000..30ab84e --- /dev/null +++ b/engine/compat/external.h @@ -0,0 +1,79 @@ +/* + * external.h -- en-tête réduit remplaçant celui de GNU Backgammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Les deux structures ci-dessous sont reprises telles quelles de external.h + * de GNU Backgammon (commit 14b6798, Copyright (C) 2001-2025 the AUTHORS). + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * drawboard.c (repris pour FormatMove, la notation des coups) inclut + * external.h pour ces deux types, utilisés par ProcessFIBSBoardInfo. Le + * reste de l'interface externe de gnubg (sockets) n'a pas lieu d'être ici. + */ + +#ifndef EXTERNAL_H +#define EXTERNAL_H + +#include +#include "backgammon.h" + +#define MAX_NAME_LEN 32 + +typedef struct { + /* + * These must be in the same order than the FIBS board string definition, + * starting at Match Length. + * See description at http://www.fibs.com/fibs_interface.html#board_state + */ + int nMatchTo; + int nScore; + int nScoreOpp; + int anFIBSBoard[26]; + int nTurn; + int anDice[2]; + int anOppDice[2]; + int nCube; + int fCanDouble; + int fOppCanDouble; + int fDoubled; + int nColor; + int nDirection; + int nHome; /* unused, set to 0 */ + int nBar; /* unused, set to 25 */ + int nOnHome; /* unused */ + int nOnHomeOpp; /* unused */ + int nOnBar; /* unused */ + int nOnBarOpp; /* unused */ + int nCanMove; /* unused */ + int fForcedMOve; /* unused */ + int fPostCrawford; + /* + * Redoubles from FIBS board is not usable by GNUbg. + * A flag for match play is put at the same place + */ + int fNonCrawford; + int nVersion; + int padding[51]; + + /* These must be last */ + GString *gsName; + GString *gsOpp; +} FIBSBoardInfo; + +typedef struct { + char szPlayer[MAX_NAME_LEN]; + char szOpp[MAX_NAME_LEN]; + int nMatchTo; + int nScore; + int nScoreOpp; + int anDice[2]; + int nCube; + int fCubeOwner; + int fDoubled; + int fCrawford; + int fJacoby; + int nResignation; + TanBoard anBoard; +} ProcessedFIBSBoard; + +#endif diff --git a/engine/compat/format.h b/engine/compat/format.h new file mode 100644 index 0000000..1d49a34 --- /dev/null +++ b/engine/compat/format.h @@ -0,0 +1,14 @@ +/* + * format.h -- en-tête réduit remplaçant celui de GNU Backgammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * eval.c inclut format.h sans utiliser ses fonctions d'affichage dans les + * parties reprises : l'en-tête est volontairement vide. + */ + +#ifndef FORMAT_H +#define FORMAT_H + +#endif diff --git a/engine/compat/glib-shim.c b/engine/compat/glib-shim.c new file mode 100644 index 0000000..d436a4d --- /dev/null +++ b/engine/compat/glib-shim.c @@ -0,0 +1,658 @@ +/* + * glib-shim.c -- implémentation du shim minimal de GLib (voir glib-shim/glib.h). + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * Écrit pour Ustagammon d'après la documentation publique de GLib ; aucun code + * de GLib n'est repris. + */ + +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "glib.h" + +/* ---------------------------------------------------------------- Erreurs */ + +void +ustagammon_g_assert_failed(const char *expr, const char *file, int line) +{ + fprintf(stderr, "moteur : assertion « %s » fausse (%s:%d)\n", expr, file, line); + abort(); +} + +/* Crée une GError dont le message est « message : detail ». */ +static GError * +new_error(gint code, const char *message, const char *detail) +{ + GError *e = g_new0(GError, 1); + size_t size = strlen(message) + strlen(detail) + 4; + + e->code = code; + e->message = g_malloc(size); + snprintf(e->message, size, "%s : %s", message, detail); + return e; +} + +static void +set_error(GError **error, gint code, const char *message, const char *detail) +{ + if (error) + *error = new_error(code, message, detail); +} + +void +g_error_free(GError *error) +{ + if (!error) + return; + g_free(error->message); + g_free(error); +} + +/* ---------------------------------------------------------------- Mémoire */ + +static gpointer +check_allocation(gpointer p, gsize n) +{ + if (!p && n) { + fprintf(stderr, "moteur : échec d'allocation de %zu octets\n", n); + abort(); + } + return p; +} + +gpointer +g_malloc(gsize n) +{ + return n ? check_allocation(malloc(n), n) : NULL; +} + +gpointer +g_malloc0(gsize n) +{ + return n ? check_allocation(calloc(1, n), n) : NULL; +} + +void +g_free(gpointer p) +{ + free(p); +} + +gpointer +g_memdup2(gconstpointer mem, gsize n) +{ + gpointer p; + + if (!mem || !n) + return NULL; + p = g_malloc(n); + memcpy(p, mem, n); + return p; +} + +gchar * +g_strdup(const gchar *s) +{ + return s ? (gchar *) g_memdup2(s, strlen(s) + 1) : NULL; +} + +/* --------------------------------------------------------------- Messages */ + +/* Tout part sur la sortie d'erreur : la sortie standard appartient à + * l'application (JSON d'engine-cli). */ + +void +g_print(const gchar *format, ...) +{ + va_list args; + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +void +g_printerr(const gchar *format, ...) +{ + va_list args; + + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +void +g_warning(const gchar *format, ...) +{ + va_list args; + + fputs("moteur (avertissement) : ", stderr); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fputc('\n', stderr); +} + +void +g_error(const gchar *format, ...) +{ + va_list args; + + fputs("moteur (erreur fatale) : ", stderr); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fputc('\n', stderr); + abort(); +} + +/* ---------------------------------------------------------------- Chaînes */ + +/* + * strtod indépendant de la locale : le point est toujours le séparateur + * décimal. Si la locale courante utilise un autre caractère (la virgule en + * français), on le substitue au point dans une copie avant d'appeler strtod. + */ +gdouble +g_ascii_strtod(const gchar *nptr, gchar **endptr) +{ + const char *point = localeconv()->decimal_point; + char *copy, *end; + double value; + size_t i; + + if (!point || strlen(point) != 1 || point[0] == '.') + return strtod(nptr, endptr); + + copy = g_strdup(nptr); + for (i = 0; copy[i]; i++) + if (copy[i] == '.') + copy[i] = point[0]; + value = strtod(copy, &end); + if (endptr) + *endptr = (gchar *) nptr + (end - copy); + g_free(copy); + return value; +} + +guint64 +g_ascii_strtoull(const gchar *nptr, gchar **endptr, guint base) +{ + return (guint64) strtoull(nptr, endptr, (int) base); +} + +static int +ascii_tolower(int c) +{ + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} + +gint +g_ascii_strncasecmp(const gchar *s1, const gchar *s2, gsize n) +{ + for (; n > 0; n--, s1++, s2++) { + int c1 = ascii_tolower((unsigned char) *s1); + int c2 = ascii_tolower((unsigned char) *s2); + if (c1 != c2 || !c1) + return c1 - c2; + } + return 0; +} + +gint +g_ascii_strcasecmp(const gchar *s1, const gchar *s2) +{ + return g_ascii_strncasecmp(s1, s2, (gsize) -1); +} + +const gchar * +g_strerror(gint errnum) +{ + return strerror(errnum); +} + +/* Copie tronquée et toujours terminée ; renvoie la longueur de src. */ +gsize +g_strlcpy(gchar *dest, const gchar *src, gsize dest_size) +{ + gsize len = strlen(src); + + if (dest_size > 0) { + gsize n = len < dest_size - 1 ? len : dest_size - 1; + memcpy(dest, src, n); + dest[n] = '\0'; + } + return len; +} + +/* Assemble des morceaux de chemin avec « / », sans doubler le séparateur. */ +gchar * +g_build_filename(const gchar *first, ...) +{ + va_list args; + const gchar *part; + size_t total = 1; + gchar *result; + + va_start(args, first); + for (part = first; part; part = va_arg(args, const gchar *)) + total += strlen(part) + 1; + va_end(args); + + result = g_malloc(total); + result[0] = '\0'; + va_start(args, first); + for (part = first; part; part = va_arg(args, const gchar *)) { + size_t len = strlen(result); + if (!*part) + continue; + if (len > 0 && result[len - 1] != '/') + strcat(result, "/"); + if (len > 0 && *part == '/') + part++; + strcat(result, part); + } + va_end(args); + return result; +} + +/* --------------------------------------------------------------- Fichiers */ + +gboolean +g_file_test(const gchar *filename, GFileTest test) +{ + struct stat st; + + if (stat(filename, &st) != 0) + return FALSE; + if (test & G_FILE_TEST_EXISTS) + return TRUE; + if ((test & G_FILE_TEST_IS_REGULAR) && S_ISREG(st.st_mode)) + return TRUE; + if ((test & G_FILE_TEST_IS_DIR) && S_ISDIR(st.st_mode)) + return TRUE; + if ((test & G_FILE_TEST_IS_EXECUTABLE) && access(filename, X_OK) == 0) + return TRUE; + return FALSE; +} + +/* Lit un fichier entier en mémoire, terminé par un octet nul. */ +gboolean +g_file_get_contents(const gchar *filename, gchar **contents, gsize *length, GError **error) +{ + FILE *f; + long size; + gchar *buffer; + + *contents = NULL; + f = fopen(filename, "rb"); + if (!f) { + set_error(error, errno, "lecture impossible", filename); + return FALSE; + } + if (fseek(f, 0, SEEK_END) != 0 || (size = ftell(f)) < 0 || fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + set_error(error, EIO, "taille illisible", filename); + return FALSE; + } + buffer = g_malloc((size_t) size + 1); + if (fread(buffer, 1, (size_t) size, f) != (size_t) size) { + fclose(f); + g_free(buffer); + set_error(error, EIO, "lecture incomplète", filename); + return FALSE; + } + fclose(f); + buffer[size] = '\0'; + *contents = buffer; + if (length) + *length = (gsize) size; + return TRUE; +} + +/* Fichier projeté en mémoire en lecture seule (mmap). */ +struct UstagammonMappedFile { + gchar *contents; + gsize length; +}; + +GMappedFile * +g_mapped_file_new(const gchar *filename, gboolean writable, GError **error) +{ + GMappedFile *m; + struct stat st; + void *p = NULL; + int fd; + + if (writable) { + set_error(error, EINVAL, "projection en écriture non prise en charge", filename); + return NULL; + } + fd = open(filename, O_RDONLY); + if (fd < 0) { + set_error(error, errno, "ouverture impossible", filename); + return NULL; + } + if (fstat(fd, &st) != 0) { + set_error(error, errno, "taille illisible", filename); + close(fd); + return NULL; + } + if (st.st_size > 0) { + p = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (p == MAP_FAILED) { + set_error(error, errno, "projection impossible", filename); + close(fd); + return NULL; + } + } + close(fd); + m = g_new0(GMappedFile, 1); + m->contents = p; + m->length = (gsize) st.st_size; + return m; +} + +gchar * +g_mapped_file_get_contents(GMappedFile *file) +{ + return file->contents; +} + +gsize +g_mapped_file_get_length(GMappedFile *file) +{ + return file->length; +} + +void +g_mapped_file_unref(GMappedFile *file) +{ + if (!file) + return; + if (file->contents) + munmap(file->contents, file->length); + g_free(file); +} + +/* ------------------------------------------------------------- XML simple */ + +/* + * Analyseur XML réduit à ce qu'utilisent les tables d'équité de gnubg + * (fichiers XML de engine/data/) : éléments, attributs entre guillemets, texte, + * commentaires, prologue et sans sous-ensemble interne, + * entités < > & " '. Tout le reste est une erreur. + */ + +struct UstagammonMarkupParseContext { + const GMarkupParser *parser; + gpointer user_data; + GDestroyNotify user_data_dnotify; +}; + +GMarkupParseContext * +g_markup_parse_context_new(const GMarkupParser *parser, GMarkupParseFlags flags, + gpointer user_data, GDestroyNotify user_data_dnotify) +{ + GMarkupParseContext *c = g_new0(GMarkupParseContext, 1); + + (void) flags; + c->parser = parser; + c->user_data = user_data; + c->user_data_dnotify = user_data_dnotify; + return c; +} + +void +g_markup_parse_context_free(GMarkupParseContext *context) +{ + if (!context) + return; + if (context->user_data_dnotify) + context->user_data_dnotify(context->user_data); + g_free(context); +} + +#define MAX_DEPTH 32 +#define MAX_ATTRIBUTES 8 + +/* Copie [start, end) en décodant les entités ; NULL si une entité est inconnue. */ +static gchar * +decode_text(const char *start, const char *end) +{ + static const struct { + const char *name; + char c; + } entities[] = { + { "<", '<' }, { ">", '>' }, { "&", '&' }, { """, '"' }, { "'", '\'' } + }; + gchar *out = g_malloc((size_t) (end - start) + 1); + gchar *o = out; + const char *p = start; + + while (p < end) { + size_t i, n = sizeof entities / sizeof entities[0]; + if (*p != '&') { + *o++ = *p++; + continue; + } + for (i = 0; i < n; i++) { + size_t len = strlen(entities[i].name); + if ((size_t) (end - p) >= len && strncmp(p, entities[i].name, len) == 0) { + *o++ = entities[i].c; + p += len; + break; + } + } + if (i == n) { + g_free(out); + return NULL; + } + } + *o = '\0'; + return out; +} + +static int +is_name_char(char c) +{ + return isalnum((unsigned char) c) || c == '-' || c == '_' || c == ':' || c == '.'; +} + +static const char * +skip_spaces(const char *p, const char *end) +{ + while (p < end && isspace((unsigned char) *p)) + p++; + return p; +} + +/* Copie le nom qui commence en p ; avance *after juste derrière. */ +static gchar * +read_name(const char *p, const char *end, const char **after) +{ + const char *q = p; + gchar *name; + + while (q < end && is_name_char(*q)) + q++; + *after = q; + if (q == p) + return NULL; + name = g_malloc((size_t) (q - p) + 1); + memcpy(name, p, (size_t) (q - p)); + name[q - p] = '\0'; + return name; +} + +/* + * Lit les attributs d'une balise ouvrante jusqu'à « > » ou « /> ». + * Renvoie la position après la balise, ou NULL en cas d'erreur. + */ +static const char * +read_attributes(const char *p, const char *end, gchar **names, gchar **values, int *empty) +{ + int n = 0; + + names[0] = values[0] = NULL; + for (;;) { + const char *value_start; + char quote; + + p = skip_spaces(p, end); + if (p >= end) + return NULL; + if (*p == '>') + return p + 1; + if (*p == '/' && p + 1 < end && p[1] == '>') { + *empty = 1; + return p + 2; + } + if (n == MAX_ATTRIBUTES) + return NULL; + names[n] = read_name(p, end, &p); + names[n + 1] = values[n] = values[n + 1] = NULL; + if (!names[n]) + return NULL; + p = skip_spaces(p, end); + if (p >= end || *p != '=') + return NULL; + p = skip_spaces(p + 1, end); + if (p >= end || (*p != '"' && *p != '\'')) + return NULL; + quote = *p++; + value_start = p; + while (p < end && *p != quote) + p++; + if (p >= end) + return NULL; + values[n] = decode_text(value_start, p); + if (!values[n]) + return NULL; + n++; + p++; + } +} + +static void +free_strings(gchar **strings) +{ + for (; *strings; strings++) + g_free(*strings); +} + +gboolean +g_markup_parse_context_parse(GMarkupParseContext *context, const gchar *text, + gssize text_len, GError **error) +{ + const GMarkupParser *mp = context->parser; + const char *p = text; + const char *end = text + (text_len < 0 ? (gssize) strlen(text) : text_len); + gchar *stack[MAX_DEPTH]; + int depth = 0; + const char *problem = NULL; + GError *err = NULL; + + while (p < end && !problem && !err) { + if (*p != '<') { + /* Texte jusqu'à la prochaine balise. */ + const char *start = p; + gchar *t; + while (p < end && *p != '<') + p++; + if (depth == 0) { + if (skip_spaces(start, p) != p) + problem = "texte hors de l'élément racine"; + continue; + } + t = decode_text(start, p); + if (!t) + problem = "entité inconnue"; + else if (mp->text) + mp->text(context, t, strlen(t), context->user_data, &err); + g_free(t); + } else if (end - p >= 4 && strncmp(p, "", 3) != 0) + q++; + if (q + 3 > end) + problem = "commentaire non terminé"; + p = q + 3; + } else if (end - p >= 2 && (p[1] == '?' || p[1] == '!')) { + const char *q = memchr(p, '>', (size_t) (end - p)); + if (!q || memchr(p, '[', (size_t) (q - p))) + problem = "déclaration non prise en charge"; + else + p = q + 1; + } else if (end - p >= 2 && p[1] == '/') { + /* Balise fermante. */ + gchar *name = read_name(p + 2, end, &p); + p = skip_spaces(p, end); + if (!name || p >= end || *p != '>') + problem = "balise fermante mal formée"; + else if (depth == 0 || strcmp(stack[depth - 1], name) != 0) + problem = "balise fermante inattendue"; + else { + depth--; + if (mp->end_element) + mp->end_element(context, name, context->user_data, &err); + g_free(stack[depth]); + p++; + } + g_free(name); + } else { + /* Balise ouvrante, éventuellement vide (). */ + gchar *names[MAX_ATTRIBUTES + 1], *values[MAX_ATTRIBUTES + 1]; + int empty = 0; + gchar *name = read_name(p + 1, end, &p); + names[0] = values[0] = NULL; + if (name) + p = read_attributes(p, end, names, values, &empty); + if (!name || !p) + problem = "balise ouvrante mal formée"; + else if (depth == MAX_DEPTH) + problem = "imbrication trop profonde"; + else { + if (mp->start_element) + mp->start_element(context, name, (const gchar **) names, + (const gchar **) values, context->user_data, &err); + if (!err && empty && mp->end_element) + mp->end_element(context, name, context->user_data, &err); + } + free_strings(names); + free_strings(values); + if (!problem && !err && !empty) + stack[depth++] = name; + else + g_free(name); + } + } + + if (!problem && !err && depth > 0) + problem = "document incomplet"; + while (depth > 0) + g_free(stack[--depth]); + if (problem) + err = new_error(0, "XML invalide", problem); + if (err) { + if (mp->error) + mp->error(context, err, context->user_data); + if (error) + *error = err; + else + g_error_free(err); + return FALSE; + } + return TRUE; +} diff --git a/engine/compat/glib-shim/glib.h b/engine/compat/glib-shim/glib.h new file mode 100644 index 0000000..5739c95 --- /dev/null +++ b/engine/compat/glib-shim/glib.h @@ -0,0 +1,212 @@ +/* + * glib.h -- shim minimal de GLib pour le moteur embarqué d'Ustagammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * Le cœur de GNU Backgammon repris dans engine/gnubg/ utilise une petite + * partie de GLib. Plutôt que d'embarquer GLib entière (taille, portage Android + * et WebAssembly), ce fichier en fournit le sous-ensemble utilisé, avec le + * même comportement observable pour le moteur. Implémentation : + * engine/compat/glib-shim.c. + * + * Écarts assumés avec GLib : + * - g_print écrit sur la sortie d'erreur (et non la sortie standard), pour ne + * jamais mêler de messages de diagnostic à la sortie JSON d'engine-cli ; + * - GMarkup ne reconnaît que le XML simple des tables d'équité de match + * (éléments, attributs, texte, commentaires, prologue, DOCTYPE, entités + * prédéfinies et numériques) ; pas de CDATA ni d'instructions dans le texte ; + * - g_error et l'échec d'allocation interrompent le programme (abort), comme + * GLib : ces chemins signalent des erreurs internes, pas des erreurs + * d'utilisation ; + * - ni threads, ni atomiques, ni listes GLib : le moteur est compilé sans + * USE_MULTITHREAD. + * + * Ce fichier ne sert qu'au moteur embarqué. Les outils de génération de + * données (makeweights, makebearoff) sont compilés avec la vraie GLib. + */ + +#ifndef USTAGAMMON_GLIB_SHIM_H +#define USTAGAMMON_GLIB_SHIM_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Types de base. */ +typedef char gchar; +typedef unsigned char guchar; +typedef int gint; +typedef unsigned int guint; +typedef int gboolean; +typedef short gshort; +typedef unsigned short gushort; +typedef long glong; +typedef unsigned long gulong; +typedef float gfloat; +typedef double gdouble; +typedef void *gpointer; +typedef const void *gconstpointer; +typedef size_t gsize; +typedef ptrdiff_t gssize; +typedef int8_t gint8; +typedef uint8_t guint8; +typedef int16_t gint16; +typedef uint16_t guint16; +typedef int32_t gint32; +typedef uint32_t guint32; +typedef int64_t gint64; +typedef uint64_t guint64; + +#ifndef FALSE +#define FALSE (0) +#endif +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#define G_PI 3.1415926535897932384626433832795028841971693993751 +#define G_PI_2 1.5707963267948966192313216916397514420985846996876 +#define G_GSIZE_FORMAT "zu" +#define G_STRINGIFY_ARG(x) #x +#define G_STRINGIFY(x) G_STRINGIFY_ARG(x) +#define G_STRLOC __FILE__ ":" G_STRINGIFY(__LINE__) + +/* + * Version de GLib imitée : 2.84 (celle de Debian trixie), pour que le code + * de gnubg choisisse les mêmes branches qu'avec la vraie bibliothèque + * (g_memdup2 plutôt que g_memdup, par exemple). + */ +#define GLIB_MAJOR_VERSION 2 +#define GLIB_MINOR_VERSION 84 +#define GLIB_MICRO_VERSION 0 +#define GLIB_CHECK_VERSION(major, minor, micro) \ + (GLIB_MAJOR_VERSION > (major) || \ + (GLIB_MAJOR_VERSION == (major) && GLIB_MINOR_VERSION > (minor)) || \ + (GLIB_MAJOR_VERSION == (major) && GLIB_MINOR_VERSION == (minor) && \ + GLIB_MICRO_VERSION >= (micro))) + +/* Assertions : désactivées par G_DISABLE_ASSERT, comme dans le build officiel. */ +void ustagammon_g_assert_failed(const char *expr, const char *file, int line); + +#if defined(G_DISABLE_ASSERT) +#define g_assert(expr) ((void) 0) +#define g_assert_not_reached() ((void) 0) +#else +#define g_assert(expr) \ + ((expr) ? (void) 0 : ustagammon_g_assert_failed(#expr, __FILE__, __LINE__)) +#define g_assert_not_reached() \ + ustagammon_g_assert_failed("code inatteignable", __FILE__, __LINE__) +#endif + +#define g_return_val_if_fail(expr, val) \ + do { if (!(expr)) { \ + g_warning("%s : condition « %s » fausse", G_STRLOC, #expr); \ + return (val); } } while (0) +#define g_return_if_fail(expr) \ + do { if (!(expr)) { \ + g_warning("%s : condition « %s » fausse", G_STRLOC, #expr); \ + return; } } while (0) + +/* Mémoire. */ +gpointer g_malloc(gsize n); +gpointer g_malloc0(gsize n); +void g_free(gpointer p); +gpointer g_memdup2(gconstpointer mem, gsize n); +gchar *g_strdup(const gchar *s); +#define g_new(type, n) ((type *) g_malloc(sizeof(type) * (gsize) (n))) +#define g_new0(type, n) ((type *) g_malloc0(sizeof(type) * (gsize) (n))) +#define g_alloca(size) alloca(size) + +/* Messages. */ +void g_print(const gchar *format, ...) __attribute__((format(printf, 1, 2))); +void g_printerr(const gchar *format, ...) __attribute__((format(printf, 1, 2))); +void g_warning(const gchar *format, ...) __attribute__((format(printf, 1, 2))); +void g_error(const gchar *format, ...) __attribute__((format(printf, 1, 2), noreturn)); + +/* Chaînes. */ +gdouble g_ascii_strtod(const gchar *nptr, gchar **endptr); +guint64 g_ascii_strtoull(const gchar *nptr, gchar **endptr, guint base); +gint g_ascii_strcasecmp(const gchar *s1, const gchar *s2); +gint g_ascii_strncasecmp(const gchar *s1, const gchar *s2, gsize n); +const gchar *g_strerror(gint errnum); +gchar *g_build_filename(const gchar *first, ...); +gsize g_strlcpy(gchar *dest, const gchar *src, gsize dest_size); + +/* GString : seul le type est utilisé (structures FIBS de drawboard.c). */ +typedef struct { + gchar *str; + gsize len; + gsize allocated_len; +} GString; + +/* Erreurs. */ +typedef guint32 GQuark; +typedef struct { + GQuark domain; + gint code; + gchar *message; +} GError; + +void g_error_free(GError *error); + +/* Fichiers. */ +typedef enum { + G_FILE_TEST_IS_REGULAR = 1 << 0, + G_FILE_TEST_IS_SYMLINK = 1 << 1, + G_FILE_TEST_IS_DIR = 1 << 2, + G_FILE_TEST_IS_EXECUTABLE = 1 << 3, + G_FILE_TEST_EXISTS = 1 << 4 +} GFileTest; + +gboolean g_file_test(const gchar *filename, GFileTest test); +gboolean g_file_get_contents(const gchar *filename, gchar **contents, gsize *length, GError **error); + +/* Fichier projeté en mémoire (bases de fin de partie .bd). */ +typedef struct UstagammonMappedFile GMappedFile; + +GMappedFile *g_mapped_file_new(const gchar *filename, gboolean writable, GError **error); +gchar *g_mapped_file_get_contents(GMappedFile *file); +gsize g_mapped_file_get_length(GMappedFile *file); +void g_mapped_file_unref(GMappedFile *file); + +/* Analyseur XML simple (tables d'équité de match). */ +typedef struct UstagammonMarkupParseContext GMarkupParseContext; +typedef void (*GDestroyNotify)(gpointer data); + +typedef enum { + G_MARKUP_DO_NOT_USE_THIS_UNSUPPORTED_FLAG = 1 << 0, + G_MARKUP_TREAT_CDATA_AS_TEXT = 1 << 1, + G_MARKUP_PREFIX_ERROR_POSITION = 1 << 2, + G_MARKUP_IGNORE_QUALIFIED = 1 << 3 +} GMarkupParseFlags; + +typedef struct { + void (*start_element)(GMarkupParseContext *context, const gchar *element_name, + const gchar **attribute_names, const gchar **attribute_values, + gpointer user_data, GError **error); + void (*end_element)(GMarkupParseContext *context, const gchar *element_name, + gpointer user_data, GError **error); + void (*text)(GMarkupParseContext *context, const gchar *text, gsize text_len, + gpointer user_data, GError **error); + void (*passthrough)(GMarkupParseContext *context, const gchar *passthrough_text, + gsize text_len, gpointer user_data, GError **error); + void (*error)(GMarkupParseContext *context, GError *error, gpointer user_data); +} GMarkupParser; + +GMarkupParseContext *g_markup_parse_context_new(const GMarkupParser *parser, GMarkupParseFlags flags, + gpointer user_data, GDestroyNotify user_data_dnotify); +gboolean g_markup_parse_context_parse(GMarkupParseContext *context, const gchar *text, + gssize text_len, GError **error); +void g_markup_parse_context_free(GMarkupParseContext *context); + +#endif /* USTAGAMMON_GLIB_SHIM_H */ diff --git a/engine/compat/glib-shim/glib/gi18n.h b/engine/compat/glib-shim/glib/gi18n.h new file mode 100644 index 0000000..68066f0 --- /dev/null +++ b/engine/compat/glib-shim/glib/gi18n.h @@ -0,0 +1,20 @@ +/* + * glib/gi18n.h -- shim minimal de GLib (voir ../glib.h). + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * Pas de traduction dans le moteur : les messages de gnubg restent en + * anglais et ne sont destinés qu'aux journaux de diagnostic. + */ + +#ifndef USTAGAMMON_GLIB_GI18N_H +#define USTAGAMMON_GLIB_GI18N_H + +#include "../glib.h" + +#define _(s) (s) +#define N_(s) (s) +#define gettext(s) (s) + +#endif diff --git a/engine/compat/glib-shim/glib/gprintf.h b/engine/compat/glib-shim/glib/gprintf.h new file mode 100644 index 0000000..e624779 --- /dev/null +++ b/engine/compat/glib-shim/glib/gprintf.h @@ -0,0 +1,14 @@ +/* + * glib/gprintf.h -- shim minimal de GLib (voir ../glib.h). + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + */ + +#ifndef USTAGAMMON_GLIB_GPRINTF_H +#define USTAGAMMON_GLIB_GPRINTF_H + +#include +#include "../glib.h" + +#endif diff --git a/engine/compat/glib-shim/glib/gstdio.h b/engine/compat/glib-shim/glib/gstdio.h new file mode 100644 index 0000000..0efd5b7 --- /dev/null +++ b/engine/compat/glib-shim/glib/gstdio.h @@ -0,0 +1,18 @@ +/* + * glib/gstdio.h -- shim minimal de GLib (voir ../glib.h). + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + */ + +#ifndef USTAGAMMON_GLIB_GSTDIO_H +#define USTAGAMMON_GLIB_GSTDIO_H + +#include +#include +#include "../glib.h" + +#define g_fopen fopen +#define g_unlink unlink + +#endif diff --git a/engine/compat/gnubg-support.c b/engine/compat/gnubg-support.c new file mode 100644 index 0000000..f4216e6 --- /dev/null +++ b/engine/compat/gnubg-support.c @@ -0,0 +1,64 @@ +/* + * gnubg-support.c -- définitions attendues par le cœur de gnubg. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * Le cœur repris dans engine/gnubg/ appelle quelques fonctions et lit + * quelques variables définies ailleurs dans GNU Backgammon (gnubg.c, set.c, + * output.c, util.c, multithread.c). Ce fichier en fournit des versions + * minimales pour le moteur embarqué, compilé sans threads ni interface. + * Liste établie à l'édition des liens ; le fichier jni-bridge/src/stubs.c de + * CBG (GPLv3) a servi de point de repère, sans reprise de code. + */ + +#include +#include + +#include "config.h" +#include "backgammon.h" +#include "multithread.h" +#include "util.h" +#include "gnubg-support.h" + +/* Code de retour des tâches (multithread.c, branche sans threads). */ +int asyncRet; + +/* Dossier des données (gnubg.wd, bases .bd) : gnubg le tient en global. */ +static char *pkg_data_dir; + +void +gnubg_support_set_data_dir(const char *dir) +{ + g_free(pkg_data_dir); + pkg_data_dir = g_strdup(dir); +} + +char * +getPkgDataDir(void) +{ + return pkg_data_dir ? pkg_data_dir : "."; +} + +/* Sorties de diagnostic : toujours sur la sortie d'erreur. */ +void +outputerrf(const char *sz, ...) +{ + va_list args; + + va_start(args, sz); + vfprintf(stderr, sz, args); + va_end(args); +} + +void +print_utf8_to_locale(const gchar *sz) +{ + fputs(sz, stderr); +} + +void +PrintError(const char *message) +{ + perror(message); +} diff --git a/engine/compat/gnubg-support.h b/engine/compat/gnubg-support.h new file mode 100644 index 0000000..0a5d350 --- /dev/null +++ b/engine/compat/gnubg-support.h @@ -0,0 +1,14 @@ +/* + * gnubg-support.h -- réglages du cœur de gnubg propres à Ustagammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + */ + +#ifndef USTAGAMMON_GNUBG_SUPPORT_H +#define USTAGAMMON_GNUBG_SUPPORT_H + +/* Fixe le dossier où gnubg cherche gnubg.wd et les bases .bd. */ +void gnubg_support_set_data_dir(const char *dir); + +#endif diff --git a/engine/compat/host-tools-support.c b/engine/compat/host-tools-support.c new file mode 100644 index 0000000..20929fc --- /dev/null +++ b/engine/compat/host-tools-support.c @@ -0,0 +1,38 @@ +/* + * host-tools-support.c -- complément pour l'outil makebearoff de gnubg. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * makebearoff (compilé avec la vraie GLib, pour générer les bases .bd au + * build) appelle GetTemporaryFile, définie dans util.c de gnubg. Version + * minimale, même contrat : ouvre un fichier temporaire en lecture-écriture + * et rend son nom, à supprimer puis libérer par l'appelant. + */ + +#include +#include +#include + +#include "util.h" + +FILE * +GetTemporaryFile(const char *nameTemplate, char **retName) +{ + FILE *pf; + int fd; + + if (!retName) + return NULL; + fd = g_file_open_tmp(nameTemplate, retName, NULL); + if (fd < 0) + return NULL; + pf = fdopen(fd, "wb+"); + if (!pf) { + close(fd); + unlink(*retName); + g_free(*retName); + *retName = NULL; + } + return pf; +} diff --git a/engine/compat/multithread.h b/engine/compat/multithread.h new file mode 100644 index 0000000..34205e8 --- /dev/null +++ b/engine/compat/multithread.h @@ -0,0 +1,64 @@ +/* + * multithread.h -- en-tête réduit remplaçant celui de GNU Backgammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * Le moteur embarqué est compilé sans USE_MULTITHREAD (ADR 0005). Ne restent + * que les définitions de la branche monothread de l'original : les macros + * MT_* et les données locales au fil unique (td.tld). + */ + +#ifndef MULTITHREAD_H +#define MULTITHREAD_H + +#include "config.h" + +#if defined(USE_MULTITHREAD) +#error "Le moteur embarqué d'Ustagammon est compilé sans threads" +#endif + +#include "backgammon.h" + +#define multi_debug(x) + +typedef struct { + int id; + move *aMoves; + NNState *pnnState; +} ThreadLocalData; + +typedef struct { + ThreadLocalData *tld; +} ThreadData; + +extern ThreadData td; + +/* Définies dans mtsupport.c (branche sans threads). */ +extern void MT_InitThreads(void); +extern void MT_Close(void); +extern ThreadLocalData *MT_CreateThreadLocalData(int id); + +#if !defined(MAX_NUMTHREADS) +#define MAX_NUMTHREADS 1 +#endif +extern int asyncRet; +#define MT_Exclusive() {} +#define MT_Release() {} +#define MT_GetNumThreads() 1 +#define MT_SetResultFailed() asyncRet = -1 +#define MT_SafeInc(x) (++(*x)) +#define MT_SafeIncValue(x) (++(*x)) +#define MT_SafeIncCheck(x) ((*x)++) +#define MT_SafeAdd(x, y) ((*x) += y) +#define MT_SafeDec(x) (--(*x)) +#define MT_SafeDecCheck(x) ((--(*x)) == 0) +#define MT_SafeGet(x) (*x) +#define MT_SafeSet(x, y) ((*x) = y) +#define MT_SafeCompare(x, y) ((*x) == y) +#define MT_GetThreadID() 0 +#define MT_Get_nnState() td.tld->pnnState +#define MT_Get_aMoves() td.tld->aMoves +#define MT_GetTLD() td.tld + +#endif diff --git a/engine/compat/output.h b/engine/compat/output.h new file mode 100644 index 0000000..0cf16e9 --- /dev/null +++ b/engine/compat/output.h @@ -0,0 +1,21 @@ +/* + * output.h -- en-tête réduit remplaçant celui de GNU Backgammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * Seules les fonctions de sortie utilisées par le cœur sont déclarées, avec + * les signatures de l'original. Dans le moteur embarqué, elles écrivent sur + * la sortie d'erreur (voir engine/compat/gnubg-support.c). + */ + +#ifndef OUTPUT_H +#define OUTPUT_H + +#include +#include + +extern void outputerrf(const char *sz, ...) __attribute__((format(printf, 1, 2))); +extern void print_utf8_to_locale(const gchar *sz); + +#endif diff --git a/engine/compat/util.h b/engine/compat/util.h new file mode 100644 index 0000000..2c7f85c --- /dev/null +++ b/engine/compat/util.h @@ -0,0 +1,25 @@ +/* + * util.h -- en-tête réduit remplaçant celui de GNU Backgammon. + * + * Copyright (C) 2026 les auteurs d'Ustagammon. + * Licence : GPLv3 ou ultérieure (voir LICENSE). + * + * getPkgDataDir() renvoie le dossier des données du moteur, fixé à + * l'initialisation (engine_init). Définitions : gnubg-support.c. + */ + +#ifndef UTIL_H +#define UTIL_H + +#include +#include + +extern char *getPkgDataDir(void); + +#define BuildFilename(file) g_build_filename(getPkgDataDir(), file, NULL) +#define BuildFilename2(file1, file2) g_build_filename(getPkgDataDir(), file1, file2, NULL) + +extern void PrintError(const char *message); +extern FILE *GetTemporaryFile(const char *nameTemplate, char **retName); + +#endif