Implement basic stdlib and a simple "println" function callable from lisp

This commit is contained in:
Konstantin Nazarov 2024-08-25 00:55:11 +01:00
parent 48163e9251
commit c7a3e820e0
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22
16 changed files with 310 additions and 17 deletions

View file

@ -21,6 +21,7 @@ target_sources(vm_lib
src/compiler.cpp
src/opcode.cpp
src/fio.cpp
src/stdlib.cpp
PUBLIC
FILE_SET HEADERS
@ -39,6 +40,7 @@ target_sources(vm_lib
src/compiler.hpp
src/opcode.hpp
src/fio.hpp
src/stdlib.hpp
)
add_executable(vli src/vli.cpp)

View file

@ -11,4 +11,5 @@
1
(* n (fact (- n 1)))))
(fact 12)
(println "12! =" (fact 12))

View file

@ -83,6 +83,8 @@ Result<PodObject*> Arena::gc_pod(PodObject* obj) {
return gc_opcode((PodOpcode*)obj);
case Tag::Function:
return gc_function((PodFunction*)obj);
case Tag::StdlibFunction:
return gc_stdlib_function((PodStdlibFunction*)obj);
case Tag::Module:
return gc_module((PodModule*)obj);
case Tag::Stack:
@ -210,9 +212,17 @@ Result<PodObject*> Arena::gc_function(PodFunction* obj) {
return nobj;
}
Result<PodObject*> Arena::gc_stdlib_function(PodStdlibFunction* obj) {
auto nobj = TRY(alloc<PodStdlibFunction>());
nobj->header.tag = Tag::StdlibFunction;
nobj->fun_id = obj->fun_id;
return nobj;
}
Result<PodObject*> Arena::gc_module(PodModule* obj) {
auto nobj = TRY(alloc<PodModule>());
nobj->header.tag = Tag::Function;
nobj->header.tag = Tag::Module;
nobj->name = TRY(gc_pod(obj->name.get()));
nobj->globals = TRY(gc_pod(obj->globals.get()));
nobj->fun = TRY(gc_pod(obj->fun.get()));

View file

@ -156,6 +156,7 @@ class Arena {
Result<PodObject*> gc_dict(PodDict* obj);
Result<PodObject*> gc_opcode(PodOpcode* obj);
Result<PodObject*> gc_function(PodFunction* obj);
Result<PodObject*> gc_stdlib_function(PodStdlibFunction* obj);
Result<PodObject*> gc_module(PodModule* obj);
Result<PodObject*> gc_stack(PodStack* obj);

View file

@ -3,6 +3,7 @@
#include "arena.hpp"
#include "error.hpp"
#include "pod.hpp"
#include "stdlib.hpp"
#include "utf8.hpp"
#include "writer.hpp"
@ -40,6 +41,8 @@ Result<Value> Value::create(PodObject* obj) {
return Value(TRY(Opcode::create((PodOpcode*)obj)));
case Tag::Function:
return Value(TRY(Function::create((PodFunction*)obj)));
case Tag::StdlibFunction:
return Value(TRY(StdlibFunction::create((PodStdlibFunction*)obj)));
case Tag::Module:
return Value(TRY(Module::create((PodModule*)obj)));
case Tag::Stack:
@ -86,7 +89,7 @@ Result<Value> syntax_unwrap(Value& val) {
return syntax->get_value();
}
Result<Value> Nil::copy_value() const { return Value(Nil()); }
Result<Value> Nil::copy_value() const { return Value(Nil(TRY(_value.copy()))); }
Result<Value> Int64::copy_value() const {
return Value(Int64(TRY(_value.copy())));
@ -162,6 +165,18 @@ Result<short> Function::cmp(const Function& rhs) const {
return res;
}
Result<Value> StdlibFunction::copy_value() const {
return Value(StdlibFunction(TRY(_value.copy())));
}
Result<StdlibFunction> StdlibFunction::copy() const {
return StdlibFunction(TRY(_value.copy()));
}
Result<short> StdlibFunction::cmp(const StdlibFunction& rhs) const {
short res = (fun_id() > rhs.fun_id()) - (fun_id() < rhs.fun_id());
return res;
}
Result<Value> Module::copy_value() const {
return Value(Module(TRY(_value.copy())));
}
@ -276,6 +291,24 @@ Result<Array> Function::closure() const {
return Array::create((PodArray*)_value->closure.get());
}
Result<StdlibFunction> StdlibFunction::create(StdlibFunctionId fun_id) {
auto pod = TRY(arena_alloc<PodStdlibFunction>());
pod->header.tag = Tag::StdlibFunction;
pod->fun_id = (uint64_t)fun_id;
return StdlibFunction(TRY(MkGcRoot(pod)));
}
Result<Symbol> StdlibFunction::name() const {
const char* fun_name =
TRY(get_stdlib_function_name(StdlibFunctionId(_value->fun_id)));
return Symbol::create(fun_name);
}
StdlibFunctionId StdlibFunction::fun_id() const {
return StdlibFunctionId(_value->fun_id);
}
Result<Module> Module::create(const Value& name, const Function& fun,
const Dict& globals) {
auto pod = TRY(arena_alloc<PodModule>());

View file

@ -9,6 +9,7 @@
#include "error.hpp"
#include "opcode.hpp"
#include "pod.hpp"
#include "stdlib.hpp"
#include "utf8.hpp"
// Forward declarations
@ -29,6 +30,7 @@ class Writer;
class Opcode;
class Stack;
class Function;
class StdlibFunction;
class Module;
short cmp_tag(Tag lhs, Tag rhs);
@ -78,6 +80,9 @@ class Object {
virtual Result<short> cmp(const Function& rhs) const {
return cmp_tag(tag(), Tag::Function);
}
virtual Result<short> cmp(const StdlibFunction& rhs) const {
return cmp_tag(tag(), Tag::StdlibFunction);
}
virtual Result<short> cmp(const Module& rhs) const {
return cmp_tag(tag(), Tag::Module);
}
@ -892,6 +897,44 @@ class Function : public Object {
GcRoot<PodFunction> _value;
};
class StdlibFunction : public Object {
public:
StdlibFunction() {}
StdlibFunction(StdlibFunction&& rhs) : _value(std::move(rhs._value)) {}
StdlibFunction(GcRoot<PodStdlibFunction>&& val) : _value(std::move(val)) {}
StdlibFunction& operator=(StdlibFunction&& rhs) {
_value = std::move(rhs._value);
return *this;
}
virtual Tag tag() const final { return Tag::StdlibFunction; }
virtual PodObject* pod() const final { return _value.get(); }
virtual Result<short> cmp(const Object& rhs) const final {
return -TRY(rhs.cmp(*this));
}
virtual Result<short> cmp(const StdlibFunction& rhs) const final;
virtual void move(Object* obj) final {
new (obj) StdlibFunction(std::move(_value));
}
static Result<StdlibFunction> create(PodStdlibFunction* obj) {
return StdlibFunction(TRY(MkGcRoot(obj)));
}
static Result<StdlibFunction> create(StdlibFunctionId fun_id);
Result<Symbol> name() const;
StdlibFunctionId fun_id() const;
virtual Result<Value> copy_value() const final;
Result<StdlibFunction> copy() const;
private:
GcRoot<PodStdlibFunction> _value;
};
class Module : public Object {
public:
Module() {}

View file

@ -1,6 +1,7 @@
#include "compiler.hpp"
#include "common.hpp"
#include "stdlib.hpp"
struct Context {
Context() {}
@ -183,20 +184,22 @@ Result<Expression> Compiler::compile_expr(Context& context, Value& expr) {
case Tag::Pair:
return compile_list(context, *expr.to<Pair>());
case Tag::Int64:
return compile_int64(context, *expr.to<Int64>());
return compile_constant(context, expr);
case Tag::Bool:
return compile_bool(context, *expr.to<Bool>());
case Tag::Symbol:
return compile_symbol(context, *expr.to<Symbol>());
case Tag::String:
return compile_constant(context, expr);
case Tag::Nil:
case Tag::Float:
case Tag::String:
case Tag::Syntax:
case Tag::Array:
case Tag::ByteArray:
case Tag::Dict:
case Tag::Opcode:
case Tag::Function:
case Tag::StdlibFunction:
case Tag::Module:
case Tag::Stack:
return ERROR(TypeMismatch);
@ -606,11 +609,11 @@ Result<Expression> Compiler::compile_list(Context& context, Pair& expr) {
return ERROR(TypeMismatch);
}
Result<Expression> Compiler::compile_int64(Context& context, Int64& value) {
Result<Expression> Compiler::compile_constant(Context& context, Value& value) {
Expression ex = TRY(Expression::create());
uint64_t reg = context.alloc_reg();
int64_t c = TRY(context.add_const(TRY(value.copy_value())));
int64_t c = TRY(context.add_const(value));
TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)reg}, {1, (int64_t)c}));
@ -646,6 +649,19 @@ Result<Expression> Compiler::compile_symbol(Context& context, Symbol& value) {
return std::move(ex);
}
auto maybe_stdlib_fun = get_stdlib_function(value);
if (!maybe_stdlib_fun.has_error()) {
auto stdlib_fun = TRY(StdlibFunction::create(maybe_stdlib_fun.value()));
int64_t c = TRY(context.add_const(TRY(stdlib_fun.copy_value())));
uint64_t reg = context.alloc_reg();
TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)reg}, {1, (int64_t)c}));
ex.reg = reg;
return std::move(ex);
}
// Otherwise treat unknown symbol as a global and try to load it from the
// global scope

View file

@ -34,7 +34,7 @@ class Compiler {
Result<Expression> compile_primop(Context& context, Symbol& op, Pair& expr);
Result<Expression> compile_comparison(Context& context, Symbol& op,
Pair& expr);
Result<Expression> compile_int64(Context& context, Int64& value);
Result<Expression> compile_constant(Context& context, Value& value);
Result<Expression> compile_symbol(Context& context, Symbol& value);
Result<Expression> compile_bool(Context& context, Bool& value);
Result<Expression> compile_if(Context& context, Symbol& op, Pair& expr);

View file

@ -20,6 +20,7 @@ enum class Tag : uint8_t {
Dict,
Opcode,
Function,
StdlibFunction,
Module,
Stack,
};
@ -169,6 +170,13 @@ class PodFunction final : public PodObject {
OffPtr<PodObject> closure;
};
class PodStdlibFunction final : public PodObject {
public:
PodStdlibFunction() : PodObject(Tag::StdlibFunction) {};
uint64_t fun_id;
};
class PodModule final : public PodObject {
public:
PodModule() : PodObject(Tag::Module) {};

117
src/stdlib.cpp Normal file
View file

@ -0,0 +1,117 @@
#include "stdlib.hpp"
#include "common.hpp"
#include "pod.hpp"
#include "writer.hpp"
typedef Result<Value> (*StdlibFunctionIdPtr)(const Array& params);
struct StdlibFunctionEntry {
const char* name;
StdlibFunctionId fun_id;
StdlibFunctionIdPtr fun_ptr;
};
Result<Value> stdlib_unknown(const Array& params) {
return ERROR(NotImplemented);
}
Result<void> print_string(const String& s) {
auto ba = TRY(ByteArray::create(s));
for (uint64_t i = 0; i < ba.size(); i++) {
std::cout << TRY(ba[i]);
}
return Result<void>();
}
Result<Value> stdlib_print(const Array& params) {
for (uint64_t i = 0; i < params.size(); i++) {
Value param = TRY(params.get(i));
if (i != 0) {
std::cout << " ";
}
if (param.is<String>()) {
TRY(print_string(*param.to<String>()));
} else {
auto s = TRY(write_one(param));
TRY(print_string(s));
}
}
return Value(TRY(Nil::create()));
}
Result<Value> stdlib_println(const Array& params) {
TRY(stdlib_print(params));
std::cout << "\n";
return Value(TRY(Nil::create()));
}
Result<Value> stdlib_prn(const Array& params) { return ERROR(NotImplemented); }
#define STDLIB_FUNCTION(name, id) \
[(uint64_t)StdlibFunctionId::id] = {#name, StdlibFunctionId::id, \
stdlib_##name}
static StdlibFunctionEntry function_entries[] = {
STDLIB_FUNCTION(unknown, Unknown),
STDLIB_FUNCTION(print, Print),
STDLIB_FUNCTION(println, PrintLn),
STDLIB_FUNCTION(prn, Prn),
[(uint64_t)StdlibFunctionId::Max] = {0, StdlibFunctionId::Max,
stdlib_unknown},
};
// TODO: this just scans through the array of functions linearly every time.
// It doesn't have much effect at runtime, because such lookup is likely to
// happen on compile-time only. But anyway, this is worth speeding up
// eventually.
StdlibFunctionEntry get_function_entry(const char* name) {
for (uint64_t i = 1; i < (uint64_t)StdlibFunctionId::Max; i++) {
if (strcmp(function_entries[i].name, name) == 0) {
return function_entries[i];
}
}
return function_entries[(uint64_t)StdlibFunctionId::Unknown];
}
Result<StdlibFunctionId> get_stdlib_function(const Symbol& name) {
const uint64_t bufsize = 256;
char buf[bufsize];
if (name.size() + 1 > bufsize) {
return ERROR(KeyError);
}
for (uint64_t i = 0; i < name.size(); i++) {
buf[i] = (char)TRY(name[i]);
}
buf[name.size()] = 0;
StdlibFunctionId fun_id = get_function_entry(buf).fun_id;
if (fun_id == StdlibFunctionId::Unknown) {
return ERROR(KeyError);
}
return fun_id;
}
Result<Value> call_stdlib_function(StdlibFunctionId fun_id,
const Array& params) {
if (fun_id == StdlibFunctionId(0) || fun_id >= StdlibFunctionId::Max) {
return ERROR(KeyError);
}
return function_entries[(uint64_t)fun_id].fun_ptr(params);
}
Result<const char*> get_stdlib_function_name(StdlibFunctionId fun_id) {
if (fun_id == StdlibFunctionId(0) || fun_id >= StdlibFunctionId::Max) {
return ERROR(KeyError);
}
return function_entries[(uint64_t)fun_id].name;
}

22
src/stdlib.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <cstdint>
#include "result.hpp"
enum class StdlibFunctionId : uint64_t {
Unknown,
Print,
PrintLn,
Prn,
Max,
};
class Value;
class Symbol;
class Array;
Result<const char*> get_stdlib_function_name(StdlibFunctionId fun_id);
Result<StdlibFunctionId> get_stdlib_function(const Symbol& name);
Result<Value> call_stdlib_function(StdlibFunctionId fun_id,
const Array& params);

View file

@ -27,22 +27,22 @@ Result<void> run(int argc, const char* argv[]) {
// TRY(String::create("((lambda (f y) (f y)) (lambda (x) (* x x)) 2)"));
auto parsed = TRY(read_multiple(src));
auto code_str_written = TRY(write_multiple(parsed));
// auto code_str_written = TRY(write_multiple(parsed));
TRY(arena_gc());
TRY(debug_print(code_str_written));
// TRY(debug_print(code_str_written));
auto compiled = TRY(compile(parsed));
Module& mod = *compiled.to<Module>();
TRY(debug_print(TRY(mod.globals())));
// TRY(debug_print(TRY(mod.globals())));
auto vm = TRY(VM::create());
auto res = TRY(vm.run(mod));
TRY(debug_print(res));
// TRY(debug_print(res));
return Result<void>();
}

View file

@ -1,6 +1,7 @@
#include "vm.hpp"
#include "common.hpp"
#include "stdlib.hpp"
Result<Value> VM::getreg(uint64_t idx) { return _stack.get(_base + idx); }
@ -100,11 +101,7 @@ Result<void> VM::vm_less_equal(Opcode& oc) {
return Result<void>();
}
Result<void> VM::vm_call(Opcode& oc) {
Value fun_value = TRY(get(oc.arg1().is_const, oc.arg1().arg));
if (!fun_value.is<Function>()) return ERROR(TypeMismatch);
Function& fun = *fun_value.to<Function>();
Result<void> VM::vm_call_lisp(Opcode& oc, Function& fun) {
uint64_t reg_start = (uint64_t)oc.arg1().arg;
uint64_t reg_end = (uint64_t)oc.arg2().arg;
@ -131,6 +128,34 @@ Result<void> VM::vm_call(Opcode& oc) {
return Result<void>();
}
Result<void> VM::vm_call_stdlib(Opcode& oc, StdlibFunction& fun) {
uint64_t reg_start = (uint64_t)oc.arg1().arg;
uint64_t reg_end = (uint64_t)oc.arg2().arg;
auto params = TRY(_stack.slice(reg_start + 1, reg_end));
auto res = TRY(call_stdlib_function(fun.fun_id(), params));
setreg(oc.arg1().arg, res);
_pc++;
return Result<void>();
}
Result<void> VM::vm_call(Opcode& oc) {
Value fun_value = TRY(get(oc.arg1().is_const, oc.arg1().arg));
if (fun_value.is<Function>()) {
Function& fun = *fun_value.to<Function>();
return vm_call_lisp(oc, fun);
}
if (fun_value.is<StdlibFunction>()) {
StdlibFunction& fun = *fun_value.to<StdlibFunction>();
return vm_call_stdlib(oc, fun);
}
return ERROR(TypeMismatch);
}
Result<void> VM::vm_ret(Opcode& oc) {
if (_callstack.gettop() == 0) {
_res = TRY(getreg((uint64_t)oc.arg1().arg));

View file

@ -26,6 +26,8 @@ class VM {
Result<void> vm_sub(Opcode& oc);
Result<void> vm_div(Opcode& oc);
Result<void> vm_call(Opcode& oc);
Result<void> vm_call_lisp(Opcode& oc, Function& fun);
Result<void> vm_call_stdlib(Opcode& oc, StdlibFunction& fun);
Result<void> vm_ret(Opcode& oc);
Result<void> vm_equal(Opcode& oc);

View file

@ -31,6 +31,8 @@ Result<String> Writer::write_one(const Value& obj) {
return write_opcode(*obj.to<Opcode>());
case Tag::Function:
return write_function(*obj.to<Function>());
case Tag::StdlibFunction:
return write_stdlib_function(*obj.to<StdlibFunction>());
case Tag::Module:
return write_module(*obj.to<Module>());
case Tag::Stack:
@ -320,6 +322,16 @@ Result<String> Writer::write_function(const Function& val) {
return res;
}
Result<String> Writer::write_stdlib_function(const StdlibFunction& val) {
auto name = TRY(val.name());
String name_str = TRY(write_symbol(name));
String res = TRY(String::create("#<function "));
res = TRY(res.concat(name_str));
res = TRY(res.concat(">"));
return res;
}
Result<String> Writer::write_module(const Module& val) {
auto name = TRY(val.name());
if (name.is<Nil>()) {

View file

@ -24,6 +24,7 @@ class Writer {
Result<String> write_syntax(const Syntax& val);
Result<String> write_opcode(const Opcode& val);
Result<String> write_function(const Function& val);
Result<String> write_stdlib_function(const StdlibFunction& val);
Result<String> write_module(const Module& val);
Result<String> write_stack(const Stack& val);
};