diff --git a/doc/grammar.bnf b/doc/grammar.bnf index 448143e..766e2ee 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -1,3 +1,7 @@ MODULE ::= EXPR* EXPR ::= -int +| int +| ident +| CALL + +CALL ::= opar ident EXPR* cpar diff --git a/libstd/common.hpp b/libstd/common.hpp new file mode 100644 index 0000000..396b0a9 --- /dev/null +++ b/libstd/common.hpp @@ -0,0 +1,9 @@ +#ifndef COMMON_HPP +#define COMMON_HPP + +#include "../src/Zarn.hpp" +using namespace zn; + +#define STDARGS std::vector> + +#endif diff --git a/libstd/fun.cpp b/libstd/fun.cpp new file mode 100644 index 0000000..81c31ed --- /dev/null +++ b/libstd/fun.cpp @@ -0,0 +1,36 @@ +#include "fun.hpp" + +std::shared_ptr println(STDARGS args) +{ + std::string sep; + + for (auto const& arg: args) + { + std::cout << sep << arg->string(); + sep = " "; + } + + std::cout << std::endl; + + return std::make_shared(TYPE_NIL, args[0]->loc()); +} + +std::shared_ptr assert_eq(STDARGS args) +{ + bool equals = args[0]->equals(*args[1]); + auto loc = args[0]->loc(); + + if (!equals) + { + std::cerr << loc.file_path().string() << ":" << loc.line(); + std::cerr << " ASSERTION FAILED: "; + + std::cerr << "expected '" << args[0]->string() << "', " + << "got '" << args[1]->string() << "'." + << std::endl; + + exit(-1); + } + + return std::make_shared(TYPE_NIL, loc, 0); +} diff --git a/libstd/fun.hpp b/libstd/fun.hpp new file mode 100644 index 0000000..dc82c87 --- /dev/null +++ b/libstd/fun.hpp @@ -0,0 +1,9 @@ +#ifndef FUN_HPP +#define FUN_HPP + +#include "common.hpp" + +std::shared_ptr println(STDARGS args); +std::shared_ptr assert_eq(STDARGS args); + +#endif diff --git a/libstd/std.cpp b/libstd/std.cpp new file mode 100644 index 0000000..b6297ba --- /dev/null +++ b/libstd/std.cpp @@ -0,0 +1,19 @@ +#include "common.hpp" +#include "fun.hpp" + +extern "C" void lib(Zarn& zarn) +{ + zarn.register_function("println", + std::make_shared() + ->param_any() + ->param_variadic() + ->ret(TYPE_NIL), + println); + + zarn.register_function("assert=", + std::make_shared() + ->param_any() + ->param_any() + ->ret(TYPE_NIL), + assert_eq); +} diff --git a/meson.build b/meson.build index 3124f82..cf9c799 100644 --- a/meson.build +++ b/meson.build @@ -7,8 +7,20 @@ project('zarn', 'cpp_std=c++17' ]) +# CONFIGURATIONS +# ============== extra_libdir = get_option('prefix') / get_option('libdir') / 'zarn' +conf = configuration_data() +conf.set('version', meson.project_version()) +conf.set('libdir', extra_libdir) + +configure_file(input: 'src/config.in.hpp', + output: 'config.hpp', + configuration: conf) + +# ZARN_LIB +# ======== zarn_lib = shared_library('zarn', sources: [ 'src/Module.cpp', @@ -17,15 +29,35 @@ zarn_lib = shared_library('zarn', 'src/Logger.cpp', 'src/Lexer.cpp', 'src/Parser.cpp', + 'src/StaticPass.cpp', 'src/Compiler.cpp', 'src/Program.cpp', 'src/VM.cpp', 'src/Constant.cpp', + 'src/SymTable.cpp', + 'src/NativeFunction.cpp', + 'src/Prototype.cpp', + 'src/Zarn.cpp', ], install: true) zarn_dep = declare_dependency(link_with: zarn_lib) +# STANDARD LIBRARY +# ================ +shared_library('zarn-std', + sources: [ + 'libstd/std.cpp', + 'libstd/fun.cpp', + ], + dependencies: [ + zarn_dep + ], + install_dir: extra_libdir, + install: true) + +# COMPILER +# ======== executable('zarn', sources: [ 'src/main.cpp' @@ -35,6 +67,8 @@ executable('zarn', ], install: true) +# TESTS +# ===== executable('zarn-tests', sources: [ 'tests/main.cpp', diff --git a/src/Compiler.cpp b/src/Compiler.cpp index 535fadc..0eca30f 100644 --- a/src/Compiler.cpp +++ b/src/Compiler.cpp @@ -4,8 +4,9 @@ namespace zn { - /*explicit*/ Compiler::Compiler(Logger& logger) + /*explicit*/ Compiler::Compiler(Logger& logger, SymTable& sym) : m_logger { logger } + , m_sym { sym } { } @@ -17,6 +18,8 @@ namespace zn { switch (node.type()) { + // MODULES + // ======= case NODE_MODULE: { for (size_t i=0; iprototype) + { + auto ref = std::make_shared(TYPE_REF, + node.loc(), + (int) sym->addr); + size_t val = program.add_constant(ref); + program.append(OPCODE_LOAD_CONST, val); + } + else + { + program.append(OPCODE_LOAD_LOCAL, sym->addr); + } + + } break; + case NODE_INT: { size_t addr = program .add_constant(std::make_shared - (TYPE_INT, std::stoi(node.repr()))); + (TYPE_INT, node.loc(), + std::stoi(node.repr()))); program.append(OPCODE_LOAD_CONST, addr); } break; diff --git a/src/Compiler.hpp b/src/Compiler.hpp index b6fe70b..c6a1d3a 100644 --- a/src/Compiler.hpp +++ b/src/Compiler.hpp @@ -5,6 +5,7 @@ #include "Logger.hpp" #include "Node.hpp" #include "Program.hpp" +#include "SymTable.hpp" namespace zn { @@ -13,13 +14,14 @@ namespace zn class Compiler { public: - explicit Compiler(Logger& logger); + explicit Compiler(Logger& logger, SymTable& sym); virtual ~Compiler(); void compile(Node const& node, Program& program); private: Logger& m_logger; + SymTable& m_sym; }; } diff --git a/src/Constant.cpp b/src/Constant.cpp index 2d610ef..f91a516 100644 --- a/src/Constant.cpp +++ b/src/Constant.cpp @@ -2,8 +2,11 @@ namespace zn { - /*explicit*/ Constant::Constant(Type type, value_t value) + /*explicit*/ Constant::Constant(Type type, + Loc const& loc, + constant_t value) : m_type { type } + , m_loc { loc } , m_value { value } { } @@ -24,15 +27,16 @@ namespace zn std::string Constant::string() const { - if (auto val = std::get_if(&(*m_value)); - val) + switch (m_type) { - return std::to_string(*val); + case TYPE_NIL: return ""; + case TYPE_INT: return std::to_string(std::get(*m_value)); + default: { + std::cerr << "cannot stringify " + << (TypeStr[m_type] + strlen("TYPE_")) + << std::endl; + abort(); + } break; } - - std::cerr << "cannot stringify " - << (TypeStr[m_type] + strlen("TYPE_")) - << std::endl; - abort(); } } diff --git a/src/Constant.hpp b/src/Constant.hpp index 07e6037..45d7db2 100644 --- a/src/Constant.hpp +++ b/src/Constant.hpp @@ -2,24 +2,23 @@ #define zn_CONSTANT_HPP #include "common.hpp" - -#define TYPES(G) \ - G(TYPE_NIL), \ - G(TYPE_INT) +#include "types.hpp" +#include "Loc.hpp" namespace zn { - ZN_MK_ENUM(Type, TYPES); - - using value_t = std::optional>; + using constant_t = std::optional>; class Constant { public: - explicit Constant(Type type, value_t value); + explicit Constant(Type type, + Loc const& loc, + constant_t value=std::nullopt); virtual ~Constant(); - value_t value() const { return m_value; } + Loc loc() const { return m_loc; } + constant_t value() const { return m_value; } Type type() const { return m_type; } bool equals(Constant const& rhs) const; @@ -27,7 +26,8 @@ namespace zn private: Type m_type; - value_t m_value; + Loc m_loc; + constant_t m_value; }; } diff --git a/src/Lexer.cpp b/src/Lexer.cpp index 51557c1..e69a205 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -1,4 +1,6 @@ #include "Lexer.hpp" +#include "src/Node.hpp" +#include namespace zn { @@ -6,7 +8,11 @@ namespace zn : m_logger { logger } , m_loc { loc } { + add_text("(", NODE_OPAR); + add_text(")", NODE_CPAR); + m_scanners.push_back(std::bind(&Lexer::scan_int, this)); + m_scanners.push_back(std::bind(&Lexer::scan_ident, this)); } /*virtual*/ Lexer::~Lexer() @@ -87,6 +93,32 @@ namespace zn return result; } + bool Lexer::is_sep(size_t index) const + { + if (index >= m_source.size() + || std::isspace(m_source[index])) + { + return true; + } + + return m_separators.find(m_source[index]) != std::string::npos; + } + + void Lexer::add_text(std::string const& text, + NodeType type, + bool has_value) + { + m_scanners.push_back(std::bind(&Lexer::scan_text, + this, + text, + type, + has_value)); + if (text.size() == 1) + { + m_separators += text; + } + } + void Lexer::skip_spaces() { while (m_cursor < m_source.size() @@ -131,4 +163,52 @@ namespace zn repr }; } + + std::optional Lexer::scan_text(std::string const& text, + NodeType type, + bool has_value) + { + if (m_cursor + text.size() > m_source.size()) + { + return std::nullopt; + } + + for (size_t i=0; i Lexer::scan_ident() + { + size_t cursor = m_cursor; + std::string repr; + + while (cursor < m_source.size() + && !is_sep(cursor)) + { + repr += m_source[cursor]; + cursor++; + } + + if (repr.empty()) + { + return std::nullopt; + } + + return ScanInfo { + cursor, + NODE_IDENT, + repr + }; + } } diff --git a/src/Lexer.hpp b/src/Lexer.hpp index 5214c4b..48740c3 100644 --- a/src/Lexer.hpp +++ b/src/Lexer.hpp @@ -35,9 +35,20 @@ namespace zn size_t m_cursor; std::vector m_scanners; Loc m_loc; + std::string m_separators; + + bool is_sep(size_t index) const; + + void add_text(std::string const& text, + NodeType type, + bool has_value=false); void skip_spaces(); std::optional scan_int(); + std::optional scan_text(std::string const& text, + NodeType type, + bool has_value); + std::optional scan_ident(); }; } diff --git a/src/Module.cpp b/src/Module.cpp index a949b05..1899164 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -3,7 +3,10 @@ #include "Parser.hpp" #include "Program.hpp" #include "Compiler.hpp" +#include "StaticPass.hpp" #include "VM.hpp" +#include "NativeFunction.hpp" +#include "config.hpp" namespace zn { @@ -43,7 +46,12 @@ namespace zn auto ast = parser.parse(lexer.all()); - Compiler compiler { m_logger }; + m_zarn.load_std_library(); + + StaticPass static_pass { m_logger, m_sym }; + static_pass.execute(*ast); + + Compiler compiler { m_logger, m_sym }; compiler.compile(*ast, m_program); m_vm.execute(m_program); diff --git a/src/Module.hpp b/src/Module.hpp index e2d19a0..33ab63a 100644 --- a/src/Module.hpp +++ b/src/Module.hpp @@ -5,6 +5,8 @@ #include "Logger.hpp" #include "VM.hpp" #include "Program.hpp" +#include "SymTable.hpp" +#include "Zarn.hpp" namespace zn { @@ -23,7 +25,9 @@ namespace zn Logger& m_logger; std::string m_source; Program m_program; - VM m_vm; + VM m_vm { m_program }; + SymTable m_sym; + Zarn m_zarn {m_sym, m_vm}; }; } diff --git a/src/NativeFunction.cpp b/src/NativeFunction.cpp new file mode 100644 index 0000000..08cfba4 --- /dev/null +++ b/src/NativeFunction.cpp @@ -0,0 +1,24 @@ +#include "NativeFunction.hpp" + +namespace zn +{ + /*explicit*/ NativeFunction::NativeFunction(std::shared_ptr + prototype, + native_fn_t body) + : m_prototype { prototype } + , m_body { body } + { + } + + /*virtual*/ NativeFunction::~NativeFunction() + { + } + + std::shared_ptr + NativeFunction::call(std::vector> const& args) + { + assert(m_body); + return m_body(args); + } + +} diff --git a/src/NativeFunction.hpp b/src/NativeFunction.hpp new file mode 100644 index 0000000..e7a719e --- /dev/null +++ b/src/NativeFunction.hpp @@ -0,0 +1,30 @@ +#ifndef zn_NATIVEFUNCTION_HPP +#define zn_NATIVEFUNCTION_HPP + +#include "common.hpp" +#include "Prototype.hpp" + +namespace zn +{ + class Constant; + using const_t = std::shared_ptr; + using native_fn_t = std::function)>; + class NativeFunction + { + public: + explicit NativeFunction(std::shared_ptr prototype, + native_fn_t body); + virtual ~NativeFunction(); + + std::shared_ptr prototype() const { return m_prototype; } + + std::shared_ptr + call(std::vector> const& args); + + private: + std::shared_ptr m_prototype; + native_fn_t m_body; + }; +} + +#endif diff --git a/src/Node.hpp b/src/Node.hpp index 8f45838..3a90164 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -4,9 +4,13 @@ #include "common.hpp" #include "Loc.hpp" -#define NODE_TYPES(G) \ - G(NODE_MODULE), \ - G(NODE_INT) +#define NODE_TYPES(G) \ + G(NODE_MODULE), \ + G(NODE_INT), \ + G(NODE_OPAR), \ + G(NODE_CPAR), \ + G(NODE_IDENT), \ + G(NODE_CALL), namespace zn { diff --git a/src/Parser.cpp b/src/Parser.cpp index 4223373..713639b 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -77,6 +77,11 @@ namespace zn std::shared_ptr Parser::parse_module() { + if (m_tokens.empty()) + { + return std::make_shared(NODE_MODULE, "", Loc {""}); + } + auto node = mk_node(NODE_MODULE); while (m_cursor < m_tokens.size()) @@ -89,11 +94,17 @@ namespace zn std::shared_ptr Parser::parse_expr() { - if (type_is(NODE_INT)) + if (type_is(NODE_INT) + || type_is(NODE_IDENT)) { return consume(); } + if (type_is(NODE_OPAR)) + { + return parse_call(); + } + std::stringstream ss; ss << "unknown expression '" << m_tokens[m_cursor]->string() @@ -104,4 +115,20 @@ namespace zn ss.str()); abort(); } + + std::shared_ptr Parser::parse_call() + { + consume(NODE_OPAR); + auto node = mk_node(NODE_CALL); + node->add_child(consume(NODE_IDENT)); + + while (!type_is(NODE_CPAR)) + { + node->add_child(parse_expr()); + } + + consume(NODE_CPAR); + + return node; + } } diff --git a/src/Parser.hpp b/src/Parser.hpp index bce17ed..11bb5e7 100644 --- a/src/Parser.hpp +++ b/src/Parser.hpp @@ -31,6 +31,7 @@ namespace zn std::shared_ptr parse_module(); std::shared_ptr parse_expr(); + std::shared_ptr parse_call(); }; } diff --git a/src/Program.hpp b/src/Program.hpp index 4f86523..8fb9fe6 100644 --- a/src/Program.hpp +++ b/src/Program.hpp @@ -6,9 +6,14 @@ #define NO_PARAM (-1) -#define OPCODES(G) \ +#define OPCODES(G) \ G(OPCODE_LOAD_CONST), \ - G(OPCODE_POP) + G(OPCODE_LOAD_GLOBAL), \ + G(OPCODE_STORE_GLOBAL), \ + G(OPCODE_LOAD_LOCAL), \ + G(OPCODE_STORE_LOCAL), \ + G(OPCODE_POP), \ + G(OPCODE_CALL_NATIVE), \ namespace zn { diff --git a/src/Prototype.cpp b/src/Prototype.cpp new file mode 100644 index 0000000..a91e366 --- /dev/null +++ b/src/Prototype.cpp @@ -0,0 +1,105 @@ +#include "Prototype.hpp" + +namespace zn +{ + /*explicit*/ Prototype::Prototype() + { + } + + /*virtual*/ Prototype::~Prototype() + { + } + + TypeSlot Prototype::get_param(size_t index) const + { + assert(index < m_params.size()); + return m_params[index]; + } + + std::shared_ptr Prototype::param(TypeSlot param) + { + m_params.push_back(param); + return shared_from_this(); + } + + std::shared_ptr Prototype::param(Type param) + { + TypeSlot slot; + slot.type = param; + m_params.push_back(slot); + return shared_from_this(); + } + + std::shared_ptr Prototype::param_any() + { + TypeSlot slot; + slot.type = std::nullopt; + slot.tag = TAG_ANY; + m_params.push_back(slot); + return shared_from_this(); + } + + std::shared_ptr Prototype::param_variadic() + { + TypeSlot slot; + slot.type = std::nullopt; + slot.tag = TAG_VARIADIC; + m_params.push_back(slot); + return shared_from_this(); + } + + std::shared_ptr Prototype::ret(TypeSlot ret) + { + m_return = ret; + return shared_from_this(); + } + + std::shared_ptr Prototype::ret(Type ret) + { + TypeSlot slot; + slot.type = ret; + m_return = slot; + return shared_from_this(); + } + + std::shared_ptr Prototype::ret_any() + { + TypeSlot slot; + slot.type = std::nullopt; + slot.tag = TAG_ANY; + m_return = slot; + return shared_from_this(); + } + + std::string Prototype::string() const + { + std::stringstream ss; + + std::string sep; + + for (auto param: m_params) + { + if (param.tag == TAG_ANY) + { + ss << sep << "ANY"; + } + else + { + ss << sep << (TypeStr[*param.type] + strlen("TYPE_")); + } + + sep = ", "; + } + + if (m_return.tag == TAG_ANY) + { + ss << " -> " << "ANY"; + } + else + { + ss << " -> " << (TypeStr[*m_return.type] + strlen("TYPE_")); + } + + return ss.str(); + } +} diff --git a/src/Prototype.hpp b/src/Prototype.hpp new file mode 100644 index 0000000..1f7dc94 --- /dev/null +++ b/src/Prototype.hpp @@ -0,0 +1,50 @@ +#ifndef zn_PROTOTYPE_HPP +#define zn_PROTOTYPE_HPP + +#include "common.hpp" +#include "types.hpp" +#include + +#define TYPE_TAGS(G) \ + G(TAG_NONE), \ + G(TAG_ANY), \ + G(TAG_VARIADIC) + +namespace zn +{ + ZN_MK_ENUM(Tag, TYPE_TAGS); + + struct TypeSlot { + std::optional type = TYPE_NIL; + Tag tag = TAG_NONE; + }; + + class Prototype: public std::enable_shared_from_this + { + public: + explicit Prototype(); + virtual ~Prototype(); + + TypeSlot get_ret() const { return m_return; } + size_t get_param_count() const { return m_params.size(); } + + TypeSlot get_param(size_t index) const; + + std::shared_ptr param(TypeSlot param); + std::shared_ptr param(Type param); + std::shared_ptr param_any(); + std::shared_ptr param_variadic(); + + std::shared_ptr ret(TypeSlot ret); + std::shared_ptr ret(Type ret); + std::shared_ptr ret_any(); + + std::string string() const; + + private: + std::vector m_params; + TypeSlot m_return; + }; +} + +#endif diff --git a/src/StaticPass.cpp b/src/StaticPass.cpp new file mode 100644 index 0000000..8f713f8 --- /dev/null +++ b/src/StaticPass.cpp @@ -0,0 +1,114 @@ +#include "StaticPass.hpp" + +namespace zn +{ + /*explicit*/ StaticPass::StaticPass(Logger& logger, SymTable& sym) + : m_logger { logger } + , m_sym { sym } + { + } + + /*virtual*/ StaticPass::~StaticPass() + { + } + + TypeSlot StaticPass::execute(Node const& node) + { + switch (node.type()) + { + case NODE_MODULE: { + for (size_t i=0; irepr(); + auto sym = m_sym.find(ident); + + if (sym == std::nullopt) + { + m_logger.log(LOG_ERROR, + node.loc(), + "cannot call unknown function '" + + ident + + "'"); + } + + auto proto = sym->prototype; + assert(proto); + + if (proto->get_param_count() != node.size() - 1 + && proto->get_param(proto->get_param_count() - 1).tag + != TAG_VARIADIC) + { + std::stringstream ss; + ss << "type mismatch, expected '" + << proto->get_param_count() + << "' parameters, got '" + << (node.size() - 1) + << "'"; + + m_logger.log(LOG_ERROR, node.loc(), ss.str()); + } + + size_t j = 0; + + for (size_t i=1; iget_param(j).tag + || ty.type != proto->get_param(j).type) + && ty.tag != TAG_ANY + && proto->get_param(j).tag != TAG_ANY) + { + type_mismatch(node.loc(), proto->get_param(j), ty); + } + + if (j + 1 < proto->get_param_count() + && proto->get_param(j + 1).tag != TAG_VARIADIC) + { + j++; + } + } + + return proto->get_ret(); + } break; + + case NODE_INT: { + return TypeSlot {TYPE_INT}; + } break; + + default: { + std::cerr << "cannot static check node '" + << NodeTypeStr[node.type()] + << "'" + << std::endl; + abort(); + } break; + } + } + + void StaticPass::type_mismatch(Loc const& loc, + TypeSlot lhs, + TypeSlot rhs) + { + auto to_s = [](TypeSlot s){ + if (s.tag == TAG_ANY) { return "ANY"; } + return TypeStr[*s.type] + strlen("TYPE_"); + }; + + std::stringstream ss; + ss << "type mismatch, expected '" + << to_s(lhs) + << "', got '" + << to_s(rhs) + << "'"; + + m_logger.log(LOG_ERROR, loc, ss.str()); + } +} diff --git a/src/StaticPass.hpp b/src/StaticPass.hpp new file mode 100644 index 0000000..1472b19 --- /dev/null +++ b/src/StaticPass.hpp @@ -0,0 +1,30 @@ +#ifndef zn_STATICPASS_HPP +#define zn_STATICPASS_HPP + +#include "common.hpp" +#include "Node.hpp" +#include "Logger.hpp" +#include "SymTable.hpp" + +namespace zn +{ + ZN_ERROR(static_error); + + class StaticPass + { + public: + explicit StaticPass(Logger& logger, SymTable& sym); + virtual ~StaticPass(); + + TypeSlot execute(Node const& node); + private: + Logger& m_logger; + SymTable& m_sym; + + void type_mismatch(Loc const& loc, + TypeSlot lhs, + TypeSlot rhs); + }; +} + +#endif diff --git a/src/SymTable.cpp b/src/SymTable.cpp new file mode 100644 index 0000000..170bad0 --- /dev/null +++ b/src/SymTable.cpp @@ -0,0 +1,37 @@ +#include "SymTable.hpp" + +namespace zn +{ + /*explicit*/ SymTable::SymTable() + { + } + + /*virtual*/ SymTable::~SymTable() + { + } + + void SymTable::declare(std::string const& name, size_t addr) + { + m_syms.push_back(Sym {name, addr}); + } + + void SymTable::declare_function(std::string const& name, + size_t addr, + std::shared_ptr prototype) + { + m_syms.push_back(Sym {name, addr, prototype}); + } + + std::optional SymTable::find(std::string const& name) + { + for (size_t i=0; i prototype = nullptr; + }; + + class SymTable + { + public: + explicit SymTable(); + virtual ~SymTable(); + + void declare(std::string const& name, size_t addr); + + void declare_function(std::string const& name, + size_t addr, + std::shared_ptr prototype); + + std::optional find(std::string const& name); + + private: + std::vector m_syms; + }; +} + +#endif diff --git a/src/VM.cpp b/src/VM.cpp index 27aa3ec..a6c92f4 100644 --- a/src/VM.cpp +++ b/src/VM.cpp @@ -3,8 +3,11 @@ namespace zn { - /*explicit*/ VM::VM() + /*explicit*/ VM::VM(Program& program) { + m_pc = 0; + Frame f = {program, {}}; + m_frames.push_back(f); } /*virtual*/ VM::~VM() @@ -14,16 +17,15 @@ namespace zn void VM::execute(Program& program) { m_pc = 0; - m_frames.push_back(Frame {program}); while (m_pc < program.size()) { auto instr = program.instr(m_pc); - execute(instr.opcode, instr.param); + execute(program, instr.opcode, instr.param); } } - void VM::execute(Opcode opcode, int param) + void VM::execute(Program& program, Opcode opcode, int param) { switch (opcode) { @@ -32,11 +34,41 @@ namespace zn m_pc++; } break; + case OPCODE_LOAD_LOCAL: { + auto constant = frame().locals[to_vm_addr(param)]; + push(program.add_constant(constant)); + m_pc++; + } break; + case OPCODE_POP: { pop(); m_pc++; } break; + case OPCODE_CALL_NATIVE: { + std::vector> args; + + for (int i=0; i(*fun_ref->value()); + + auto fun = std::get> + (m_globals[ref]); + assert(fun); + + auto result = fun->call(args); + int addr = m_frames.back().program.add_constant(result); + push(addr); + + m_pc++; + } break; + default: { std::cerr << "cannot execute opcode '" << OpcodeStr[opcode] @@ -63,6 +95,52 @@ namespace zn return ss.str(); } + size_t VM::store_local(int index, std::shared_ptr constant) + { + frame().locals.push_back(constant); + size_t addr = frame().locals.size() - 1; + m_addr_mapping[index] = addr; + return addr; + } + + std::shared_ptr VM::load_local(int index) const + { + size_t addr = to_vm_addr(index); + assert(addr < m_frames.back().locals.size()); + return m_frames.back().locals[addr]; + } + + size_t VM::store_global(global_var_t var) + { + m_globals.push_back(var); + return m_globals.size() - 1; + } + + size_t VM::to_vm_addr(int user_addr) const + { + return m_addr_mapping.at(user_addr); + } + + int VM::to_user_addr(size_t vm_addr) const + { + for (auto const& entry: m_addr_mapping) + { + if (entry.second == vm_addr) + { + return entry.first; + } + } + + std::cerr << "cannot find user addr of '" + << vm_addr << "'" << std::endl; + abort(); + } + + Frame& VM::frame() + { + return m_frames.back(); + } + void VM::push(int value) { m_stack.push_back(value); diff --git a/src/VM.hpp b/src/VM.hpp index 63cd995..54b64d6 100644 --- a/src/VM.hpp +++ b/src/VM.hpp @@ -3,29 +3,44 @@ #include "common.hpp" #include "Program.hpp" +#include "src/NativeFunction.hpp" namespace zn { struct Frame { Program& program; + std::vector> locals; }; + using global_var_t = std::variant>; + class VM { public: - explicit VM(); + explicit VM(Program& program); virtual ~VM(); void execute(Program& program); - void execute(Opcode opcode, int param); + void execute(Program& program, Opcode opcode, int param); std::string string() const; + size_t store_local(int index, std::shared_ptr constant); + std::shared_ptr load_local(int index) const; + + size_t store_global(global_var_t var); + + size_t to_vm_addr(int user_addr) const; + int to_user_addr(size_t vm_addr) const; + private: std::vector m_frames; std::vector m_stack; + std::vector m_globals; + std::unordered_map m_addr_mapping; size_t m_pc = 0; + Frame& frame(); void push(int value); int pop(); }; diff --git a/src/Zarn.cpp b/src/Zarn.cpp new file mode 100644 index 0000000..0e50f71 --- /dev/null +++ b/src/Zarn.cpp @@ -0,0 +1,54 @@ +#include +#include "config.hpp" +#include "Zarn.hpp" + +namespace zn +{ + /*explicit*/ Zarn::Zarn(SymTable& sym, VM& vm) + : m_sym { sym } + , m_vm { vm } + { + } + + /*virtual*/ Zarn::~Zarn() + { + for (void* handler: m_handlers) + { + // dlclose(handler); + } + + m_handlers.clear(); + } + + void Zarn::register_function(std::string const& name, + std::shared_ptr prototype, + native_fn_t body) + { + auto func = std::make_shared(prototype, body); + int addr = m_vm.store_global(func); + m_sym.declare_function(name, addr, prototype); + + // auto ref = std::make_shared(TYPE_REF, addr); + // m_vm.store_local(0, ref); + // m_sym.declare_function(name, 0, prototype); + } + + void Zarn::load_std_library() + { + load_library(ZN_LIBDIR / "libzarn-std.so"); + } + + void Zarn::load_library(std::filesystem::path lib_path) + { + void* handler = dlopen(lib_path.string().c_str(), RTLD_LAZY); + assert(handler); + m_handlers.push_back(handler); + + + typedef void(*fun)(Zarn&); + fun f = (fun) dlsym(handler, "lib"); + assert(f); + + f(*this); + } +} diff --git a/src/Zarn.hpp b/src/Zarn.hpp new file mode 100644 index 0000000..ff7a8e0 --- /dev/null +++ b/src/Zarn.hpp @@ -0,0 +1,29 @@ +#ifndef zn_ZARN_HPP +#define zn_ZARN_HPP + +#include "common.hpp" +#include "SymTable.hpp" +#include "VM.hpp" + +namespace zn +{ + class Zarn + { + public: + explicit Zarn(SymTable& sym, VM& vm); + virtual ~Zarn(); + + void register_function(std::string const& name, + std::shared_ptr prototype, + native_fn_t body); + + void load_std_library(); + void load_library(std::filesystem::path lib_path); + private: + SymTable& m_sym; + VM& m_vm; + std::vector m_handlers; + }; +} + +#endif diff --git a/src/config.in.hpp b/src/config.in.hpp new file mode 100644 index 0000000..5e2c324 --- /dev/null +++ b/src/config.in.hpp @@ -0,0 +1,8 @@ +#ifndef zn_CONFIG_HPP +#define zn_CONFIG_HPP + +#include + +#define ZN_VERSION "@version@" +#define ZN_LIBDIR std::filesystem::path("@libdir@") +#endif diff --git a/src/types.hpp b/src/types.hpp new file mode 100644 index 0000000..1039bdf --- /dev/null +++ b/src/types.hpp @@ -0,0 +1,16 @@ +#ifndef zn_TYPES_HPP +#define zn_TYPES_HPP + +#include "common.hpp" + +#define TYPES(G) \ + G(TYPE_NIL), \ + G(TYPE_INT), \ + G(TYPE_REF) + +namespace zn +{ + ZN_MK_ENUM(Type, TYPES); +} + +#endif diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index 0219f8c..1413b7f 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -21,12 +21,6 @@ protected: zn::Lexer m_lexer { m_logger, m_loc }; }; -TEST_CASE_METHOD(LexerTest, "Lexer_unknown_text") -{ - m_lexer.scan(" ยงยงยง "); - REQUIRE_THROWS_AS(m_lexer.try_next(), zn::lex_error); -} - TEST_CASE_METHOD(LexerTest, "Lexer_int") { m_lexer.scan(" 3 -2 167 "); @@ -36,3 +30,14 @@ TEST_CASE_METHOD(LexerTest, "Lexer_int") test_next(m_lexer, "INT[167]"); REQUIRE(nullptr == m_lexer.try_next()); } + +TEST_CASE_METHOD(LexerTest, "Lexer_call") +{ + m_lexer.scan(" (hello world) "); + + test_next(m_lexer, "OPAR"); + test_next(m_lexer, "IDENT[hello]"); + test_next(m_lexer, "IDENT[world]"); + test_next(m_lexer, "CPAR"); + REQUIRE(nullptr == m_lexer.try_next()); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 6024cff..4860342 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -29,3 +29,9 @@ TEST_CASE_METHOD(ParserTest, "Parser_int") { test_parse("MODULE(INT[37])", " 37"); } + +TEST_CASE_METHOD(ParserTest, "Parser_call") +{ + test_parse("MODULE(CALL(IDENT[hello],INT[1],INT[2],IDENT[world]))", + " (hello 1 2 world)"); +}