324 lines
7.3 KiB
C
324 lines
7.3 KiB
C
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
|
|
#include <ink.h>
|
|
|
|
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;
|
|
|
|
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 = "<STDIN>";
|
|
} 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;
|
|
}
|
|
|
|
const struct ink_story_options opts = {
|
|
.source_bytes = source.bytes,
|
|
.source_length = source.length,
|
|
.filename = (uint8_t *)filename,
|
|
.filename_length = strlen(filename),
|
|
.flags = flags,
|
|
};
|
|
|
|
struct ink_story *const story = ink_load_story_options(&opts);
|
|
if (story == NULL) {
|
|
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;
|
|
}
|