diff --git a/examples/fizzbuzz.grr b/examples/fizzbuzz.grr index 0fc260a..ad5a72e 100644 --- a/examples/fizzbuzz.grr +++ b/examples/fizzbuzz.grr @@ -1,4 +1,13 @@ -#load("std.grr") +def print { file/stdout file/write } +def when { [] if } +def keep { over [call] dip } +def bi { [keep] dip call } + +def times { + if: over 0 = + [drop drop] + [swap over >r >r call r> 1 - r> times]; +} def fizzbuzz? { [3 % 0 =] [5 % 0 =] bi or } def fizz { when: 3 % 0 = ["Fizz" print]; } @@ -6,7 +15,7 @@ def buzz { when: 5 % 0 = ["Buzz" print]; } def fizzbuzz1 { if: fizzbuzz? - [ [fizz] keep buzz nl ] + [ [fizz] keep buzz "\n" print ] [ . ]; } diff --git a/meson.build b/meson.build index 00c7f43..0f3bcc3 100644 --- a/meson.build +++ b/meson.build @@ -51,10 +51,12 @@ growlnext_sources = [ 'next/core/compiler.c', 'next/core/dictionary.c', 'next/core/disasm.c', + 'next/core/file.c', 'next/core/gc.c', 'next/core/hash.c', 'next/core/lexer.c', 'next/core/list.c', + 'next/core/native.c', 'next/core/print.c', 'next/core/sleb128.c', 'next/core/string.c', @@ -65,9 +67,13 @@ growlnext_sources = [ 'next/main.c', ] +cc = meson.get_compiler('c') +m_dep = cc.find_library('m', required: false) + growlnext = executable( 'growlnext', growlnext_sources, + dependencies: [m_dep], include_directories: ['next/include'], install: true, ) diff --git a/next/core/alien.c b/next/core/alien.c index 3281c6f..c609aba 100644 --- a/next/core/alien.c +++ b/next/core/alien.c @@ -10,6 +10,16 @@ Growl growl_make_alien(GrowlVM *vm, GrowlAlienType *type, void *data) { return GROWL_BOX(hdr); } +Growl growl_make_alien_tenured(GrowlVM *vm, GrowlAlienType *type, void *data) { + size_t size = sizeof(GrowlObjectHeader) + sizeof(GrowlAlien); + GrowlObjectHeader *hdr = growl_gc_alloc_tenured(vm, size); + hdr->type = GROWL_TYPE_ALIEN; + GrowlAlien *alien = (GrowlAlien *)(hdr + 1); + alien->type = type; + alien->data = data; + return GROWL_BOX(hdr); +} + GrowlAlien *growl_unwrap_alien(Growl obj, GrowlAlienType *type) { if (obj == GROWL_NIL || GROWL_IMM(obj)) return NULL; diff --git a/next/core/compiler.c b/next/core/compiler.c index 4e40496..a751f00 100644 --- a/next/core/compiler.c +++ b/next/core/compiler.c @@ -6,6 +6,9 @@ #include "opcodes.h" #include "sleb128.h" +#include "dynarray.h" + +#define COMPILER_DEBUG 0 typedef struct { Growl *data; @@ -46,12 +49,15 @@ Primitive primitives[] = { {"call", {GOP_CALL, 0}}, {"compose", {GOP_COMPOSE, 0}}, {"curry", {GOP_CURRY, 0}}, + {"dip", {GOP_DIP, 0}}, {".", {GOP_PPRINT, 0}}, {"+", {GOP_ADD, 0}}, {"*", {GOP_MUL, 0}}, {"-", {GOP_SUB, 0}}, {"/", {GOP_DIV, 0}}, {"%", {GOP_MOD, 0}}, + {"and", {GOP_AND, 0}}, + {"or", {GOP_OR, 0}}, {"=", {GOP_EQ, 0}}, {"!=", {GOP_NEQ, 0}}, {"<", {GOP_LT, 0}}, @@ -66,40 +72,6 @@ Primitive primitives[] = { }; // clang-format on -// See https://nullprogram.com/blog/2023/10/05/ -#define push(s, a) \ - ({ \ - typeof(s) s_ = (s); \ - typeof(a) a_ = (a); \ - if (s_->count >= s_->capacity) { \ - grow(s_, sizeof(*s_->data), _Alignof(*s_->data), a_); \ - } \ - s_->data + s_->count++; \ - }) - -static void grow(void *slice, ptrdiff_t size, ptrdiff_t align, GrowlArena *a) { - struct { - uint8_t *data; - ptrdiff_t len; - ptrdiff_t cap; - } replica; - memcpy(&replica, slice, sizeof(replica)); - - if (!replica.data) { - replica.cap = 1; - replica.data = growl_arena_alloc(a, 2 * size, align, replica.cap); - } else if (a->free == replica.data + size * replica.cap) { - growl_arena_alloc(a, size, 1, replica.cap); - } else { - void *data = growl_arena_alloc(a, 2 * size, align, replica.cap); - memcpy(data, replica.data, size * replica.len); - replica.data = data; - } - - replica.cap *= 2; - memcpy(slice, &replica, sizeof(replica)); -} - static void emit_byte(GrowlVM *vm, Chunk *chunk, uint8_t byte) { *push(chunk, &vm->scratch) = byte; } @@ -160,10 +132,8 @@ static void optimize_tail_calls(Chunk *chunk) { } if (i < chunk->count && chunk->data[i] == GOP_RETURN) { if (opcode == GOP_CALL) { - chunk->data[i] = GOP_NOP; chunk->data[start] = GOP_TAIL_CALL; } else if (opcode == GOP_WORD) { - chunk->data[i] = GOP_NOP; chunk->data[start] = GOP_TAIL_WORD; } } @@ -200,7 +170,7 @@ static int compile_quotation(GrowlVM *vm, GrowlLexer *lexer, Chunk *chunk) { } static int compile_string(GrowlVM *vm, GrowlLexer *lexer, Chunk *chunk) { - Growl str = growl_wrap_string(vm, lexer->buffer); + Growl str = growl_wrap_string_tenured(vm, lexer->buffer); size_t const_idx = add_constant(vm, chunk, str); emit_byte(vm, chunk, GOP_PUSH_CONSTANT); emit_sleb128(vm, chunk, (intptr_t)const_idx); @@ -222,6 +192,15 @@ static int compile_def(GrowlVM *vm, GrowlLexer *lexer) { return 1; } + // Add a forward declaration to the dictionary so the word can reference itself + GrowlDictionary *entry = + growl_dictionary_upsert(&vm->dictionary, name, &vm->arena); + GrowlDefinition *def = push(&vm->defs, &vm->arena); + def->name = growl_arena_strdup(&vm->arena, name); + def->callable = GROWL_NIL; // Placeholder, will be filled in after compilation + entry->callable = GROWL_NIL; + entry->index = vm->defs.count - 1; + growl_lexer_next(lexer); Chunk fn_chunk = {0}; while (lexer->kind != GTOK_RBRACE && lexer->kind != GTOK_EOF && @@ -241,14 +220,16 @@ static int compile_def(GrowlVM *vm, GrowlLexer *lexer) { Growl fn = growl_make_quotation(vm, fn_chunk.data, fn_chunk.count, fn_chunk.constants.data, fn_chunk.constants.count); - GrowlQuotation *quot = (GrowlQuotation *)(GROWL_UNBOX(fn) + 1); - GrowlDictionary *entry = - growl_dictionary_upsert(&vm->dictionary, name, &vm->arena); - GrowlDefinition *def = push(&vm->defs, &vm->arena); - def->name = growl_arena_strdup(&vm->arena, name); - def->quotation = quot; - entry->quotation = quot; - entry->index = vm->defs.count - 1; + +#if COMPILER_DEBUG + GrowlQuotation *quot = growl_unwrap_quotation(fn); + fprintf(stderr, "=== %s ===\n", def->name); + growl_disassemble(vm, quot); +#endif + + // Now update the definition with the compiled quotation + def->callable = fn; + entry->callable = fn; growl_lexer_next(lexer); return 0; diff --git a/next/core/disasm.c b/next/core/disasm.c index 5f1b4d2..eec6871 100644 --- a/next/core/disasm.c +++ b/next/core/disasm.c @@ -7,15 +7,15 @@ static void disassemble(GrowlVM *vm, GrowlQuotation *quot, int indent); static size_t disassemble_instr(GrowlVM *vm, GrowlQuotation *quot, size_t offset, int indent) { for (int i = 0; i < indent; i++) { - printf(" "); + fprintf(stderr, " "); } - printf("%04zu ", offset); + fprintf(stderr, "%04zu ", offset); uint8_t opcode = quot->data[offset++]; // clang-format off #define OPCODE(name) case GOP_## name: -#define OPCODE1(name) case GOP_## name: printf(#name "\n"); return offset; +#define OPCODE1(name) case GOP_## name: fprintf(stderr, #name "\n"); return offset; // clang-format on switch (opcode) { @@ -24,26 +24,26 @@ static size_t disassemble_instr(GrowlVM *vm, GrowlQuotation *quot, OPCODE(PUSH_CONSTANT) { intptr_t idx; size_t bytes_read = growl_sleb128_peek("->data[offset], &idx); - printf("PUSH_CONSTANT %ld", idx); + fprintf(stderr, "PUSH_CONSTANT %ld", idx); if (quot->constants != GROWL_NIL && growl_type(quot->constants) == GROWL_TYPE_TUPLE) { GrowlTuple *constants = growl_unwrap_tuple(quot->constants); if (idx >= 0 && (size_t)idx < constants->count) { Growl constant = constants->data[idx]; - printf(" ("); - growl_print(constant); - printf(")"); + fprintf(stderr, " ("); + growl_print_to(stderr, constant); + fprintf(stderr, ")"); if (!GROWL_IMM(constant) && constant != GROWL_NIL && growl_type(constant) == GROWL_TYPE_QUOTATION) { - putchar('\n'); + putc('\n', stderr); GrowlQuotation *inner = growl_unwrap_quotation(constant); disassemble(vm, inner, indent + 1); return offset + bytes_read; } } } - putchar('\n'); + putc('\n', stderr); return offset + bytes_read; } OPCODE1(DROP); @@ -61,22 +61,24 @@ static size_t disassemble_instr(GrowlVM *vm, GrowlQuotation *quot, OPCODE1(CHOOSE); OPCODE1(CALL); OPCODE1(CALL_NEXT); + OPCODE1(PUSH_NEXT); OPCODE1(TAIL_CALL); OPCODE(WORD) { intptr_t idx; size_t bytes_read = growl_sleb128_peek("->data[offset], &idx); - printf("WORD %s\n", vm->defs.data[idx].name); + fprintf(stderr, "WORD %s\n", vm->defs.data[idx].name); return offset + bytes_read; } OPCODE(TAIL_WORD) { intptr_t idx; size_t bytes_read = growl_sleb128_peek("->data[offset], &idx); - printf("TAIL_WORD %s\n", vm->defs.data[idx].name); + fprintf(stderr, "TAIL_WORD %s\n", vm->defs.data[idx].name); return offset + bytes_read; } OPCODE1(RETURN); OPCODE1(COMPOSE); OPCODE1(CURRY); + OPCODE1(DIP); OPCODE1(PPRINT); OPCODE1(ADD); OPCODE1(MUL); diff --git a/next/core/dynarray.h b/next/core/dynarray.h new file mode 100644 index 0000000..1a2a463 --- /dev/null +++ b/next/core/dynarray.h @@ -0,0 +1,42 @@ +#ifndef GROWL_DYNARRAY_H +#define GROWL_DYNARRAY_H + +// See https://nullprogram.com/blog/2023/10/05/ + +#include +#include +#include +#include + +#define push(s, a) \ + ({ \ + typeof(s) s_ = (s); \ + typeof(a) a_ = (a); \ + if (s_->count >= s_->capacity) { \ + __grow(s_, sizeof(*s_->data), _Alignof(*s_->data), a_); \ + } \ + s_->data + s_->count++; \ + }) + +static void __grow(void *slice, ptrdiff_t size, ptrdiff_t align, GrowlArena *a) { + struct { + uint8_t *data; + ptrdiff_t len; + ptrdiff_t cap; + } replica; + memcpy(&replica, slice, sizeof(replica)); + if (!replica.data) { + replica.cap = 1; + replica.data = growl_arena_alloc(a, 2 * size, align, replica.cap); + } else if (a->free == replica.data + size * replica.cap) { + growl_arena_alloc(a, size, 1, replica.cap); + } else { + void *data = growl_arena_alloc(a, 2 * size, align, replica.cap); + memcpy(data, replica.data, size * replica.len); + replica.data = data; + } + replica.cap *= 2; + memcpy(slice, &replica, sizeof(replica)); +} + +#endif // GROWL_DYNARRAY_H diff --git a/next/core/file.c b/next/core/file.c new file mode 100644 index 0000000..30b89cb --- /dev/null +++ b/next/core/file.c @@ -0,0 +1,49 @@ +#include + +static void file_finalize(void *data) { + FILE *f = data; + if (f && f != stdin && f != stdout && f != stderr) + fclose(f); +} + +// clang-format off +static GrowlAlienType alien_file_type = { + .name = "file", + .finalizer = file_finalize, + .call = NULL, +}; +// clang-format on + +static Growl stdout_obj = GROWL_NIL; +static void native_file_stdout(GrowlVM *vm) { + if (stdout_obj == GROWL_NIL) { + GrowlObjectHeader *hdr = growl_gc_alloc_tenured( + vm, sizeof(GrowlObjectHeader) + sizeof(GrowlAlien)); + hdr->type = GROWL_TYPE_ALIEN; + GrowlAlien *stdout_alien = (GrowlAlien *)(hdr + 1); + stdout_alien->data = stdout; + stdout_alien->type = &alien_file_type; + stdout_obj = GROWL_BOX(hdr); + } + growl_push(vm, stdout_obj); +} + +static void native_file_write(GrowlVM *vm) { + Growl file_obj = growl_pop(vm); + Growl string_obj = growl_pop(vm); + + GrowlAlien *file_alien = growl_unwrap_alien(file_obj, &alien_file_type); + if (file_alien == NULL) + growl_vm_error(vm, "expected file object"); + + GrowlString *str = growl_unwrap_string(string_obj); + if (str == NULL) + growl_vm_error(vm, "expected string"); + + fwrite(str->data, sizeof(char), str->len, file_alien->data); +} + +void growl_register_file_library(GrowlVM *vm) { + growl_register_native(vm, "file/stdout", native_file_stdout); + growl_register_native(vm, "file/write", native_file_write); +} diff --git a/next/core/gc.c b/next/core/gc.c index 8bfe3cf..87056e9 100644 --- a/next/core/gc.c +++ b/next/core/gc.c @@ -139,14 +139,26 @@ void growl_gc_collect(GrowlVM *vm) { gc_print_stats(vm, "before GC"); #endif + // Forward work stack for (size_t i = 0; i < GROWL_STACK_SIZE; ++i) { vm->wst[i] = forward(vm, vm->wst[i]); } + // Forward retain stack + for (size_t i = 0; i < GROWL_STACK_SIZE; ++i) { + vm->rst[i] = forward(vm, vm->rst[i]); + } + + // Forward GC roots for (size_t i = 0; i < vm->root_count; ++i) { *vm->roots[i] = forward(vm, *vm->roots[i]); } + // Forward word definitions + for (size_t i = 0; i < vm->defs.count; ++i) { + vm->defs.data[i].callable = forward(vm, vm->defs.data[i].callable); + } + uint8_t *tenured_scan = vm->tenured.start; while (tenured_scan < vm->tenured.free) { GrowlObjectHeader *hdr = (GrowlObjectHeader *)tenured_scan; diff --git a/next/core/lexer.c b/next/core/lexer.c index 2a95117..01843ff 100644 --- a/next/core/lexer.c +++ b/next/core/lexer.c @@ -134,7 +134,7 @@ int growl_lexer_next(GrowlLexer *lexer) { switch (next) { case '\\': - for (; next != '\n'; next = getc_ws(lexer)) + for (; next != '\n'; next = lexer_getc(lexer)) ; return growl_lexer_next(lexer); case '(': diff --git a/next/core/native.c b/next/core/native.c new file mode 100644 index 0000000..3541f50 --- /dev/null +++ b/next/core/native.c @@ -0,0 +1,29 @@ +#include +#include + +#include "dynarray.h" + +static void call_native(GrowlVM *vm, void *data) { + void (*fn)(GrowlVM *) = data; + fn(vm); +} + +// clang-format off +static GrowlAlienType native_type = { + .name = "native", + .finalizer = NULL, + .call = call_native, +}; +// clang-format on + +void growl_register_native(GrowlVM *vm, const char *name, + void (*fn)(GrowlVM *)) { + Growl alien = growl_make_alien_tenured(vm, &native_type, (void *)fn); + GrowlDictionary *entry = + growl_dictionary_upsert(&vm->dictionary, name, &vm->arena); + GrowlDefinition *def = push(&vm->defs, &vm->arena); + def->name = growl_arena_strdup(&vm->arena, name); + def->callable = alien; + entry->callable = alien; + entry->index = vm->defs.count - 1; +} diff --git a/next/core/opcodes.h b/next/core/opcodes.h index b6c6f8b..8ebf6c5 100644 --- a/next/core/opcodes.h +++ b/next/core/opcodes.h @@ -5,6 +5,7 @@ enum GrowlOpcode { GOP_NOP = 0, GOP_PUSH_NIL, GOP_PUSH_CONSTANT, + GOP_PUSH_NEXT, GOP_DROP, GOP_DUP, GOP_SWAP, @@ -26,6 +27,7 @@ enum GrowlOpcode { GOP_RETURN, GOP_COMPOSE, GOP_CURRY, + GOP_DIP, GOP_PPRINT, GOP_ADD, GOP_MUL, @@ -36,6 +38,8 @@ enum GrowlOpcode { GOP_BOR, GOP_BXOR, GOP_BNOT, + GOP_AND, + GOP_OR, GOP_EQ, GOP_NEQ, GOP_LT, diff --git a/next/core/print.c b/next/core/print.c index bc0d4b0..f81129d 100644 --- a/next/core/print.c +++ b/next/core/print.c @@ -8,57 +8,56 @@ void growl_println(Growl value) { putchar('\n'); } -static void print_escaped(const char *data, size_t len) { - putchar('"'); +static void print_escaped(FILE *file, const char *data, size_t len) { + putc('"', file); for (size_t i = 0; i < len; ++i) { switch (data[i]) { case '\0': - putchar('\\'); - putchar('0'); + putc('\\', file); + putc('0', file); break; case '\t': - putchar('\\'); - putchar('t'); + putc('\\', file); + putc('t', file); break; case '\n': - putchar('\\'); - putchar('n'); + putc('\\', file); + putc('n', file); break; case '\r': - putchar('\\'); - putchar('r'); + putc('\\', file); + putc('r', file); break; case '\b': - putchar('\\'); - putchar('b'); + putc('\\', file); + putc('b', file); break; case '\v': - putchar('\\'); - putchar('v'); + putc('\\', file); + putc('v', file); break; case '\f': - putchar('\\'); - putchar('f'); + putc('\\', file); + putc('f', file); break; case '\x1b': - putchar('\\'); - putchar('e'); + putc('\\', file); + putc('e', file); break; case '\\': - putchar('\\'); - putchar('\\'); + putc('\\', file); + putc('\\', file); break; case '"': - putchar('\\'); - putchar('"'); + putc('\\', file); + putc('"', file); break; default: - putchar(data[i]); + putc(data[i], file); break; - } } - putchar('"'); + putc('"', file); } void growl_print_to(FILE *file, Growl value) { @@ -71,7 +70,7 @@ void growl_print_to(FILE *file, Growl value) { switch (hdr->type) { case GROWL_TYPE_STRING: { GrowlString *str = (GrowlString *)(hdr + 1); - print_escaped(str->data, str->len); + print_escaped(file, str->data, str->len); break; } default: diff --git a/next/core/string.c b/next/core/string.c index bf913bd..39eca36 100644 --- a/next/core/string.c +++ b/next/core/string.c @@ -23,6 +23,18 @@ Growl growl_wrap_string(GrowlVM *vm, const char *cstr) { return GROWL_BOX(hdr); } +Growl growl_wrap_string_tenured(GrowlVM *vm, const char *cstr) { + size_t len = strlen(cstr); + size_t size = sizeof(GrowlObjectHeader) + sizeof(GrowlString) + len + 1; + GrowlObjectHeader *hdr = growl_gc_alloc_tenured(vm, size); + hdr->type = GROWL_TYPE_STRING; + GrowlString *str = (GrowlString *)(hdr + 1); + str->len = len; + memcpy(str->data, cstr, len); + str->data[len] = 0; + return GROWL_BOX(hdr); +} + GrowlString *growl_unwrap_string(Growl obj) { if (obj == 0 || GROWL_IMM(obj)) return NULL; diff --git a/next/core/vm.c b/next/core/vm.c index 8634fd3..88d5262 100644 --- a/next/core/vm.c +++ b/next/core/vm.c @@ -31,9 +31,17 @@ GrowlVM *growl_vm_init(void) { vm->root_count = 0; vm->root_capacity = 0; - static uint8_t trampoline_code[] = {GOP_CALL_NEXT}; - Growl trampoline = growl_make_quotation(vm, trampoline_code, 1, NULL, 0); - vm->compose_trampoline = (GrowlQuotation *)(GROWL_UNBOX(trampoline) + 1); + static uint8_t compose_code[] = {GOP_CALL_NEXT}; + Growl compose_tramp = growl_make_quotation(vm, compose_code, 1, NULL, 0); + vm->compose_trampoline = (GrowlQuotation *)(GROWL_UNBOX(compose_tramp) + 1); + + static uint8_t return_code[] = {GOP_RETURN}; + Growl return_tramp = growl_make_quotation(vm, return_code, 1, NULL, 0); + vm->return_trampoline = (GrowlQuotation *)(GROWL_UNBOX(return_tramp) + 1); + + static uint8_t dip_code[] = {GOP_PUSH_NEXT, GOP_RETURN}; + Growl dip_tramp = growl_make_quotation(vm, dip_code, 2, NULL, 0); + vm->dip_trampoline = (GrowlQuotation *)(GROWL_UNBOX(dip_tramp) + 1); return vm; } @@ -49,8 +57,8 @@ void growl_vm_free(GrowlVM *vm) { free(vm); } -__attribute__((format(printf, 2, 3))) static noreturn void -vm_error(GrowlVM *vm, const char *fmt, ...) { +__attribute__((format(printf, 2, 3))) noreturn void +growl_vm_error(GrowlVM *vm, const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "vm: "); @@ -62,19 +70,19 @@ vm_error(GrowlVM *vm, const char *fmt, ...) { void growl_push(GrowlVM *vm, Growl obj) { if (vm->sp >= vm->wst + GROWL_STACK_SIZE) - vm_error(vm, "work stack overflow"); + growl_vm_error(vm, "work stack overflow"); *vm->sp++ = obj; } Growl growl_peek(GrowlVM *vm, size_t depth) { if (vm->sp <= vm->wst + depth) - vm_error(vm, "work stack underflow"); + growl_vm_error(vm, "work stack underflow"); return vm->sp[-(depth + 1)]; } Growl growl_pop(GrowlVM *vm) { if (vm->sp <= vm->wst) - vm_error(vm, "work stack underflow"); + growl_vm_error(vm, "work stack underflow"); Growl obj = *--vm->sp; *vm->sp = GROWL_NIL; return obj; @@ -82,13 +90,13 @@ Growl growl_pop(GrowlVM *vm) { void growl_rpush(GrowlVM *vm, Growl obj) { if (vm->rsp >= vm->rst + GROWL_STACK_SIZE) - vm_error(vm, "work stack overflow"); + growl_vm_error(vm, "work stack overflow"); *vm->rsp++ = obj; } Growl growl_rpop(GrowlVM *vm) { if (vm->rsp <= vm->rst) - vm_error(vm, "work stack underflow"); + growl_vm_error(vm, "work stack underflow"); Growl obj = *--vm->rsp; *vm->rsp = GROWL_NIL; return obj; @@ -96,7 +104,7 @@ Growl growl_rpop(GrowlVM *vm) { static void callstack_push(GrowlVM *vm, GrowlQuotation *q, uint8_t *ip) { if (vm->csp >= vm->cst + GROWL_CALL_STACK_SIZE) - vm_error(vm, "call stack overflow"); + growl_vm_error(vm, "call stack overflow"); vm->csp->quot = q; vm->csp->ip = ip; vm->csp->next = GROWL_NIL; @@ -105,11 +113,12 @@ static void callstack_push(GrowlVM *vm, GrowlQuotation *q, uint8_t *ip) { static GrowlFrame callstack_pop(GrowlVM *vm) { if (vm->csp <= vm->cst) - vm_error(vm, "call stack underflow"); + growl_vm_error(vm, "call stack underflow"); return *--vm->csp; } -static inline void dispatch(GrowlVM *vm, Growl obj) { +static inline void dispatch(GrowlVM *vm, Growl obj, + int tail __attribute__((unused))) { for (;;) { switch (growl_type(obj)) { case GROWL_TYPE_QUOTATION: { @@ -131,8 +140,28 @@ static inline void dispatch(GrowlVM *vm, Growl obj) { obj = c->callable; continue; } + case GROWL_TYPE_ALIEN: { + GrowlAlien *alien = (GrowlAlien *)(GROWL_UNBOX(obj) + 1); + if (alien->type && alien->type->call) { + alien->type->call(vm, alien->data); + // After calling a native function, we need to return to the caller + if (vm->csp != vm->cst) { + GrowlFrame frame = callstack_pop(vm); + vm->current_quotation = frame.quot; + vm->ip = frame.ip; + vm->next = frame.next; + } else { + // No frames on call stack, use return trampoline to exit + vm->current_quotation = vm->return_trampoline; + vm->ip = vm->return_trampoline->data; + } + return; + } + growl_vm_error(vm, "attempt to call non-callable alien"); + } default: - vm_error(vm, "attempt to call non-callable"); + growl_vm_error(vm, "attempt to call non-callable (type=%d)", + growl_type(obj)); } } } @@ -171,13 +200,18 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { if (idx >= 0 && (size_t)idx < constants->count) { growl_push(vm, constants->data[idx]); } else { - vm_error(vm, "constant index %" PRIdPTR " out of bounds", idx); + growl_vm_error(vm, "constant index %" PRIdPTR " out of bounds", idx); } } else { - vm_error(vm, "attempt to index nil constant table"); + growl_vm_error(vm, "attempt to index nil constant table"); } VM_NEXT(); } + VM_OP(PUSH_NEXT) { + growl_push(vm, vm->next); + vm->next = GROWL_NIL; + VM_NEXT(); + } VM_OP(DROP) { (void)growl_pop(vm); VM_NEXT(); @@ -251,9 +285,9 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { VM_NEXT(); } VM_OP(CHOOSE) { - Growl cond = growl_pop(vm); Growl f = growl_pop(vm); Growl t = growl_pop(vm); + Growl cond = growl_pop(vm); if (cond != GROWL_NIL) { growl_push(vm, t); } else { @@ -264,32 +298,33 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { VM_OP(CALL) { Growl obj = growl_pop(vm); callstack_push(vm, vm->current_quotation, vm->ip); - dispatch(vm, obj); + dispatch(vm, obj, 0); VM_NEXT(); } VM_OP(CALL_NEXT) { - growl_push(vm, vm->compose_next); - vm->compose_next = GROWL_NIL; - __attribute__((__fallthrough__)); + Growl callable = vm->next; + vm->next = GROWL_NIL; + dispatch(vm, callable, 1); + VM_NEXT(); } VM_OP(TAIL_CALL) { Growl obj = growl_pop(vm); - dispatch(vm, obj); + dispatch(vm, obj, 1); VM_NEXT(); } VM_OP(WORD) { intptr_t idx = growl_sleb128_decode(&vm->ip); GrowlDefinition *def = &vm->defs.data[idx]; - Growl word = GROWL_BOX((GrowlObjectHeader *)(def->quotation) - 1); + Growl word = def->callable; callstack_push(vm, vm->current_quotation, vm->ip); - dispatch(vm, word); + dispatch(vm, word, 0); VM_NEXT(); } VM_OP(TAIL_WORD) { intptr_t idx = growl_sleb128_decode(&vm->ip); GrowlDefinition *def = &vm->defs.data[idx]; - Growl word = GROWL_BOX((GrowlObjectHeader *)(def->quotation) - 1); - dispatch(vm, word); + Growl word = def->callable; + dispatch(vm, word, 1); VM_NEXT(); } VM_OP(RETURN) { @@ -297,7 +332,7 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { GrowlFrame frame = callstack_pop(vm); vm->current_quotation = frame.quot; vm->ip = frame.ip; - vm->compose_next = frame.next; + vm->next = frame.next; } else { goto done; } @@ -308,7 +343,7 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { Growl first = growl_pop(vm); Growl composed = growl_compose(vm, first, second); if (composed == GROWL_NIL) - vm_error(vm, "attempt to compose with a non-callable"); + growl_vm_error(vm, "attempt to compose with a non-callable"); growl_push(vm, composed); VM_NEXT(); } @@ -317,10 +352,19 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { Growl value = growl_pop(vm); Growl curried = growl_curry(vm, value, callable); if (curried == GROWL_NIL) - vm_error(vm, "attempt to curry with a non-callable"); + growl_vm_error(vm, "attempt to curry with a non-callable"); growl_push(vm, curried); VM_NEXT(); } + VM_OP(DIP) { + Growl callable = growl_pop(vm); + Growl x = growl_pop(vm); + callstack_push(vm, vm->current_quotation, vm->ip); + callstack_push(vm, vm->dip_trampoline, vm->dip_trampoline->data); + vm->csp[-1].next = x; + dispatch(vm, callable, 0); + VM_NEXT(); + } VM_OP(PPRINT) { growl_println(growl_pop(vm)); VM_NEXT(); @@ -333,7 +377,7 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { if (GROWL_IMM(b) && GROWL_IMM(a)) { \ growl_push(vm, GROWL_NUM(GROWL_ORD(a) op GROWL_ORD(b))); \ } else { \ - vm_error(vm, "numeric op on non-numbers"); \ + growl_vm_error(vm, "numeric op on non-numbers"); \ } \ VM_NEXT(); \ } @@ -346,10 +390,10 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { Growl a = growl_pop(vm); if (GROWL_IMM(b) && GROWL_IMM(a)) { if (GROWL_ORD(b) == 0) - vm_error(vm, "division by zero"); + growl_vm_error(vm, "division by zero"); growl_push(vm, GROWL_NUM(GROWL_ORD(a) / GROWL_ORD(b))); } else { - vm_error(vm, "numeric op on non-numbers"); + growl_vm_error(vm, "numeric op on non-numbers"); }; VM_NEXT(); } @@ -358,10 +402,10 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { Growl a = growl_pop(vm); if (GROWL_IMM(b) && GROWL_IMM(a)) { if (GROWL_ORD(b) == 0) - vm_error(vm, "division by zero"); + growl_vm_error(vm, "division by zero"); growl_push(vm, GROWL_NUM(GROWL_ORD(a) % GROWL_ORD(b))); } else { - vm_error(vm, "numeric op on non-numbers"); + growl_vm_error(vm, "numeric op on non-numbers"); }; VM_NEXT(); } @@ -373,7 +417,27 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { if (GROWL_IMM(a)) { growl_push(vm, GROWL_NUM(~GROWL_ORD(a))); } else { - vm_error(vm, "arithmetic on non-numbers"); + growl_vm_error(vm, "arithmetic on non-numbers"); + } + VM_NEXT(); + } + VM_OP(AND) { + Growl b = growl_pop(vm); + Growl a = growl_pop(vm); + if (a == GROWL_NIL) { + growl_push(vm, a); + } else { + growl_push(vm, b); + } + VM_NEXT(); + } + VM_OP(OR) { + Growl b = growl_pop(vm); + Growl a = growl_pop(vm); + if (a == GROWL_NIL) { + growl_push(vm, b); + } else { + growl_push(vm, a); } VM_NEXT(); } @@ -399,7 +463,29 @@ int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot) { } VM_NEXT(); } - VM_DEFAULT() { vm_error(vm, "unknown opcode %d", opcode); } + +#define VM_CMPOP(name, op) \ + case GOP_##name: { \ + Growl b = growl_pop(vm); \ + Growl a = growl_pop(vm); \ + if (GROWL_IMM(b) && GROWL_IMM(a)) { \ + if (GROWL_ORD(a) op GROWL_ORD(b)) { \ + growl_push(vm, GROWL_NUM(1)); \ + } else { \ + growl_push(vm, GROWL_NIL); \ + } \ + } else { \ + growl_vm_error(vm, "comparison on non-numbers"); \ + } \ + VM_NEXT(); \ + } + + VM_CMPOP(LT, <); + VM_CMPOP(LTE, <=); + VM_CMPOP(GT, >); + VM_CMPOP(GTE, >=); + + VM_DEFAULT() { growl_vm_error(vm, "unknown opcode %d", opcode); } VM_END() done: diff --git a/next/include/growl.h b/next/include/growl.h index 3821f55..bb6d2cc 100644 --- a/next/include/growl.h +++ b/next/include/growl.h @@ -5,6 +5,7 @@ #include #include #include +#include typedef uintptr_t Growl; @@ -28,6 +29,8 @@ typedef struct GrowlAlien GrowlAlien; typedef struct GrowlLexer GrowlLexer; typedef struct GrowlArena GrowlArena; typedef struct GrowlFrame GrowlFrame; +typedef struct GrowlModule GrowlModule; +typedef struct GrowlCompileContext GrowlCompileContext; typedef struct GrowlDictionary GrowlDictionary; typedef struct GrowlDefinition GrowlDefinition; typedef struct GrowlDefinitionTable GrowlDefinitionTable; @@ -52,6 +55,8 @@ struct GrowlObjectHeader { }; uint32_t growl_type(Growl obj); +int growl_equals(Growl a, Growl b); + uint64_t growl_hash_combine(uint64_t a, uint64_t b); uint64_t growl_hash_bytes(const uint8_t *data, size_t len); uint64_t growl_hash(Growl obj); @@ -60,8 +65,6 @@ void growl_print_to(FILE *file, Growl value); void growl_print(Growl value); void growl_println(Growl value); -int growl_equals(Growl a, Growl b); - struct GrowlString { size_t len; char data[]; @@ -69,6 +72,7 @@ struct GrowlString { Growl growl_make_string(GrowlVM *vm, size_t len); Growl growl_wrap_string(GrowlVM *vm, const char *cstr); +Growl growl_wrap_string_tenured(GrowlVM *vm, const char *cstr); GrowlString *growl_unwrap_string(Growl obj); struct GrowlList { @@ -85,7 +89,6 @@ GrowlTuple *growl_unwrap_tuple(Growl obj); struct GrowlTable {}; GrowlTable *growl_unwrap_table(Growl obj); -GrowlTable *growl_table_upsert(GrowlVM *vm, GrowlTable **table, Growl key); struct GrowlQuotation { size_t count; @@ -112,6 +115,7 @@ GrowlCurry *growl_unwrap_curry(Growl obj); struct GrowlAlienType { const char *name; + void (*call)(GrowlVM *, void *); void (*finalizer)(void *); }; @@ -121,7 +125,10 @@ struct GrowlAlien { }; Growl growl_make_alien(GrowlVM *vm, GrowlAlienType *type, void *data); +Growl growl_make_alien_tenured(GrowlVM *vm, GrowlAlienType *type, void *data); GrowlAlien *growl_unwrap_alien(Growl obj, GrowlAlienType *type); +void growl_register_native(GrowlVM *vm, const char *name, + void (*fn)(GrowlVM *)); /** Lexer */ enum { @@ -179,7 +186,7 @@ struct GrowlFrame { struct GrowlDefinition { const char *name; - GrowlQuotation *quotation; + Growl callable; }; struct GrowlDefinitionTable { @@ -190,10 +197,20 @@ struct GrowlDefinitionTable { struct GrowlDictionary { GrowlDictionary *child[4]; const char *name; - GrowlQuotation *quotation; + Growl callable; size_t index; }; +struct GrowlModule { + char *resolved_path; + GrowlModule *next; +}; + +struct GrowlCompileContext { + GrowlCompileContext *parent; + const char *file_path, *file_dir; +}; + GrowlDictionary *growl_dictionary_upsert(GrowlDictionary **dict, const char *name, GrowlArena *perm); @@ -213,7 +230,9 @@ struct GrowlVM { GrowlFrame cst[GROWL_CALL_STACK_SIZE], *csp; GrowlQuotation *compose_trampoline; - Growl compose_next; + GrowlQuotation *return_trampoline; + GrowlQuotation *dip_trampoline; + Growl next; Growl **roots; size_t root_count, root_capacity; @@ -229,10 +248,19 @@ void growl_gc_collect(GrowlVM *vm); void growl_gc_root(GrowlVM *vm, Growl *ptr); size_t growl_gc_mark(GrowlVM *vm); void growl_gc_reset(GrowlVM *vm, size_t mark); +void growl_push(GrowlVM *vm, Growl obj); +Growl growl_peek(GrowlVM *vm, size_t depth); +Growl growl_pop(GrowlVM *vm); +void growl_rpush(GrowlVM *vm, Growl obj); +Growl growl_rpop(GrowlVM *vm); +noreturn void growl_vm_error(GrowlVM *vm, const char *fmt, ...); int growl_vm_execute(GrowlVM *vm, GrowlQuotation *quot); /** Compiler */ Growl growl_compile(GrowlVM *vm, GrowlLexer *lexer); void growl_disassemble(GrowlVM *vm, GrowlQuotation *quot); +/** Extra libraries */ +void growl_register_file_library(GrowlVM *vm); + #endif // GROWL_H diff --git a/next/main.c b/next/main.c index 2d11435..4976e06 100644 --- a/next/main.c +++ b/next/main.c @@ -1,23 +1,25 @@ -#include "core/opcodes.h" #include +#include int main(void) { GrowlVM *vm = growl_vm_init(); + growl_register_file_library(vm); GrowlLexer lexer = {0}; lexer.file = stdin; Growl obj = growl_compile(vm, &lexer); if (obj != GROWL_NIL) { GrowlQuotation *quot = growl_unwrap_quotation(obj); - growl_disassemble(vm, quot); - growl_vm_execute(vm, quot); - - printf("Stack:"); - for (Growl *p = vm->wst; p < vm->sp; p++) { - putchar(' '); - growl_print(*p); + if (!growl_vm_execute(vm, quot)) { + if (vm->sp != vm->wst) { + fprintf(stderr, "Stack:"); + for (Growl *p = vm->wst; p < vm->sp; p++) { + putc(' ', stderr); + growl_print_to(stderr, *p); + } + putchar('\n'); + } } - putchar('\n'); } growl_gc_collect(vm); diff --git a/std.grr b/std.grr index 3bcce87..d89b60d 100644 --- a/std.grr +++ b/std.grr @@ -8,7 +8,6 @@ def eprintln { stderr fprint "\n" stderr fprint } def when { [] if } def unless { swap when } -def dip { swap [] curry compose call } def 2dip { swap [dip] dip } def 3dip { swap [2dip] dip }