#include #include #include #include #include #include struct option { char *name; int id; bool has_args; }; struct ink_source { uint8_t *bytes; size_t length; }; enum { OPTION_UNKNOWN = -2, OPTION_OPERAND = -3 }; enum { OPT_COLORS = 1000, OPT_COMPILE_ONLY, OPT_USE_STDIN, OPT_DUMP_AST, OPT_DUMP_IR, OPT_DUMP_STORY, OPT_VM_TRACING, OPT_HELP, }; static const struct option cli_options[] = { {"--colors", OPT_COLORS, false}, {"--compile-only", OPT_COMPILE_ONLY, false}, //{"--dump-ast", OPT_DUMP_AST, false}, //{"--dump-ir", OPT_DUMP_AST, false}, //{"--dump-story", OPT_DUMP_STORY, false}, //{"--trace", OPT_VM_TRACING, false}, {"--stdin", OPT_USE_STDIN, false}, {"--help", OPT_HELP, false}, {"-h", OPT_HELP, false}, {0}, }; static const char *usage_msg = "Usage: %s [OPTION]... [FILE]\n" "Load and execute an Ink story.\n\n" " -h, --help Print this message\n" " --colors Enable color output\n" " --compile-only Compile the story without executing\n" " --stdin Read source file from standard input\n" "\n"; static const struct option *_g_option_opts; static char *_g_arg_ptr; static char **_g_option_argv; static char *option_operand; static char *option_unknown_opt; static void print_usage(const char *name) { fprintf(stderr, usage_msg, name); } static void option_setopts(const struct option *opts, char **argv) { _g_option_argv = argv; _g_option_opts = opts; } static int option_nextopt(void) { if (*_g_option_argv != NULL) { ++_g_option_argv; } else { return 0; } char **optstr_p = _g_option_argv; if (*optstr_p == NULL) { return 0; } const struct option *p = _g_option_opts; while (p->id != 0) { if (!strcmp(*optstr_p, p->name)) { if (p->has_args) { _g_arg_ptr = optstr_p[1]; ++_g_option_argv; } return p->id; } else if (**optstr_p != '-') { option_operand = *optstr_p; return OPTION_OPERAND; } ++p; } option_unknown_opt = *optstr_p; return OPTION_UNKNOWN; } static char *option_nextarg(void) { char *arg = _g_arg_ptr; char *arg_p = arg; while (*arg_p != '\0' && *arg_p != ',') { ++arg_p; } if (*arg_p == '\0') { _g_arg_ptr = arg_p; return arg; } _g_arg_ptr = arg_p + 1; *arg_p = '\0'; return arg; } #define INK_SOURCE_BUF_MAX 1024 static const char *INK_FILE_EXT = ".ink"; static const size_t INK_FILE_EXT_LENGTH = 4; static int ink_read_file(const char *file_path, uint8_t **bytes, size_t *length) { size_t sz = 0, nr = 0; uint8_t *b = NULL; FILE *const fp = fopen(file_path, "rb"); if (!fp) { fprintf(stderr, "Could not read file '%s'\n", file_path); return -1; } fseek(fp, 0u, SEEK_END); sz = (size_t)ftell(fp); fseek(fp, 0u, SEEK_SET); b = malloc(sz + 1); if (!b) { fclose(fp); return -1; } nr = fread(b, 1u, sz, fp); if (nr < sz) { fprintf(stderr, "Could not read file '%s'.\n", file_path); fclose(fp); free(b); return -1; } b[nr] = '\0'; *bytes = b; *length = sz; fclose(fp); return 0; } static void ink_source_free(struct ink_source *s) { free(s->bytes); s->bytes = NULL; s->length = 0; } static int ink_source_load_stdin(struct ink_source *s) { uint8_t *tmp; char b[INK_SOURCE_BUF_MAX]; s->bytes = NULL; s->length = 0; while (fgets(b, INK_SOURCE_BUF_MAX, stdin)) { const size_t len = s->length; const size_t buflen = strlen(b); tmp = realloc(s->bytes, len + buflen + 1); if (!tmp) { ink_source_free(s); return -1; } s->bytes = tmp; memcpy(s->bytes + len, b, buflen); s->length += buflen; s->bytes[s->length] = '\0'; } if (ferror(stdin)) { ink_source_free(s); return -1; } return 0; } static int ink_source_load(const char *file_path, struct ink_source *s) { const char *ext; const size_t namelen = strlen(file_path); s->bytes = NULL; s->length = 0; if (namelen < INK_FILE_EXT_LENGTH) { return -1; } ext = file_path + namelen - INK_FILE_EXT_LENGTH; if (!(strncmp(ext, INK_FILE_EXT, INK_FILE_EXT_LENGTH) == 0)) { return -1; } return ink_read_file(file_path, &s->bytes, &s->length); } int main(int argc, char *argv[]) { bool compile_only = false; bool use_stdin = false; int opt = 0; int rc = -1; int flags = 0; const char *filename = NULL; struct ink_source source; struct ink_story *story = NULL; option_setopts(cli_options, argv); while ((opt = option_nextopt())) { switch (opt) { case OPT_COLORS: flags |= INK_F_USE_COLOR; break; case OPT_DUMP_AST: flags |= INK_F_DUMP_AST; break; case OPT_DUMP_IR: flags |= INK_F_DUMP_IR; break; case OPT_DUMP_STORY: flags |= INK_F_DUMP_CODE; break; case OPT_COMPILE_ONLY: compile_only = true; break; case OPT_USE_STDIN: use_stdin = true; break; case OPTION_UNKNOWN: fprintf(stderr, "Unrecognised option %s.\n\n", option_unknown_opt); print_usage(argv[0]); return EXIT_FAILURE; case OPT_HELP: print_usage(argv[0]); return EXIT_SUCCESS; case OPTION_OPERAND: filename = option_operand; break; default: print_usage(argv[0]); return EXIT_FAILURE; } } if (filename == NULL || *filename == '\0') { if (use_stdin) { rc = ink_source_load_stdin(&source); filename = ""; } else { print_usage(argv[0]); return EXIT_FAILURE; } } else { rc = ink_source_load(filename, &source); } if (rc < 0) { fprintf(stderr, "Error while loading source file.\n"); return rc; } story = ink_open(); if (!story) { goto out; } const struct ink_story_options opts = { .source_bytes = source.bytes, .source_length = source.length, .filename = (uint8_t *)filename, .filename_length = strlen(filename), .flags = flags, }; rc = ink_load_story_options(story, &opts); if (rc < 0) { goto out; } if (!compile_only) { while (ink_story_can_continue(story)) { const char *line = NULL; size_t linelen = 0; size_t choice_index = 0; while (ink_story_continue(story, &line, &linelen)) { if (line) { printf("%.*s\n", (int)linelen, line); } } while (ink_story_choice_next(story, &line, &linelen)) { printf("%zu: %.*s\n", ++choice_index, (int)linelen, line); } if (choice_index > 0) { printf("> "); scanf("%zu", &choice_index); ink_story_choose(story, choice_index); } } } out: ink_close(story); ink_source_free(&source); return EXIT_SUCCESS; }