/* SPDX-License-Identifier: MIT */ /* * konscriptc.h * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * what every converter uses to write konscript as C, so there is one place that does it and one place to check. * in one .c file: #define KONSCRIPTC_IMPLEMENTATION before including it * * the text goes through the real parser first, so every jump lands on a label that is there, and nothing is written if it * doesn't parse. what comes out is only constant data: a list of kon_cmd_t, a sorted list of labels and a find function. * strings are written as literals where everything but plain printable characters is an octal escape, so what is in the * script can't be anything but text to the compiler. there is no pointer arithmetic, no generated logic and nothing to overrun * * writes prefix.h and prefix.c, and with split also prefix_0.inc, prefix_1.inc and so on that prefix.c includes inside * the one array, so a big script is several files for the editor and one list for the program */ #ifndef KONSCRIPTC_H #define KONSCRIPTC_H #include #ifdef __cplusplus extern "C" { #endif typedef struct konsc_result { int commands, labels; } konsc_result_t; /* name is the start of every name in the files, letters digits and _, not starting with a digit. split is how many commands go in each .inc, 0 for none. 0 if it worked, -1 and a message in error if not */ int konsc_write(const char *source, size_t size, const char *prefix, const char *name, int split, konsc_result_t *result, char *error, int errorSize); /* a C string literal of s, or NULL. long ones are cut into pieces the compiler joins */ void konsc_string(FILE *f, const char *s); /* a name for the files made from the end of a path, "out/my game.c" becomes my_game */ void konsc_name(char *out, size_t size, const char *path); #ifdef __cplusplus } #endif #ifdef KONSCRIPTC_IMPLEMENTATION #include "konscript.h" #include #include #include static const char *const konsc__ops[] = { "KON_OP_END", "KON_OP_SAY", "KON_OP_MENU", "KON_OP_OPTION", "KON_OP_JUMP", "KON_OP_SWITCH", "KON_OP_VAR", "KON_OP_IFSWITCH", "KON_OP_IFVAR", "KON_OP_WAIT", "KON_OP_HOST", "KON_OP_CALL", "KON_OP_RETURN" }; static int konsc__compareLabels(const void *a, const void *b) { return strcmp(((const kon_scriptLabel_t *)a)->name, ((const kon_scriptLabel_t *)b)->name); } void konsc_string(FILE *f, const char *s) { size_t i, column = 0; if (!s) { fputs("NULL", f); return; } fputc('"', f); for (i = 0; s[i]; i++) { unsigned char c = (unsigned char)s[i]; if (column >= 100) { fputs("\"\n\t\t\"", f); column = 0; } if (c == '"' || c == '\\' || c == '?') { fprintf(f, "\\%c", c); column += 2; } else if (c == '\n' || c == '\t') { fputs(c == '\n' ? "\\n" : "\\t", f); column += 2; } else if (c >= 0x20 && c < 0x7F) { fputc(c, f); column++; } else { fprintf(f, "\\%03o", c); column += 4; } } fputc('"', f); } void konsc_name(char *out, size_t size, const char *path) { const char *base = strrchr(path, '/'); size_t i, n = 0; base = base ? base + 1 : path; for (i = 0; base[i] && base[i] != '.' && n + 1 < size; i++) out[n++] = isalnum((unsigned char)base[i]) ? (char)tolower((unsigned char)base[i]) : '_'; out[n] = '\0'; if (!n || isdigit((unsigned char)out[0])) { memmove(out + 1, out, n + 1 < size ? n + 1 : size - 1); out[0] = 'k'; } } static void konsc__row(FILE *f, const kon_cmd_t *c) { fprintf(f, "\t{%s, %d, %d, %d, %d, ", konsc__ops[c->op], c->a, c->b, c->c, c->d); konsc_string(f, c->text); fputs("},\n", f); } static int konsc__fail(char *error, int errorSize, const char *what, const char *detail) { if (error && errorSize > 0) snprintf(error, (size_t)errorSize, "%s%s", what, detail ? detail : ""); return -1; } static FILE *konsc__create(const char *prefix, const char *suffix, char *error, int errorSize) { char path[1024]; FILE *f; if (snprintf(path, sizeof path, "%s%s", prefix, suffix) >= (int)sizeof path) { konsc__fail(error, errorSize, "path too long", NULL); return NULL; } f = fopen(path, "wb"); if (!f) konsc__fail(error, errorSize, "cannot write ", path); return f; } int konsc_write(const char *source, size_t size, const char *prefix, const char *name, int split, konsc_result_t *result, char *error, int errorSize) { char upper[64]; const char *base = strrchr(prefix, '/'); kon_scriptBuild_t b; kon_cmd_t *cmds; size_t k; int i, rows, lines = 1, status = -1; FILE *f; base = base ? base + 1 : prefix; if (!name[0] || strlen(name) >= sizeof upper || isdigit((unsigned char)name[0])) return konsc__fail(error, errorSize, "the name has to be letters, digits and _ and not start with a digit", NULL); for (k = 0; name[k]; k++) { if (!isalnum((unsigned char)name[k]) && name[k] != '_') return konsc__fail(error, errorSize, "the name has to be letters, digits and _ and not start with a digit", NULL); upper[k] = (char)toupper((unsigned char)name[k]); } upper[k] = '\0'; if (split < 0 || size > 1000000000u) return konsc__fail(error, errorSize, "bad split or a script that is too big", NULL); for (k = 0; k < size; k++) { if (source[k] == '\n') lines++; } memset(&b, 0, sizeof b); b.maxCmds = lines + 2; b.maxLabels = lines + 2; b.poolSize = (int)size + 16; b.hashSize = 16; while (b.hashSize < 2 * b.maxLabels) b.hashSize *= 2; cmds = malloc((size_t)b.maxCmds * sizeof *cmds); b.cmds = cmds; b.pool = malloc((size_t)b.poolSize); b.labels = malloc((size_t)b.maxLabels * sizeof *b.labels); b.hash = malloc((size_t)b.hashSize * sizeof *b.hash); if (!cmds || !b.pool || !b.labels || !b.hash) { konsc__fail(error, errorSize, "out of memory", NULL); goto done; } if (!kon_scriptParse(&b, source)) { if (error && errorSize > 0) snprintf(error, (size_t)errorSize, "line %d: %s", b.errorLine, b.error); goto done; } /* a label at the very end would point past the list, so the list gets an end of its own to land on */ cmds[b.count].op = KON_OP_END; cmds[b.count].a = cmds[b.count].b = cmds[b.count].c = cmds[b.count].d = 0; cmds[b.count].text = NULL; rows = b.count + 1; qsort(b.labels, (size_t)b.labelCount, sizeof *b.labels, konsc__compareLabels); if (split) { int part, first; for (part = 0, first = 0; first < rows; part++, first += split) { char suffix[32]; int last = first + split < rows ? first + split : rows; snprintf(suffix, sizeof suffix, "_%d.inc", part); f = konsc__create(prefix, suffix, error, errorSize); if (!f) goto done; fprintf(f, "/* part %d of %s, commands %d to %d, included by %s.c */\n", part, name, first, last - 1, base); for (i = first; i < last; i++) konsc__row(f, &cmds[i]); if (fclose(f) != 0) { konsc__fail(error, errorSize, "write failed", NULL); goto done; } } } f = konsc__create(prefix, ".c", error, errorSize); if (!f) goto done; fprintf(f, "/* made by konscriptc, don't edit */\n\n#include \"%s.h\"\n\n#include \n\n", base); fprintf(f, "const int %s_count = %d;\n\n", name, rows); fprintf(f, "const kon_cmd_t %s_cmds[%d] = {\n", name, rows); if (split) { int part, first; for (part = 0, first = 0; first < rows; part++, first += split) fprintf(f, "#include \"%s_%d.inc\"\n", base, part); } else { for (i = 0; i < rows; i++) konsc__row(f, &cmds[i]); } fprintf(f, "};\n\n"); fprintf(f, "const int %s_labelCount = %d;\n\n", name, b.labelCount); fprintf(f, "const kon_scriptLabel_t %s_labels[%d] = {\n", name, b.labelCount > 0 ? b.labelCount : 1); for (i = 0; i < b.labelCount; i++) { fputs("\t{", f); konsc_string(f, b.labels[i].name); fprintf(f, ", %d},\n", b.labels[i].at); } if (b.labelCount == 0) fputs("\t{\"\", 0}\n", f); fputs("};\n\n", f); fprintf(f, "int %s_find(const char *label)\n{\n\tint low = 0, high = %s_labelCount - 1;\n\n", name, name); fprintf(f, "\twhile (low <= high) {\n\t\tint mid = low + (high - low) / 2;\n\t\tint order = strcmp(label, %s_labels[mid].name);\n\n", name); fprintf(f, "\t\tif (order == 0) return %s_labels[mid].at;\n\t\tif (order < 0) high = mid - 1;\n\t\telse low = mid + 1;\n\t}\n\treturn -1;\n}\n", name); if (fclose(f) != 0) { konsc__fail(error, errorSize, "write failed", NULL); goto done; } f = konsc__create(prefix, ".h", error, errorSize); if (!f) goto done; fprintf(f, "/* made by konscriptc, don't edit */\n\n#ifndef %s_H\n#define %s_H\n\n#include \"konscript.h\"\n\n", upper, upper); fputs("#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n", f); fprintf(f, "#define %s_COUNT %d\n\n", upper, rows); fprintf(f, "extern const int %s_count;\nextern const kon_cmd_t %s_cmds[%d];\n", name, name, rows); fprintf(f, "extern const int %s_labelCount;\nextern const kon_scriptLabel_t %s_labels[%d];\n\n", name, name, b.labelCount > 0 ? b.labelCount : 1); fprintf(f, "/* where a label is, or -1 */\nint %s_find(const char *label);\n\n", name); fputs("#ifdef __cplusplus\n}\n#endif\n#endif\n", f); if (fclose(f) != 0) { konsc__fail(error, errorSize, "write failed", NULL); goto done; } if (result) { result->commands = rows; result->labels = b.labelCount; } status = 0; done: free(cmds); free(b.pool); free(b.labels); free(b.hash); return status; } #endif /* end of KONSCRIPTC_IMPLEMENTATION */ #endif