Instead of passing a flag through the parser and tokenizer for telling the input source if we need further input or not, we steal a trick from Lua: In the REPL, we just continue to read lines and append them to the input, until the input was loaded with no "unexpected EOF" error. After all, when we didn't expect an EOF is exactly the scenario, when we need more input. Doing things this way simplifies a bunch of places and lets us remove the ugly source_reader and iterative_runner concepts. To allow the REPL to see the error that happened during loading required some smaller refactorings, but those were honestly for the better anyway. I also decided to get rid of the token_source concept, the parser now gets the tokenizer directly. This also made things a bit simpler, also I want to soon-ish implement string interpolation, and for that the parser needs to do more with the tokenizer than just reading the next token. One last thing: This also cleans up the web playground and makes the playground and REPL share a bunch of code. Nice!
241 lines
6.1 KiB
C
241 lines
6.1 KiB
C
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "apfl.h"
|
|
#include "bytecode.h"
|
|
#include "compile.h"
|
|
#include "format.h"
|
|
#include "gc.h"
|
|
#include "strings.h"
|
|
|
|
struct c_writer_data {
|
|
struct apfl_io_writer w;
|
|
size_t written_len;
|
|
};
|
|
|
|
static const unsigned char hex[] = {
|
|
'0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
|
};
|
|
|
|
static bool
|
|
write_as_c_source(void *opaque, const unsigned char *buf, size_t len)
|
|
{
|
|
struct c_writer_data *data = opaque;
|
|
for (size_t i = 0; i < len; i++) {
|
|
if (data->written_len % 16 == 0) {
|
|
if (!apfl_io_write_byte(data->w, '\n')) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
unsigned char out[] = {'0', 'x', hex[(buf[i] >> 4) & 0x0F], hex[buf[i] & 0x0F], ','};
|
|
|
|
if (!apfl_io_write_string_view(
|
|
data->w,
|
|
(struct apfl_string_view) { .bytes = out, .len = sizeof(out), }
|
|
)) {
|
|
return false;
|
|
}
|
|
|
|
(data->written_len)++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
c_source_prelude(struct apfl_io_writer w)
|
|
{
|
|
return apfl_io_write_string(
|
|
w,
|
|
"#include <stddef.h>\n"
|
|
// TODO: This is a bit hackish, the generated C code should include
|
|
// apfl.h instead of declaring the string view struct itself. However,
|
|
// since the code is generated, it does not live in the same directory
|
|
// as apfl.h, so our usual `#include "apfl.h"` doesn't work.
|
|
"struct apfl_string_view {\n"
|
|
" const unsigned char *bytes;\n"
|
|
" size_t len;\n"
|
|
"};\n"
|
|
"static const unsigned char bytecode[] = {"
|
|
);
|
|
}
|
|
|
|
static bool
|
|
c_source_epilogue(struct apfl_io_writer w, const char *function_name)
|
|
{
|
|
return apfl_io_write_string(w,
|
|
"\n};\n"
|
|
"struct apfl_string_view\n"
|
|
)
|
|
&& apfl_io_write_string(w, function_name)
|
|
&& apfl_io_write_string(w,
|
|
"(void)\n"
|
|
"{\n"
|
|
" return (struct apfl_string_view) {\n"
|
|
" .bytes = bytecode,\n"
|
|
" .len = sizeof(bytecode),\n"
|
|
" };\n"
|
|
"}\n"
|
|
);
|
|
}
|
|
|
|
static const char *
|
|
get_next_string_from_arg(const char *progname, const char ***argpp)
|
|
{
|
|
if (**argpp == NULL) {
|
|
fprintf(stderr, "Usage: %s [-c function_name] input output\n", progname);
|
|
return NULL;
|
|
}
|
|
|
|
const char *filename = **argpp;
|
|
(*argpp)++;
|
|
return filename;
|
|
}
|
|
|
|
static FILE *
|
|
openfile(const char *progname, const char *filename, const char *mode)
|
|
{
|
|
FILE *f = fopen(filename, mode);
|
|
if (f == NULL) {
|
|
fprintf(stderr, "%s: Could not open %s: %s\n", progname, filename, strerror(errno));
|
|
return NULL;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
static void
|
|
closefile(FILE *f)
|
|
{
|
|
if (f != NULL) {
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
struct protected_compiling_data {
|
|
const char *name;
|
|
struct apfl_io_reader *r;
|
|
struct apfl_io_writer *w;
|
|
};
|
|
|
|
static void
|
|
protected_compiling(apfl_ctx ctx, void *opaque)
|
|
{
|
|
struct protected_compiling_data *data = opaque;
|
|
|
|
apfl_push_const_string(ctx, data->name);
|
|
apfl_load(ctx, *data->r, -1);
|
|
apfl_bytecode_save(ctx, *data->w, -1);
|
|
}
|
|
|
|
#define MAIN_TRY_FMT(x) if (!(x)) { goto error; }
|
|
|
|
int
|
|
main(int argc, const char *argv[])
|
|
{
|
|
(void)argc;
|
|
const char **argp = argv+1;
|
|
|
|
int rv = 1;
|
|
|
|
FILE *in = NULL;
|
|
FILE *out = NULL;
|
|
|
|
const char *c_function_name = NULL;
|
|
if (*argp != NULL && apfl_string_eq(*argp, "-c")) {
|
|
argp++;
|
|
if ((c_function_name = get_next_string_from_arg(argv[0], &argp)) == NULL) {
|
|
goto error_before_ctx;
|
|
}
|
|
}
|
|
|
|
const char *name_in = NULL;
|
|
const char *name_out = NULL;
|
|
if (
|
|
(name_in = get_next_string_from_arg(argv[0], &argp)) == NULL
|
|
|| (name_out = get_next_string_from_arg(argv[0], &argp)) == NULL
|
|
) {
|
|
goto error_before_ctx;
|
|
}
|
|
|
|
if (
|
|
(in = openfile(argv[0], name_in, "rb")) == NULL
|
|
|| (out = openfile(argv[0], name_out, "wb")) == NULL
|
|
) {
|
|
goto error_before_ctx;
|
|
}
|
|
|
|
struct apfl_io_reader r = apfl_io_file_reader(in);
|
|
struct apfl_io_writer w_raw = apfl_io_file_writer(out);
|
|
|
|
if (c_function_name != NULL && !c_source_prelude(w_raw)) {
|
|
fprintf(stderr, "%s: IO error\n", argv[0]);
|
|
goto error_before_ctx;
|
|
}
|
|
|
|
struct apfl_io_writer w_err = apfl_io_file_writer(stderr);
|
|
struct apfl_io_writer bytecode_writer = w_raw;
|
|
struct c_writer_data c_writer_data = {
|
|
.w = w_raw,
|
|
.written_len = 0,
|
|
};
|
|
if (c_function_name != NULL) {
|
|
bytecode_writer = (struct apfl_io_writer) {
|
|
.opaque = &c_writer_data,
|
|
.write = write_as_c_source,
|
|
};
|
|
}
|
|
|
|
apfl_ctx ctx = apfl_ctx_new((struct apfl_config) {
|
|
.allocator = apfl_allocator_default(),
|
|
.output_writer = apfl_io_file_writer(stdout),
|
|
.no_standard_modules = true,
|
|
});
|
|
|
|
if (ctx == NULL) {
|
|
fprintf(stderr, "Could not init context\n");
|
|
goto error;
|
|
}
|
|
|
|
struct protected_compiling_data data = {
|
|
.name = name_in,
|
|
.r = &r,
|
|
.w = &bytecode_writer,
|
|
};
|
|
|
|
switch (apfl_do_protected(ctx, protected_compiling, &data, NULL)) {
|
|
case APFL_RESULT_OK :
|
|
break;
|
|
case APFL_RESULT_ERR:
|
|
MAIN_TRY_FMT(apfl_io_write_string(w_err, "Error occurred during compilation:\n"));
|
|
if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) {
|
|
MAIN_TRY_FMT(apfl_io_write_string(w_err, apfl_get_string(ctx, -1)));
|
|
} else {
|
|
MAIN_TRY_FMT(apfl_debug_print_val(ctx, -1, w_err));
|
|
}
|
|
MAIN_TRY_FMT(apfl_io_write_byte(w_err, '\n'));
|
|
break;
|
|
case APFL_RESULT_ERRERR:
|
|
MAIN_TRY_FMT(apfl_io_write_string(w_err, "Error occurred during error handling.\n"));
|
|
break;
|
|
case APFL_RESULT_ERR_ALLOC:
|
|
MAIN_TRY_FMT(apfl_io_write_string(w_err, "Fatal: Could not allocate memory.\n"));
|
|
goto error;
|
|
}
|
|
|
|
if (c_function_name != NULL && !c_source_epilogue(w_raw, c_function_name)) {
|
|
fprintf(stderr, "%s: IO error\n", argv[0]);
|
|
goto error;
|
|
}
|
|
|
|
rv = 0;
|
|
error:
|
|
apfl_ctx_destroy(ctx);
|
|
error_before_ctx:
|
|
closefile(in);
|
|
closefile(out);
|
|
return rv;
|
|
}
|