ink/examples/ink-c-driver.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;
}