From c7a3e820e026c040b857fd5bc931f18f41ddbc90 Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Sun, 25 Aug 2024 00:55:11 +0100 Subject: [PATCH] Implement basic stdlib and a simple "println" function callable from lisp --- CMakeLists.txt | 2 + example.vli | 3 +- src/arena.cpp | 12 ++++- src/arena.hpp | 1 + src/common.cpp | 35 +++++++++++++- src/common.hpp | 43 +++++++++++++++++ src/compiler.cpp | 24 ++++++++-- src/compiler.hpp | 2 +- src/pod.hpp | 8 ++++ src/stdlib.cpp | 117 +++++++++++++++++++++++++++++++++++++++++++++++ src/stdlib.hpp | 22 +++++++++ src/vli.cpp | 8 ++-- src/vm.cpp | 35 ++++++++++++-- src/vm.hpp | 2 + src/writer.cpp | 12 +++++ src/writer.hpp | 1 + 16 files changed, 310 insertions(+), 17 deletions(-) create mode 100644 src/stdlib.cpp create mode 100644 src/stdlib.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 609e7be..11bbe0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/example.vli b/example.vli index 2dcb876..99175a4 100644 --- a/example.vli +++ b/example.vli @@ -11,4 +11,5 @@ 1 (* n (fact (- n 1))))) -(fact 12) + +(println "12! =" (fact 12)) \ No newline at end of file diff --git a/src/arena.cpp b/src/arena.cpp index d1a5835..838c8d0 100644 --- a/src/arena.cpp +++ b/src/arena.cpp @@ -83,6 +83,8 @@ Result 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 Arena::gc_function(PodFunction* obj) { return nobj; } +Result Arena::gc_stdlib_function(PodStdlibFunction* obj) { + auto nobj = TRY(alloc()); + nobj->header.tag = Tag::StdlibFunction; + nobj->fun_id = obj->fun_id; + + return nobj; +} + Result Arena::gc_module(PodModule* obj) { auto nobj = TRY(alloc()); - 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())); diff --git a/src/arena.hpp b/src/arena.hpp index d9a51f9..9fa736f 100644 --- a/src/arena.hpp +++ b/src/arena.hpp @@ -156,6 +156,7 @@ class Arena { Result gc_dict(PodDict* obj); Result gc_opcode(PodOpcode* obj); Result gc_function(PodFunction* obj); + Result gc_stdlib_function(PodStdlibFunction* obj); Result gc_module(PodModule* obj); Result gc_stack(PodStack* obj); diff --git a/src/common.cpp b/src/common.cpp index 0d3075e..6691eaf 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -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::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 syntax_unwrap(Value& val) { return syntax->get_value(); } -Result Nil::copy_value() const { return Value(Nil()); } +Result Nil::copy_value() const { return Value(Nil(TRY(_value.copy()))); } Result Int64::copy_value() const { return Value(Int64(TRY(_value.copy()))); @@ -162,6 +165,18 @@ Result Function::cmp(const Function& rhs) const { return res; } +Result StdlibFunction::copy_value() const { + return Value(StdlibFunction(TRY(_value.copy()))); +} +Result StdlibFunction::copy() const { + return StdlibFunction(TRY(_value.copy())); +} + +Result StdlibFunction::cmp(const StdlibFunction& rhs) const { + short res = (fun_id() > rhs.fun_id()) - (fun_id() < rhs.fun_id()); + return res; +} + Result Module::copy_value() const { return Value(Module(TRY(_value.copy()))); } @@ -276,6 +291,24 @@ Result Function::closure() const { return Array::create((PodArray*)_value->closure.get()); } +Result StdlibFunction::create(StdlibFunctionId fun_id) { + auto pod = TRY(arena_alloc()); + pod->header.tag = Tag::StdlibFunction; + pod->fun_id = (uint64_t)fun_id; + + return StdlibFunction(TRY(MkGcRoot(pod))); +} + +Result 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::create(const Value& name, const Function& fun, const Dict& globals) { auto pod = TRY(arena_alloc()); diff --git a/src/common.hpp b/src/common.hpp index 8663510..95af76b 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -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 cmp(const Function& rhs) const { return cmp_tag(tag(), Tag::Function); } + virtual Result cmp(const StdlibFunction& rhs) const { + return cmp_tag(tag(), Tag::StdlibFunction); + } virtual Result cmp(const Module& rhs) const { return cmp_tag(tag(), Tag::Module); } @@ -892,6 +897,44 @@ class Function : public Object { GcRoot _value; }; +class StdlibFunction : public Object { + public: + StdlibFunction() {} + StdlibFunction(StdlibFunction&& rhs) : _value(std::move(rhs._value)) {} + StdlibFunction(GcRoot&& 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 cmp(const Object& rhs) const final { + return -TRY(rhs.cmp(*this)); + } + virtual Result cmp(const StdlibFunction& rhs) const final; + + virtual void move(Object* obj) final { + new (obj) StdlibFunction(std::move(_value)); + } + + static Result create(PodStdlibFunction* obj) { + return StdlibFunction(TRY(MkGcRoot(obj))); + } + + static Result create(StdlibFunctionId fun_id); + + Result name() const; + StdlibFunctionId fun_id() const; + + virtual Result copy_value() const final; + Result copy() const; + + private: + GcRoot _value; +}; + class Module : public Object { public: Module() {} diff --git a/src/compiler.cpp b/src/compiler.cpp index 8d77813..adff360 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -1,6 +1,7 @@ #include "compiler.hpp" #include "common.hpp" +#include "stdlib.hpp" struct Context { Context() {} @@ -183,20 +184,22 @@ Result Compiler::compile_expr(Context& context, Value& expr) { case Tag::Pair: return compile_list(context, *expr.to()); case Tag::Int64: - return compile_int64(context, *expr.to()); + return compile_constant(context, expr); case Tag::Bool: return compile_bool(context, *expr.to()); case Tag::Symbol: return compile_symbol(context, *expr.to()); + 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 Compiler::compile_list(Context& context, Pair& expr) { return ERROR(TypeMismatch); } -Result Compiler::compile_int64(Context& context, Int64& value) { +Result 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 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 diff --git a/src/compiler.hpp b/src/compiler.hpp index 25b8866..57110f0 100644 --- a/src/compiler.hpp +++ b/src/compiler.hpp @@ -34,7 +34,7 @@ class Compiler { Result compile_primop(Context& context, Symbol& op, Pair& expr); Result compile_comparison(Context& context, Symbol& op, Pair& expr); - Result compile_int64(Context& context, Int64& value); + Result compile_constant(Context& context, Value& value); Result compile_symbol(Context& context, Symbol& value); Result compile_bool(Context& context, Bool& value); Result compile_if(Context& context, Symbol& op, Pair& expr); diff --git a/src/pod.hpp b/src/pod.hpp index 1c23702..ce4caa8 100644 --- a/src/pod.hpp +++ b/src/pod.hpp @@ -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 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) {}; diff --git a/src/stdlib.cpp b/src/stdlib.cpp new file mode 100644 index 0000000..ce3f9e2 --- /dev/null +++ b/src/stdlib.cpp @@ -0,0 +1,117 @@ +#include "stdlib.hpp" + +#include "common.hpp" +#include "pod.hpp" +#include "writer.hpp" + +typedef Result (*StdlibFunctionIdPtr)(const Array& params); + +struct StdlibFunctionEntry { + const char* name; + StdlibFunctionId fun_id; + StdlibFunctionIdPtr fun_ptr; +}; + +Result stdlib_unknown(const Array& params) { + return ERROR(NotImplemented); +} + +Result 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(); +} + +Result 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()) { + TRY(print_string(*param.to())); + } else { + auto s = TRY(write_one(param)); + TRY(print_string(s)); + } + } + + return Value(TRY(Nil::create())); +} + +Result stdlib_println(const Array& params) { + TRY(stdlib_print(params)); + std::cout << "\n"; + + return Value(TRY(Nil::create())); +} + +Result 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 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 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 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; +} diff --git a/src/stdlib.hpp b/src/stdlib.hpp new file mode 100644 index 0000000..5f30d8a --- /dev/null +++ b/src/stdlib.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "result.hpp" + +enum class StdlibFunctionId : uint64_t { + Unknown, + Print, + PrintLn, + Prn, + Max, +}; + +class Value; +class Symbol; +class Array; + +Result get_stdlib_function_name(StdlibFunctionId fun_id); +Result get_stdlib_function(const Symbol& name); +Result call_stdlib_function(StdlibFunctionId fun_id, + const Array& params); diff --git a/src/vli.cpp b/src/vli.cpp index 19fbe60..adacd1d 100644 --- a/src/vli.cpp +++ b/src/vli.cpp @@ -27,22 +27,22 @@ Result 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(); - 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(); } diff --git a/src/vm.cpp b/src/vm.cpp index 15b20e3..cbb6e5a 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -1,6 +1,7 @@ #include "vm.hpp" #include "common.hpp" +#include "stdlib.hpp" Result VM::getreg(uint64_t idx) { return _stack.get(_base + idx); } @@ -100,11 +101,7 @@ Result VM::vm_less_equal(Opcode& oc) { return Result(); } -Result VM::vm_call(Opcode& oc) { - Value fun_value = TRY(get(oc.arg1().is_const, oc.arg1().arg)); - if (!fun_value.is()) return ERROR(TypeMismatch); - Function& fun = *fun_value.to(); - +Result 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 VM::vm_call(Opcode& oc) { return Result(); } +Result 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(); +} + +Result VM::vm_call(Opcode& oc) { + Value fun_value = TRY(get(oc.arg1().is_const, oc.arg1().arg)); + if (fun_value.is()) { + Function& fun = *fun_value.to(); + return vm_call_lisp(oc, fun); + } + + if (fun_value.is()) { + StdlibFunction& fun = *fun_value.to(); + return vm_call_stdlib(oc, fun); + } + + return ERROR(TypeMismatch); +} + Result VM::vm_ret(Opcode& oc) { if (_callstack.gettop() == 0) { _res = TRY(getreg((uint64_t)oc.arg1().arg)); diff --git a/src/vm.hpp b/src/vm.hpp index d029ee7..b1297f7 100644 --- a/src/vm.hpp +++ b/src/vm.hpp @@ -26,6 +26,8 @@ class VM { Result vm_sub(Opcode& oc); Result vm_div(Opcode& oc); Result vm_call(Opcode& oc); + Result vm_call_lisp(Opcode& oc, Function& fun); + Result vm_call_stdlib(Opcode& oc, StdlibFunction& fun); Result vm_ret(Opcode& oc); Result vm_equal(Opcode& oc); diff --git a/src/writer.cpp b/src/writer.cpp index c43000c..5a58771 100644 --- a/src/writer.cpp +++ b/src/writer.cpp @@ -31,6 +31,8 @@ Result Writer::write_one(const Value& obj) { return write_opcode(*obj.to()); case Tag::Function: return write_function(*obj.to()); + case Tag::StdlibFunction: + return write_stdlib_function(*obj.to()); case Tag::Module: return write_module(*obj.to()); case Tag::Stack: @@ -320,6 +322,16 @@ Result Writer::write_function(const Function& val) { return res; } +Result Writer::write_stdlib_function(const StdlibFunction& val) { + auto name = TRY(val.name()); + String name_str = TRY(write_symbol(name)); + + String res = TRY(String::create("#")); + return res; +} + Result Writer::write_module(const Module& val) { auto name = TRY(val.name()); if (name.is()) { diff --git a/src/writer.hpp b/src/writer.hpp index 3db39fd..c2a6f7f 100644 --- a/src/writer.hpp +++ b/src/writer.hpp @@ -24,6 +24,7 @@ class Writer { Result write_syntax(const Syntax& val); Result write_opcode(const Opcode& val); Result write_function(const Function& val); + Result write_stdlib_function(const StdlibFunction& val); Result write_module(const Module& val); Result write_stack(const Stack& val); };