From c298c2eec0b7ec0fcdacb8f661a9da3c184d2960 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Fri, 25 Sep 2026 15:14:08 +0100 Subject: Add fns and libraries fn name(params) = value declares one expression, inlined where it is called: arguments are computed once onto the stack and the body reads them with PICK, so a fn costs no runtime call and keeps Pure's bound on work. Parameters take any kind of value; recursion is a compile error. use name reads name.aup beside the source or from AUP_PATH. A library declares only fns and uses, sees only its parameters, and is called as name.fn(...). Diagnostics now carry the file they are in, and the compiler takes a loader so that sources come from the host. examples/words.aup is such a library; examples/tasks.aup uses it and still matches its reference model. --- src/aup.h | 16 ++-- src/compile.c | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ src/main.c | 60 ++++++++++--- 3 files changed, 317 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/aup.h b/src/aup.h index 75047af..98b59b0 100644 --- a/src/aup.h +++ b/src/aup.h @@ -4,16 +4,22 @@ #include /* aup: source text to a Pure module (Pure docs/design/008-module-format.md). */ -enum { AUP_SOURCE_LIMIT = 32768, AUP_MESSAGE = 160 }; +enum { AUP_SOURCE_LIMIT = 32768, AUP_MESSAGE = 256, AUP_PATH = 256 }; typedef struct { + char path[AUP_PATH]; /* the library the error is in, or "" for the main source */ int line, column; char message[AUP_MESSAGE]; } AupDiagnostic; +/* Finds the library that `use name` names: its source, alive until + * aup_compile returns, and a path that diagnostics show. */ +typedef bool (*AupLoad)(void *context, const char *name, const char **source, size_t *length, + char path[AUP_PATH]); + /* Compile source into a newly allocated module (free with free()). The same - * source always produces the same bytes. On failure *module is NULL and the - * diagnostic names the first error. */ -bool aup_compile(const char *source, size_t length, unsigned char **module, size_t *size, - AupDiagnostic *error); + * sources always produce the same bytes. On failure *module is NULL and the + * diagnostic names the first error. load may be NULL when nothing is used. */ +bool aup_compile(const char *source, size_t length, AupLoad load, void *context, + unsigned char **module, size_t *size, AupDiagnostic *error); #endif diff --git a/src/compile.c b/src/compile.c index ad6c489..c2e5ff2 100644 --- a/src/compile.c +++ b/src/compile.c @@ -10,13 +10,14 @@ /* Limits and opcodes of Pure module format v5. */ enum { STATES = 32, INPUTS = 32, VIEWS = 64, HANDLERS = 64, CODE = 512, STACK = 64, NAME = 32, JOINS = 8, DEPTH = 64, FIELDS = 32, COLLECTIONS = 1, CONSTANTS = 64, CONSTANT_BYTES = 8192, - RECORDS = 8, RECORD_FIELDS = 64, FIELDS_PER_RECORD = 16 }; + RECORDS = 8, RECORD_FIELDS = 64, FIELDS_PER_RECORD = 16, FNS = 64, PARAMS = 8, MODULES = 9 }; enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR, NOT, MIN, MAX, CLAMP, STORE, JZ, JMP, FIELD, DOC, DOC_SIZE, LEN, REPLACE, REPLY, REQUIRE, TEXT_CONST, TEXT_LOAD, TEXT_INPUT, NUM_TEXT, CONCAT, CHARS, LINES, TEXT_BYTES, CUR_LOAD, CUR_START, CUR_END, CUR_MOVE, CUR_STORE, TEXT_INSERT, TEXT_REMOVE, TEXT_LINE, TEXT_SET, TEXT_FROM, BYTES_FROM, CUR_COUNT, LIST_COUNT, LIST_APPEND, LIST_INSERT, LIST_REMOVE, - ITEM_GET, ITEM_SET, INDEX_LOAD, INDEX_STORE, INDEX_MOVE, INDEX_FIRST, INDEX_LAST, INDEX_NUM, ROW }; + ITEM_GET, ITEM_SET, INDEX_LOAD, INDEX_STORE, INDEX_MOVE, INDEX_FIRST, INDEX_LAST, INDEX_NUM, ROW, + PICK, SLIDE }; enum { KIND_KEY = 1, KIND_MESSAGE = 2, KIND_TEXT = 3, FIELD_U32 = 1, FIELD_BYTES = 2 }; enum { STATE_NUM = 0, STATE_TEXT = 1, STATE_CURSOR = 2, STATE_LIST = 3, STATE_INDEX = 4, UNIT_CHAR = 0, UNIT_LINE = 1 }; typedef enum { NUM = 1, BYTES, TEXT, CURSOR, UNIT, INDEX } Type; @@ -40,7 +41,7 @@ typedef enum { E_TEXT, /* literal bytes */ E_UNIT, /* args[0] char|line */ E_CONVERT, /* text(args[0]) or bytes(args[0]) */ - E_LINE, /* name.line(args[0]) */ + E_FN, /* name(args): a fn, qualified by the library in member (name.line(at) when member is a text) */ E_ITEM, /* name[args[0]].member: a field of a list item */ E_RECORD, /* name { fields }: E_FIELD nodes linked from args[0] */ E_FIELD, /* member: args[0] */ @@ -51,7 +52,7 @@ typedef struct Expr { int line, column, op; double number; char name[NAME], member[NAME]; - struct Expr *args[3], *next; + struct Expr *args[PARAMS], *next; unsigned count; unsigned char *bytes; size_t size; @@ -89,6 +90,21 @@ typedef struct { unsigned fields; } Record; +/* A fn: one expression over its parameters, inlined where it is called. */ +typedef struct { + char name[NAME], params[PARAMS][NAME]; + unsigned param_count; + int module, line, column; + Expr *body; + bool active; /* being inlined: a call back into it is recursion */ +} Fn; + +/* The fn being inlined: where its arguments are on the stack and their kinds. */ +typedef struct Scope { + int fn, base, line, column; + Kind kinds[PARAMS]; +} Scope; + typedef struct { const char *source; size_t length, at; @@ -123,6 +139,15 @@ typedef struct { /* Code generation. */ int message; /* message or text input of the handler being generated, or -1 */ int row; /* the table whose column is being generated, or -1 */ + /* Libraries: module 0 is the main source; each module lists the libraries it uses. */ + struct { char name[NAME], path[AUP_PATH]; bool uses[MODULES], parsing; } modules[MODULES]; + unsigned module_count; + int module; /* the module being parsed, or whose fn is being inlined */ + AupLoad load; + void *load_context; + Fn fns[FNS]; + unsigned fn_count; + Scope *scope; /* the fn being inlined, or NULL */ unsigned joins; /* branches ahead that carry a value */ struct { uint8_t op; uint32_t arg; double value; } code[CODE]; unsigned code_count; @@ -136,15 +161,20 @@ typedef struct { static const char *const reserved[] = {"state", "input", "view", "on", "if", "else", "key", "frame", "dt", "min", "max", "clamp", "message", "by", "bytes", "u32", - "reply", "require", "record", "each"}; + "reply", "require", "record", "each", "fn", "use"}; +/* An error in the module being parsed or inlined; inside a fn it names the call. */ static _Noreturn void fail_at(Compiler *c, int line, int column, const char *format, ...) { va_list args; va_start(args, format); c->error->line = line; c->error->column = column; - vsnprintf(c->error->message, sizeof(c->error->message), format, args); + snprintf(c->error->path, sizeof(c->error->path), "%s", c->modules[c->module].path); + int used = vsnprintf(c->error->message, sizeof(c->error->message), format, args); va_end(args); + if (c->scope && used >= 0 && (size_t)used < sizeof(c->error->message)) + snprintf(c->error->message + used, sizeof(c->error->message) - (size_t)used, " (in fn '%s' called at %d:%d)", + c->fns[c->scope->fn].name, c->scope->line, c->scope->column); longjmp(c->fail, 1); } @@ -351,6 +381,22 @@ static void index_key(Compiler *c) { expect(c, "]", "']'"); } +/* (a, b, ...) after a fn name. */ +static Expr *arguments(Compiler *c, Expr *call) { + expect(c, "(", "'('"); + newlines(c); + while (!is(c, ")")) { + if (call->count == PARAMS) fail_at(c, c->token.line, c->token.column, "a call takes at most 8 arguments"); + call->args[call->count++] = expression(c, 0, true); + newlines(c); + if (!is(c, ",")) break; + take(c); + newlines(c); + } + expect(c, ")", "')'"); + return call; +} + static Expr *postfix(Compiler *c, Expr *e) { const Token *next = is(c, "[") ? lookahead(c) : NULL; if (e->kind == E_NAME && next && next->kind == T_NAME && !strcmp(next->text, "key")) { @@ -382,16 +428,13 @@ static Expr *postfix(Compiler *c, Expr *e) { take(c); Token member = expect_name(c, "a field or member name"); if (is(c, "(")) { - if (e->kind != E_NAME || strcmp(member.text, "line")) - fail_at(c, member.line, member.column, "only a text state has a method here: name.line(cursor)"); - take(c); - Expr *line = node(c, E_LINE, member.line, member.column); - memcpy(line->name, e->name, NAME); - line->args[0] = expression(c, 0, true); - line->count = 1; - newlines(c); - expect(c, ")", "')'"); - e = line; + /* library.fn(...), or text.line(cursor) */ + if (e->kind != E_NAME) + fail_at(c, member.line, member.column, "only a library fn or a text's line is called here"); + Expr *call = node(c, E_FN, member.line, member.column); + memcpy(call->name, member.text, NAME); + memcpy(call->member, e->name, NAME); + e = arguments(c, call); continue; } Expr *access = node(c, E_MEMBER, e->line, e->column); /* errors point at the expression start */ @@ -578,8 +621,9 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) { left = conditional(c, &token); } else { if (reserved_name(token.text)) fail_at(c, token.line, token.column, "expected a number, state, or expression"); - left = node(c, E_NAME, token.line, token.column); + left = node(c, is(c, "(") ? E_FN : E_NAME, token.line, token.column); memcpy(left->name, token.text, NAME); + if (left->kind == E_FN) left = arguments(c, left); left = postfix(c, left); } } else fail_at(c, token.line, token.column, "expected a number, state, or expression"); @@ -792,8 +836,24 @@ static int record_index(const Compiler *c, const char *name) { return -1; } +static int fn_index(const Compiler *c, int module, const char *name) { + for (unsigned i = 0; i < c->fn_count; ++i) + if (c->fns[i].module == module && !strcmp(c->fns[i].name, name)) return (int)i; + return -1; +} + +/* The library a module names by `use name`, or -1. */ +static int used_library(const Compiler *c, int module, const char *name) { + for (unsigned i = 1; i < c->module_count; ++i) + if (c->modules[module].uses[i] && !strcmp(c->modules[i].name, name)) return (int)i; + return -1; +} + static const char *declared_kind(const Compiler *c, const char *name) { static const char *const states[] = {"state", "text state", "cursor", "list", "list index"}; + if (fn_index(c, c->module, name) >= 0) return "fn"; + if (used_library(c, c->module, name) >= 0) return "library"; + if (c->module) return NULL; /* a library has only fns and uses */ int state = state_index(c, name); if (state >= 0) return states[c->state_types[state]]; if (record_index(c, name) >= 0) return "record"; @@ -988,9 +1048,109 @@ static void table_declaration(Compiler *c, unsigned table) { take(c); } +/* fn name(params) = value, or fn name = value. */ +static void fn_declaration(Compiler *c) { + Token name = expect_name(c, "fn name"); + declare(c, &name); + if (!strcmp(name.text, "text") || !strcmp(name.text, "bytes")) + fail_at(c, name.line, name.column, "'%s' is a built-in conversion", name.text); + if (c->fn_count == FNS) fail_at(c, name.line, name.column, "at most 64 fns are allowed"); + Fn *f = &c->fns[c->fn_count]; + memset(f, 0, sizeof(*f)); + memcpy(f->name, name.text, NAME); + f->module = c->module; + f->line = name.line; + f->column = name.column; + if (is(c, "(")) { + take(c); + newlines(c); + while (!is(c, ")")) { + Token param = expect_name(c, "a parameter name"); + if (reserved_name(param.text)) fail_at(c, param.line, param.column, "'%s' is reserved", param.text); + for (unsigned i = 0; i < f->param_count; ++i) + if (!strcmp(f->params[i], param.text)) + fail_at(c, param.line, param.column, "duplicate parameter '%s'", param.text); + if (f->param_count == PARAMS) fail_at(c, param.line, param.column, "a fn takes at most 8 parameters"); + memcpy(f->params[f->param_count++], param.text, NAME); + newlines(c); + if (!is(c, ",")) break; + take(c); + newlines(c); + } + expect(c, ")", "')'"); + } + expect(c, "=", "'='"); + f->body = expression(c, 0, false); + ++c->fn_count; +} + +static void library_declaration(Compiler *c); + +/* use name: the fns of name.aup, called as name.fn(...). Each library is read once. */ +static void use_declaration(Compiler *c) { + Token name = expect_name(c, "a library name"); + declare(c, &name); + int library = -1; + for (unsigned i = 1; i < c->module_count; ++i) if (!strcmp(c->modules[i].name, name.text)) library = (int)i; + if (library >= 0 && c->modules[library].parsing) + fail_at(c, name.line, name.column, "library '%s' uses itself through its uses", name.text); + if (library < 0) { + if (c->module_count == MODULES) fail_at(c, name.line, name.column, "at most 8 libraries are allowed"); + const char *source = NULL; + size_t length = 0; + char path[AUP_PATH] = ""; + if (!c->load || !c->load(c->load_context, name.text, &source, &length, path)) + fail_at(c, name.line, name.column, "cannot find library '%s' (%s.aup)", name.text, name.text); + if (length > AUP_SOURCE_LIMIT) fail_at(c, name.line, name.column, "library '%s' exceeds 32768 bytes", name.text); + library = (int)c->module_count++; + memcpy(c->modules[library].name, name.text, NAME); + snprintf(c->modules[library].path, AUP_PATH, "%s", *path ? path : name.text); + /* Parse it with its own scanner, then resume here. */ + const char *saved_source = c->source; + size_t saved_length = c->length, saved_at = c->at; + int saved_line = c->line, saved_column = c->column, saved_module = c->module; + Token saved_token = c->token, saved_ahead = c->ahead; + bool saved_has_ahead = c->has_ahead; + c->source = source; + c->length = length; + c->at = 0; + c->line = c->column = 1; + c->has_ahead = false; + c->module = library; + c->modules[library].parsing = true; + c->token = scan(c); + separators(c); + while (c->token.kind != T_EOF) library_declaration(c); + c->modules[library].parsing = false; + c->source = saved_source; + c->length = saved_length; + c->at = saved_at; + c->line = saved_line; + c->column = saved_column; + c->module = saved_module; + c->token = saved_token; + c->ahead = saved_ahead; + c->has_ahead = saved_has_ahead; + } + c->modules[c->module].uses[library] = true; +} + +/* A library declares fns and uses only: it has no state and sees none. */ +static void library_declaration(Compiler *c) { + Token kind = expect_name(c, "fn or use"); + if (!strcmp(kind.text, "fn")) fn_declaration(c); + else if (!strcmp(kind.text, "use")) use_declaration(c); + else fail_at(c, kind.line, kind.column, "a library declares only fn and use"); + end(c); +} + static void declaration(Compiler *c) { - Token kind = expect_name(c, "state, record, input, view, or on"); - if (!strcmp(kind.text, "state")) { + Token kind = expect_name(c, "state, record, input, view, fn, use, or on"); + if (!strcmp(kind.text, "fn")) { + fn_declaration(c); + } else if (!strcmp(kind.text, "use")) { + use_declaration(c); + } else if (!strcmp(kind.text, "state")) { state_declaration(c); } else if (!strcmp(kind.text, "record")) { record_declaration(c); @@ -1159,6 +1319,52 @@ static void index_of(Compiler *c, const Expr *e, bool handler, int height, int l c->state_names[list]); } +/* A fn inlined: its arguments go on the stack once, the body reads them with + * PICK, and SLIDE leaves only the result. e names the call (E_FN), or is the + * name of a fn without parameters. */ +static Kind inline_fn(Compiler *c, const Expr *e, int module, const char *name, bool handler, int height) { + int index = fn_index(c, module, name); + if (index < 0) { + if (module != c->module) fail_at(c, e->line, e->column, "library '%s' has no fn '%s'", c->modules[module].name, name); + const char *kind = declared_kind(c, name); + if (kind) fail_at(c, e->line, e->column, "'%s' is a %s, not a fn", name, kind); + fail_at(c, e->line, e->column, "unknown fn '%s'", name); + } + Fn *f = &c->fns[index]; + unsigned count = e->kind == E_FN ? e->count : 0; + if (count != f->param_count) + fail_at(c, e->line, e->column, "%s expects %u argument%s", name, f->param_count, f->param_count == 1 ? "" : "s"); + if (f->active) fail_at(c, e->line, e->column, "fn '%s' calls itself", name); + Scope scope = {.fn = index, .base = height, .line = e->line, .column = e->column}; + for (unsigned i = 0; i < count; ++i) scope.kinds[i] = value(c, e->args[i], handler, height + (int)i); + Scope *outer = c->scope; + int caller = c->module; + c->scope = &scope; + c->module = f->module; + f->active = true; + Kind result = value(c, f->body, handler, height + (int)count); + f->active = false; + c->scope = outer; + c->module = caller; + if (count) emit(c, e->line, e->column, SLIDE, count, 0); + return result; +} + +/* A parameter of the fn being inlined, or -1. */ +static int parameter(const Compiler *c, const char *name) { + if (!c->scope) return -1; + const Fn *f = &c->fns[c->scope->fn]; + for (unsigned i = 0; i < f->param_count; ++i) if (!strcmp(f->params[i], name)) return (int)i; + return -1; +} + +/* A name of the app (a state, input or collection): not visible in a library + * fn, and hidden by a parameter of the same name. */ +static void app_name(Compiler *c, const Expr *e, const char *name) { + if (c->module) fail_at(c, e->line, e->column, "'%s' is not a parameter; a library fn reads only its parameters", name); + if (parameter(c, name) >= 0) fail_at(c, e->line, e->column, "'%s' is a parameter here", name); +} + /* Emit an expression; height is the stack height before it. */ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { if (height + 1 > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values"); @@ -1181,7 +1387,14 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { return (Kind){TEXT, -1}; } case E_NAME: { - if (c->row >= 0 && !strcmp(e->name, c->view_row[c->row])) { + int param = parameter(c, e->name); + if (param >= 0) { + emit(c, e->line, e->column, PICK, (uint32_t)(height - 1 - (c->scope->base + param)), 0); + return c->scope->kinds[param]; + } + if (fn_index(c, c->module, e->name) >= 0) return inline_fn(c, e, c->module, e->name, handler, height); + if (c->module) fail_at(c, e->line, e->column, "'%s' is not a parameter; a library fn reads only its parameters", e->name); + if (!c->scope && c->row >= 0 && !strcmp(e->name, c->view_row[c->row])) { int list = state_index(c, c->view_list[c->row]); emit(c, e->line, e->column, ROW, (uint32_t)list, 0); return (Kind){INDEX, list}; @@ -1209,7 +1422,9 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { } case E_MEMBER: { const Expr *base = e->args[0]; - if (base->kind == E_NAME) { + int library = base->kind == E_NAME && parameter(c, base->name) < 0 ? used_library(c, c->module, base->name) : -1; + if (library >= 0) return inline_fn(c, e, library, e->member, handler, height); /* library.constant */ + if (base->kind == E_NAME && parameter(c, base->name) < 0 && !c->module) { int input = input_index(c, base->name); if (input >= 0 && c->inputs[input].kind == KIND_MESSAGE) { if (input != c->message) @@ -1261,16 +1476,25 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { } fail_at(c, e->line, e->column, "%s has no '%s'", type_name(inner.type), m); } - case E_LINE: { - int text = text_state(c, e->name, e->line, e->column); - cursor_of(c, e->args[0], handler, height, text, e->name); + case E_FN: { + if (!*e->member) return inline_fn(c, e, c->module, e->name, handler, height); + int library = parameter(c, e->member) < 0 ? used_library(c, c->module, e->member) : -1; + if (library >= 0) return inline_fn(c, e, library, e->name, handler, height); + if (strcmp(e->name, "line")) fail_at(c, e->line, e->column, "unknown library '%s'; write use %s first", e->member, e->member); + /* note.line(at): the line a cursor is on. */ + app_name(c, e, e->member); + int text = text_state(c, e->member, e->line, e->column); + if (e->count != 1) fail_at(c, e->line, e->column, "%s.line takes one cursor", e->member); + cursor_of(c, e->args[0], handler, height, text, e->member); emit(c, e->line, e->column, TEXT_LINE, (uint32_t)text, 0); return (Kind){TEXT, -1}; } case E_DOC: + app_name(c, e, e->name); emit(c, e->line, e->column, DOC, collection(c, e->name, e->line, e->column), 0); return (Kind){BYTES, -1}; case E_SIZE: + app_name(c, e, e->name); emit(c, e->line, e->column, DOC_SIZE, collection(c, e->name, e->line, e->column), 0); return (Kind){NUM, -1}; case E_CONVERT: { @@ -1283,6 +1507,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { return (Kind){e->op == TEXT_FROM ? TEXT : BYTES, -1}; } case E_ITEM: { + app_name(c, e, e->name); int list = list_state(c, e->name, e->line, e->column, e->args[0]); const Record *r = record_of(c, list); if (!*e->member) fail_at(c, e->line, e->column, "read a field of the item, as in %s[index].%s", e->name, r->field_names[0]); @@ -1695,18 +1920,21 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) { if (c->constant_used) memcpy(p + used, c->constant_data, c->constant_used); } -bool aup_compile(const char *source, size_t length, unsigned char **module, size_t *size, - AupDiagnostic *error) { +bool aup_compile(const char *source, size_t length, AupLoad load, void *context, + unsigned char **module, size_t *size, AupDiagnostic *error) { *module = NULL; *size = 0; - *error = (AupDiagnostic){0}; + *error = (AupDiagnostic){.line = 0}; Compiler *c = calloc(1, sizeof(*c)); - if (!c) { *error = (AupDiagnostic){1, 1, "out of memory"}; return false; } + if (!c) { *error = (AupDiagnostic){.line = 1, .column = 1, .message = "out of memory"}; return false; } c->source = source; c->length = length; c->line = c->column = 1; c->error = error; c->row = -1; + c->load = load; + c->load_context = context; + c->module_count = 1; /* the main source */ unsigned char *result = NULL; if (!setjmp(c->fail)) { if (length > AUP_SOURCE_LIMIT) fail_at(c, 1, 1, "source exceeds 32768 bytes"); diff --git a/src/main.c b/src/main.c index 3731eec..2797d93 100644 --- a/src/main.c +++ b/src/main.c @@ -5,26 +5,66 @@ #include static int usage(void) { - fputs("aup check FILE\naup build FILE -o OUT.pmod\n", stderr); + fputs("aup check FILE\naup build FILE -o OUT.pmod\n" + "A library `use name` is name.aup beside FILE or in a directory of AUP_PATH (':' separated).\n", stderr); return 2; } +/* Read a whole source; NULL if it cannot be read. Longer than the limit is + * left for the compiler to report. */ +static char *read_source(const char *path, size_t *length) { + FILE *file = fopen(path, "rb"); + if (!file) return NULL; + char *source = malloc(AUP_SOURCE_LIMIT + 1); + *length = source ? fread(source, 1, AUP_SOURCE_LIMIT + 1, file) : 0; + bool failed = !source || ferror(file); + fclose(file); + if (failed) { free(source); return NULL; } + return source; +} + +typedef struct { char directory[AUP_PATH]; char *sources[16]; unsigned count; } Libraries; + +static bool try_library(Libraries *l, const char *directory, size_t directory_length, const char *name, + const char **source, size_t *length, char path[AUP_PATH]) { + int written = snprintf(path, AUP_PATH, "%.*s/%s.aup", (int)directory_length, directory, name); + if (written < 0 || written >= AUP_PATH || l->count == sizeof(l->sources) / sizeof(*l->sources)) return false; + char *text = read_source(path, length); + if (!text) return false; + *source = l->sources[l->count++] = text; + return true; +} + +/* Beside the main source first, then each directory of AUP_PATH in order. */ +static bool load_library(void *context, const char *name, const char **source, size_t *length, char path[AUP_PATH]) { + Libraries *l = context; + if (try_library(l, l->directory, strlen(l->directory), name, source, length, path)) return true; + for (const char *at = getenv("AUP_PATH"); at && *at;) { + size_t span = strcspn(at, ":"); + if (span && try_library(l, at, span, name, source, length, path)) return true; + at += span + (at[span] == ':'); + } + return false; +} + int main(int argc, char **argv) { bool build = argc == 5 && !strcmp(argv[1], "build") && !strcmp(argv[3], "-o"); if (!build && !(argc == 3 && !strcmp(argv[1], "check"))) return usage(); const char *path = argv[2]; - FILE *file = fopen(path, "rb"); - if (!file) { perror(path); return 2; } - static char source[AUP_SOURCE_LIMIT + 1]; - size_t length = fread(source, 1, sizeof(source), file); - bool unreadable = ferror(file); - fclose(file); - if (unreadable) { fprintf(stderr, "%s: cannot read\n", path); return 2; } + size_t length; + char *source = read_source(path, &length); + if (!source) { perror(path); return 2; } + Libraries libraries = {.count = 0}; + const char *slash = strrchr(path, '/'); + snprintf(libraries.directory, sizeof(libraries.directory), "%.*s", slash ? (int)(slash - path) : 1, slash ? path : "."); unsigned char *module; size_t size; AupDiagnostic error; - if (!aup_compile(source, length, &module, &size, &error)) { - fprintf(stderr, "%s:%d:%d: %s\n", path, error.line, error.column, error.message); + bool compiled = aup_compile(source, length, load_library, &libraries, &module, &size, &error); + for (unsigned i = 0; i < libraries.count; ++i) free(libraries.sources[i]); + free(source); + if (!compiled) { + fprintf(stderr, "%s:%d:%d: %s\n", *error.path ? error.path : path, error.line, error.column, error.message); return 1; } int status = 0; -- cgit v1.2.3