This commit is contained in:
Lobo 2026-02-09 10:42:23 -03:00
parent 1746d12266
commit d279bf1d31
17 changed files with 407 additions and 137 deletions

View file

@ -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 ]
[ . ];
}

View file

@ -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,
)

View file

@ -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;

View file

@ -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;

View file

@ -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(&quot->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(&quot->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(&quot->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);

42
next/core/dynarray.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef GROWL_DYNARRAY_H
#define GROWL_DYNARRAY_H
// See https://nullprogram.com/blog/2023/10/05/
#include <growl.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#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

49
next/core/file.c Normal file
View file

@ -0,0 +1,49 @@
#include <growl.h>
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);
}

View file

@ -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;

View file

@ -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 '(':

29
next/core/native.c Normal file
View file

@ -0,0 +1,29 @@
#include <growl.h>
#include <string.h>
#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;
}

View file

@ -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,

View file

@ -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:

View file

@ -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;

View file

@ -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:

View file

@ -5,6 +5,7 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdnoreturn.h>
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

View file

@ -1,23 +1,25 @@
#include "core/opcodes.h"
#include <growl.h>
#include <math.h>
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);

View file

@ -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 }