diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-14 01:12:34 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-14 01:12:34 +0800 |
| commit | 0815b5fc1f7e7b98e65499dbebc05034bec6dba0 (patch) | |
| tree | a3de9e397f9b39fa4cf28ac2f656bd1d7ab240ca | |
| parent | 5ce08e68e437fd41b07d1c7ddc8846dadba23ca2 (diff) | |
add kons2c: konscript text to checked C
| -rw-r--r-- | tools/kons2c.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/tools/kons2c.c b/tools/kons2c.c new file mode 100644 index 0000000..8cacbc8 --- /dev/null +++ b/tools/kons2c.c @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: MIT */ +/* + * kons2c.c + * + * 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. + */ + +/* + * usage: kons2c [-n name] [-split rows] <script.kons> <output prefix> + * turns konscript text into C. any converter can write konscript text, this is the one place that writes C from 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 + */ + +#include "konscript.h" + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static const char *const opNames[] = { + "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" +}; + +/*** reading ***/ + +static char *readFile(const char *path, size_t *size) +{ + FILE *f = fopen(path, "rb"); + long n; + char *text; + + if (!f) return NULL; + if (fseek(f, 0, SEEK_END) != 0 || (n = ftell(f)) < 0 || fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return NULL; + } + text = malloc((size_t)n + 1); + if (!text) { + fclose(f); + return NULL; + } + *size = fread(text, 1, (size_t)n, f); + fclose(f); + text[*size] = '\0'; + return text; +} + +static int compareLabels(const void *a, const void *b) +{ + return strcmp(((const kon_scriptLabel_t *)a)->name, ((const kon_scriptLabel_t *)b)->name); +} + +/*** writing ***/ + +static int putString(FILE *f, const char *s) +{ + size_t i, column = 0; + + if (!s) return fputs("NULL", f) >= 0; + 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); + return 1; +} + +static void putRow(FILE *f, const kon_cmd_t *c) +{ + fprintf(f, "\t{%s, %d, %d, %d, %d, ", opNames[c->op], c->a, c->b, c->c, c->d); + putString(f, c->text); + fputs("},\n", f); +} + +static void identifier(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 FILE *create(const char *prefix, const char *suffix) +{ + char path[1024]; + FILE *f; + + if (snprintf(path, sizeof path, "%s%s", prefix, suffix) >= (int)sizeof path) return NULL; + f = fopen(path, "wb"); + if (!f) fprintf(stderr, "cannot write %s\n", path); + return f; +} + +int main(int argc, char **argv) +{ + char name[64] = "", upper[64]; + const char *input = NULL, *prefix = NULL; + int split = 0, i, rows, lines = 1; + size_t size, k; + char *source; + kon_scriptBuild_t b; + kon_cmd_t *cmds; + FILE *f; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-n") && i + 1 < argc) { + snprintf(name, sizeof name, "%s", argv[++i]); + } else if (!strcmp(argv[i], "-split") && i + 1 < argc) { + split = atoi(argv[++i]); + } else if (!input) { + input = argv[i]; + } else if (!prefix) { + prefix = argv[i]; + } + } + if (!input || !prefix || split < 0) { + fprintf(stderr, "usage: kons2c [-n name] [-split rows] <script.kons> <output prefix>\n"); + return 2; + } + if (!name[0]) identifier(name, sizeof name, prefix); + for (k = 0; name[k]; k++) { + if (!isalnum((unsigned char)name[k]) && name[k] != '_') { + fprintf(stderr, "the name has to be letters, digits and _\n"); + return 2; + } + upper[k] = (char)toupper((unsigned char)name[k]); + } + upper[k] = '\0'; + if (isdigit((unsigned char)name[0])) { + fprintf(stderr, "the name can't start with a digit\n"); + return 2; + } + + source = readFile(input, &size); + if (!source) { + fprintf(stderr, "cannot read %s\n", input); + return 1; + } + 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 (size > 2000000000u || !cmds || !b.pool || !b.labels || !b.hash) { + fprintf(stderr, "out of memory\n"); + return 1; + } + if (!kon_scriptParse(&b, source)) { + fprintf(stderr, "%s:%d: %s\n", input, b.errorLine, b.error); + return 1; + } + + /* 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, 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 = create(prefix, suffix); + if (!f) return 1; + fprintf(f, "/* part %d of %s, rows %d to %d, included by %s.c */\n", part, name, first, last - 1, name); + for (i = first; i < last; i++) putRow(f, &cmds[i]); + if (fclose(f) != 0) return 1; + } + } + + f = create(prefix, ".c"); + if (!f) return 1; + { + const char *base = strrchr(prefix, '/'); + + fprintf(f, "/* made by kons2c from %s, don't edit */\n\n", strrchr(input, '/') ? strrchr(input, '/') + 1 : input); + fprintf(f, "#include \"%s.h\"\n\n#include <string.h>\n\n", base ? base + 1 : prefix); + } + 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) { + const char *base = strrchr(prefix, '/'); + int part, first; + + for (part = 0, first = 0; first < rows; part++, first += split) fprintf(f, "#include \"%s_%d.inc\"\n", base ? base + 1 : prefix, part); + } else { + for (i = 0; i < rows; i++) putRow(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++) { + fprintf(f, "\t{"); + putString(f, b.labels[i].name); + fprintf(f, ", %d},\n", b.labels[i].at); + } + if (b.labelCount == 0) fprintf(f, "\t{\"\", 0}\n"); + fprintf(f, "};\n\n"); + 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) return 1; + + f = create(prefix, ".h"); + if (!f) return 1; + fprintf(f, "/* made by kons2c, don't edit */\n\n#ifndef %s_H\n#define %s_H\n\n#include \"konscript.h\"\n\n", upper, upper); + fprintf(f, "#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n"); + 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); + fprintf(f, "#ifdef __cplusplus\n}\n#endif\n#endif\n"); + if (fclose(f) != 0) return 1; + + printf("%s: %d commands, %d labels%s\n", name, rows, b.labelCount, split ? ", split" : ""); + free(source); + free(cmds); + free(b.pool); + free(b.labels); + free(b.hash); + return 0; +} |
