aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-03-30 12:14:24 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-03-30 12:14:24 -0400
commit838c9963fc2216e827ceed80fc352d1d16ea30f9 (patch)
tree82eed3eb921ae36ba745122ed6b182d8b92ec329
parent04d9adc8138566eec5d6bf7b233a6c617306bcce (diff)
Adding a REPL
-rw-r--r--Makefile6
-rw-r--r--environment.h2
-rw-r--r--parse.c2
-rw-r--r--repl.c407
-rw-r--r--repl.h5
-rw-r--r--tomo.c9
6 files changed, 423 insertions, 8 deletions
diff --git a/Makefile b/Makefile
index f94f27b2..2262b3e7 100644
--- a/Makefile
+++ b/Makefile
@@ -23,13 +23,13 @@ EXTRA=
G=-ggdb
O=-Og
CFLAGS=$(CCONFIG) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS)
-LDLIBS=-lgc -lcord -lm -lunistring
+LDLIBS=-lgc -lcord -lm -lunistring -ldl
BUILTIN_OBJS=builtins/array.o builtins/bool.o builtins/color.o builtins/nums.o builtins/functions.o builtins/integers.o \
builtins/pointer.o builtins/memory.o builtins/text.o builtins/table.o builtins/types.o builtins/util.o builtins/files.o
-all: libtomo.so tomo repl.o
+all: libtomo.so tomo
-tomo: tomo.c SipHash/halfsiphash.o ast.o parse.o environment.o types.o typecheck.o structs.o enums.o compile.o $(BUILTIN_OBJS)
+tomo: tomo.c SipHash/halfsiphash.o ast.o parse.o environment.o types.o typecheck.o structs.o enums.o compile.o repl.o $(BUILTIN_OBJS)
libtomo.so: $(BUILTIN_OBJS) SipHash/halfsiphash.o
$(CC) $^ $(CFLAGS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LDLIBS) -Wl,-soname,libtomo.so -shared -o $@
diff --git a/environment.h b/environment.h
index fbff64b1..dc7f527c 100644
--- a/environment.h
+++ b/environment.h
@@ -45,7 +45,7 @@ typedef struct {
type_t *type;
union {
CORD code;
- char value[0];
+ void *value;
};
} binding_t;
diff --git a/parse.c b/parse.c
index 5355f91e..e683bb63 100644
--- a/parse.c
+++ b/parse.c
@@ -1899,7 +1899,7 @@ ast_t *parse_file(file_t *file, jmp_buf *on_err) {
ast_t *ast = parse_namespace(&ctx, pos);
pos = ast->end;
whitespace(&pos);
- if (pos < file->text + file->len) {
+ if (pos < file->text + file->len && *pos != '\0') {
parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the file");
}
return ast;
diff --git a/repl.c b/repl.c
new file mode 100644
index 00000000..fa00a7dc
--- /dev/null
+++ b/repl.c
@@ -0,0 +1,407 @@
+
+#include <dlfcn.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "builtins/tomo.h"
+#include "typecheck.h"
+#include "parse.h"
+
+typedef union {
+ int8_t i8;
+ int16_t i16;
+ int32_t i32;
+ int64_t i64;
+ double n64;
+ float n32;
+} number_t;
+
+static jmp_buf on_err;
+
+void run(env_t *env, ast_t *ast);
+void eval(env_t *env, ast_t *ast, void *dest);
+
+void repl(void)
+{
+ env_t *env = new_compilation_unit();
+ void *dl = dlopen("libtomo.so", RTLD_LAZY);
+ if (!dl) errx(1, "I couldn't find libtomo.so in your library paths");
+
+ size_t buf_size = 0;
+ char *line = NULL;
+ ssize_t len = 0;
+
+ if (setjmp(on_err))
+ printf("\n");
+
+ printf("\x1b[33;1m>>\x1b[m ");
+ fflush(stdout);
+
+ while ((len=getline(&line, &buf_size, stdin)) >= 0) {
+ file_t *f = spoof_file("<repl>", heap_strf(">> %s", line));
+ ast_t *ast = parse_file(f, &on_err);
+ ast = WrapAST(ast, DocTest, .expr=ast, .skip_source=true);
+ run(env, ast);
+ printf("\x1b[33;1m>>\x1b[m ");
+ fflush(stdout);
+ }
+ if (line) free(line);
+ printf("\n");
+}
+
+__attribute__((noreturn))
+static void repl_err(ast_t *node, const char *fmt, ...)
+{
+ bool color = isatty(STDERR_FILENO) && !getenv("NO_COLOR");
+ if (color)
+ fputs("\x1b[31;7;1m", stderr);
+ if (node)
+ fprintf(stderr, "%s:%ld.%ld: ", node->file->relative_filename, get_line_number(node->file, node->start),
+ get_line_column(node->file, node->start));
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ if (color)
+ fputs(" \x1b[m", stderr);
+ fputs("\n\n", stderr);
+ if (node)
+ fprint_span(stderr, node->file, node->start, node->end, "\x1b[31;1m", 2, color);
+
+ longjmp(on_err, 1);
+}
+
+const TypeInfo *type_to_type_info(type_t *t)
+{
+ switch (t->tag) {
+ case IntType:
+ switch (Match(t, IntType)->bits) {
+ case 0: case 64: return &$Int;
+ case 32: return &$Int32;
+ case 16: return &$Int16;
+ case 8: return &$Int8;
+ default: errx(1, "Invalid bits");
+ }
+ default: errx(1, "Unsupported type: %T", t);
+ }
+}
+
+static void *get_address(env_t *env, ast_t *ast)
+{
+ switch (ast->tag) {
+ case Var: {
+ binding_t *b = get_binding(env, Match(ast, Var)->name);
+ if (!b) repl_err(ast, "No such variable");
+ return b->value;
+ }
+ default: errx(1, "Address not implemented for %W", ast);
+ }
+}
+
+static int64_t ast_to_int(env_t *env, ast_t *ast)
+{
+ type_t *t = get_type(env, ast);
+ switch (t->tag) {
+ case IntType: {
+ number_t num;
+ eval(env, ast, &num);
+ switch (Match(t, IntType)->bits) {
+ case 0: case 64: return (int64_t)num.i64;
+ case 32: return (int64_t)num.i32;
+ case 16: return (int64_t)num.i16;
+ case 8: return (int64_t)num.i8;
+ default: errx(1, "Invalid int bits");
+ }
+ }
+ default: repl_err(NULL, "Cannot convert to integer");
+ }
+}
+
+static double ast_to_num(env_t *env, ast_t *ast)
+{
+ type_t *t = get_type(env, ast);
+ switch (t->tag) {
+ case IntType: {
+ number_t num;
+ eval(env, ast, &num);
+ switch (Match(t, IntType)->bits) {
+ case 0: case 64: return (double)num.i64;
+ case 32: return (double)num.i32;
+ case 16: return (double)num.i16;
+ case 8: return (double)num.i8;
+ default: errx(1, "Invalid int bits");
+ }
+ }
+ case NumType: {
+ number_t num;
+ eval(env, ast, &num);
+ return Match(t, NumType)->bits == 32 ? (double)num.n32 : (double)num.n64;
+ }
+ default: repl_err(NULL, "Cannot convert to number");
+ }
+}
+
+static CORD obj_to_text(type_t *t, const void *obj, bool use_color)
+{
+#define C(code, fmt) (use_color ? "\x1b[" code fmt "\x1b[m" : fmt)
+ switch (t->tag) {
+ case MemoryType: return "<Memory>";
+ case BoolType: return *(bool*)obj ? C("35", "yes") : C("35", "no");
+ case IntType: {
+ switch (Match(t, IntType)->bits) {
+ case 0: case 64: return CORD_asprintf(C("35", "%ld"), *(int64_t*)obj);
+ case 32: return CORD_asprintf(C("35", "%d"), *(int32_t*)obj);
+ case 16: return CORD_asprintf(C("35", "%d"), *(int16_t*)obj);
+ case 8: return CORD_asprintf(C("35", "%d"), *(int8_t*)obj);
+ default: errx(1, "Invalid bits: %ld", Match(t, IntType)->bits);
+ }
+ }
+ case NumType: {
+ switch (Match(t, NumType)->bits) {
+ case 0: case 64: return CORD_asprintf(C("35", "%g"), *(double*)obj);
+ case 32: return CORD_asprintf(C("35", "%g"), *(float*)obj);
+ default: errx(1, "Invalid bits: %ld", Match(t, NumType)->bits);
+ }
+ }
+ case TextType: return Text$quoted(*(CORD*)obj, use_color);
+ case ArrayType: {
+ type_t *item_t = Match(t, ArrayType)->item_type;
+ CORD ret = "[";
+ const array_t *arr = obj;
+ for (int64_t i = 0; i < arr->length; i++) {
+ if (i > 0) ret = CORD_cat(ret, ", ");
+ ret = CORD_cat(ret, obj_to_text(item_t, arr->data + i*arr->stride, use_color));
+ }
+ return CORD_cat(ret, "]");
+ }
+ case TableType: {
+ type_t *key_t = Match(t, TableType)->key_type;
+ type_t *value_t = Match(t, TableType)->value_type;
+ CORD ret = "{";
+ const table_t *table = obj;
+ size_t value_offset = type_size(key_t);
+ if (type_align(value_t) > 1 && value_offset % type_align(value_t))
+ value_offset += type_align(value_t) - (value_offset % type_align(value_t)); // padding
+ for (int64_t i = 0; i < table->entries.length; i++) {
+ if (i > 0) ret = CORD_cat(ret, ", ");
+ ret = CORD_all(ret, obj_to_text(key_t, table->entries.data + i*table->entries.stride, use_color), "=>",
+ obj_to_text(value_t, table->entries.data + i*table->entries.stride + value_offset, use_color));
+ }
+ if (table->fallback)
+ ret = CORD_all(ret, "; ", obj_to_text(t, table->fallback, use_color));
+ if (table->default_value)
+ ret = CORD_all(ret, "; ", obj_to_text(value_t, table->default_value, use_color));
+ return CORD_cat(ret, "}");
+ }
+ case PointerType: {
+ const void *p = *(const void**)obj;
+ auto ptr = Match(t, PointerType);
+ if (!p) return CORD_cat("!", type_to_cord(ptr->pointed));
+ CORD sigil = ptr->is_stack ? "&" : (ptr->is_optional ? "?" : "@");
+ if (ptr->is_readonly) sigil = CORD_cat(sigil, "(readonly)");
+ return CORD_all(sigil, obj_to_text(ptr->pointed, p, use_color));
+ }
+ case StructType: {
+ errx(1, "Struct strings not implemented yet");
+ }
+ case EnumType: {
+ errx(1, "Enum strings not implemented yet");
+ }
+ default: return type_to_cord(t);
+ }
+}
+
+void run(env_t *env, ast_t *ast)
+{
+ switch (ast->tag) {
+ case Declare: {
+ auto decl = Match(ast, Declare);
+ const char *name = Match(ast, Var)->name;
+ type_t *type = get_type(env, decl->value);
+ binding_t *binding = new(binding_t, .type=type, .value=GC_MALLOC(type_size(type)));
+ eval(env, decl->value, binding->value);
+ set_binding(env, name, binding);
+ break;
+ }
+ case Assign: {
+ auto assign = Match(ast, Assign);
+ int64_t n = 0;
+ for (ast_list_t *t = assign->targets; t; t = t->next)
+ ++n;
+ for (ast_list_t *val = assign->values, *target = assign->targets; val && target; val = val->next, target = target->next) {
+ type_t *t_target = get_type(env, target->ast);
+ type_t *t_val = get_type(env, val->ast);
+ if (!type_eq(t_val, t_target))
+ repl_err(target->ast, "This value has type %T but I expected a %T", t_val, t_target);
+
+ if (!can_be_mutated(env, target->ast)) {
+ if (target->ast->tag == Index || target->ast->tag == FieldAccess) {
+ ast_t *subject = target->ast->tag == Index ? Match(target->ast, Index)->indexed : Match(target->ast, FieldAccess)->fielded;
+ repl_err(subject, "This is an immutable value, you can't assign to it");
+ } else {
+ repl_err(target->ast, "This is a value of type %T and can't be assigned to", get_type(env, target->ast));
+ }
+ }
+ }
+ for (ast_list_t *val = assign->values, *target = assign->targets; val && target; val = val->next, target = target->next) {
+ switch (target->ast->tag) {
+ case Var: {
+ void *dest = get_address(env, target->ast);
+ eval(env, val->ast, dest);
+ break;
+ }
+ // case Index: {
+ // auto index = Match(target->ast, Index);
+ // type_t *obj_t = get_type(env, index->indexed);
+ // TypeInfo *table_info = type_to_type_info(t);
+ // }
+ default: errx(1, "Assignment not implemented: %W", target->ast);
+ }
+ }
+ break;
+ }
+ case DocTest: {
+ auto doctest = Match(ast, DocTest);
+ type_t *t = get_type(env, doctest->expr);
+ size_t size = t ? type_size(t) : 0;
+ if (size == 0) {
+ run(env, doctest->expr);
+ } else {
+ void *value = GC_MALLOC(size);
+ eval(env, doctest->expr, value);
+ CORD c = obj_to_text(t, value, true);
+ printf("= %s \x1b[2m: %T\n", CORD_to_const_char_star(c), t);
+ fflush(stdout);
+ }
+ break;
+ }
+ case Block: {
+ auto block = Match(ast, Block);
+ for (ast_list_t *stmt = block->statements; stmt; stmt = stmt->next) {
+ run(env, stmt->ast);
+ }
+ break;
+ }
+ // UpdateAssign,
+ // FunctionDef,
+ // Block,
+ // For, While, If, When,
+ // Skip, Stop, Pass,
+ // Return,
+ // Extern,
+ // StructDef, EnumDef, LangDef,
+ // DocTest,
+ // Use,
+ // LinkerDirective,
+ // InlineCCode,
+ default: {
+ eval(env, ast, NULL);
+ break;
+ }
+ }
+}
+
+void eval(env_t *env, ast_t *ast, void *dest)
+{
+ type_t *t = get_type(env, ast);
+ size_t size = type_size(t);
+ switch (ast->tag) {
+ case Nil:
+ if (dest) *(void**)dest = 0;
+ break;
+ case Bool:
+ if (dest) *(bool*)dest = Match(ast, Bool)->b;
+ break;
+ case Var: {
+ if (!dest) return;
+ binding_t *b = get_binding(env, Match(ast, Var)->name);
+ if (!b)
+ repl_err(ast, "No such variable: %s", Match(ast, Var)->name);
+ memcpy(dest, b->value, size);
+ break;
+ }
+ case Int: {
+ if (!dest) return;
+ switch (Match(ast, Int)->bits) {
+ case 0: case 64: *(int64_t*)dest = Match(ast, Int)->i; break;
+ case 32: *(int32_t*)dest = Match(ast, Int)->i; break;
+ case 16: *(int16_t*)dest = Match(ast, Int)->i; break;
+ case 8: *(int8_t*)dest = Match(ast, Int)->i; break;
+ default: errx(1, "Invalid int bits: %ld", Match(ast, Int)->bits);
+ }
+ break;
+ }
+ case Num: {
+ if (!dest) return;
+ switch (Match(ast, Num)->bits) {
+ case 0: case 64: *(double*)dest = Match(ast, Num)->n; break;
+ case 32: *(float*)dest = Match(ast, Num)->n; break;
+ default: errx(1, "Invalid num bits: %ld", Match(ast, Num)->bits);
+ }
+ break;
+ }
+ case TextLiteral:
+ if (dest) *(CORD*)dest = Match(ast, TextLiteral)->cord;
+ break;
+ case TextJoin: {
+ CORD ret = CORD_EMPTY;
+ for (ast_list_t *chunk = Match(ast, TextJoin)->children; chunk; chunk = chunk->next) {
+ type_t *chunk_t = get_type(env, chunk->ast);
+ if (chunk_t->tag == TextType) {
+ CORD c;
+ eval(env, chunk->ast, &c);
+ ret = CORD_cat(ret, c);
+ } else {
+ size_t chunk_size = type_size(chunk_t);
+ char buf[chunk_size];
+ eval(env, chunk->ast, buf);
+ ret = CORD_cat(ret, obj_to_text(chunk_t, buf, false));
+ }
+ }
+ if (dest) *(CORD*)dest = ret;
+ break;
+ }
+#define CASE_OP(OP_NAME, C_OP) case BINOP_##OP_NAME: {\
+ if (t->tag == IntType) { \
+ int64_t lhs = ast_to_int(env, binop->lhs); \
+ int64_t rhs = ast_to_int(env, binop->rhs); \
+ switch (Match(t, IntType)->bits) { \
+ case 64: *(int64_t*)dest = lhs C_OP rhs; break; \
+ case 32: *(int32_t*)dest = (int32_t)(lhs C_OP rhs); break; \
+ case 16: *(int16_t*)dest = (int16_t)(lhs C_OP rhs); break; \
+ case 8: *(int8_t*)dest = (int8_t)(lhs C_OP rhs); break; \
+ default: errx(1, "Invalid int bits"); \
+ } \
+ } else if (t->tag == NumType) { \
+ double lhs = ast_to_num(env, binop->lhs); \
+ double rhs = ast_to_num(env, binop->rhs); \
+ if (Match(t, NumType)->bits == 64) \
+ *(double*)dest = (double)(lhs C_OP rhs); \
+ else \
+ *(float*)dest = (float)(lhs C_OP rhs); \
+ } else { \
+ errx(1, "Binary ops are not yet supported for %W", ast); \
+ } \
+ break; \
+}
+ case BinaryOp: {
+ auto binop = Match(ast, BinaryOp);
+ switch (binop->op) {
+ CASE_OP(MULT, *) CASE_OP(DIVIDE, /) CASE_OP(PLUS, +)
+ CASE_OP(MINUS, -) CASE_OP(EQ, ==)
+ CASE_OP(NE, !=) CASE_OP(LT, <) CASE_OP(LE, <=) CASE_OP(GT, >) CASE_OP(GE, >=)
+ default: errx(1, "Binary op not implemented: %W");
+ }
+ break;
+ }
+ default:
+ errx(1, "Eval not implemented for %W", ast);
+ }
+}
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/repl.h b/repl.h
new file mode 100644
index 00000000..10ca0cc7
--- /dev/null
+++ b/repl.h
@@ -0,0 +1,5 @@
+#pragma once
+
+void repl(void);
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/tomo.c b/tomo.c
index 8d95d45a..976c4b6b 100644
--- a/tomo.c
+++ b/tomo.c
@@ -14,6 +14,7 @@
#include "builtins/text.h"
#include "compile.h"
#include "parse.h"
+#include "repl.h"
#include "typecheck.h"
#include "types.h"
@@ -55,9 +56,6 @@ int main(int argc, char *argv[])
}
}
- if (filename == NULL)
- errx(1, "No file provided");
-
// register_printf_modifier(L"p");
if (register_printf_specifier('T', printf_type, printf_pointer_size))
errx(1, "Couldn't set printf specifier");
@@ -70,6 +68,11 @@ int main(int argc, char *argv[])
verbose = (getenv("VERBOSE") && strcmp(getenv("VERBOSE"), "1") == 0);
+ if (filename == NULL) {
+ repl();
+ return 0;
+ }
+
cconfig = getenv("CCONFIG");
if (!cconfig)
cconfig = "-std=c11 -fdollars-in-identifiers -fsanitize=signed-integer-overflow -fno-sanitize-recover -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -D_DEFAULT_SOURCE";