ADD: native functions.

ADD: assert= and println core functions.
main
bog 2023-09-18 17:33:04 +02:00
parent 8db23dbd3c
commit 55c094e4ec
34 changed files with 972 additions and 43 deletions

View File

@ -1,3 +1,7 @@
MODULE ::= EXPR*
EXPR ::=
int
| int
| ident
| CALL
CALL ::= opar ident EXPR* cpar

9
libstd/common.hpp Normal file
View File

@ -0,0 +1,9 @@
#ifndef COMMON_HPP
#define COMMON_HPP
#include "../src/Zarn.hpp"
using namespace zn;
#define STDARGS std::vector<std::shared_ptr<Constant>>
#endif

36
libstd/fun.cpp Normal file
View File

@ -0,0 +1,36 @@
#include "fun.hpp"
std::shared_ptr<Constant> 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<Constant>(TYPE_NIL, args[0]->loc());
}
std::shared_ptr<Constant> 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<Constant>(TYPE_NIL, loc, 0);
}

9
libstd/fun.hpp Normal file
View File

@ -0,0 +1,9 @@
#ifndef FUN_HPP
#define FUN_HPP
#include "common.hpp"
std::shared_ptr<Constant> println(STDARGS args);
std::shared_ptr<Constant> assert_eq(STDARGS args);
#endif

19
libstd/std.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "common.hpp"
#include "fun.hpp"
extern "C" void lib(Zarn& zarn)
{
zarn.register_function("println",
std::make_shared<Prototype>()
->param_any()
->param_variadic()
->ret(TYPE_NIL),
println);
zarn.register_function("assert=",
std::make_shared<Prototype>()
->param_any()
->param_any()
->ret(TYPE_NIL),
assert_eq);
}

View File

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

View File

@ -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; i<node.size(); i++)
{
@ -25,10 +28,44 @@ namespace zn
}
} break;
// FUNCTION STUFF
// ==============
case NODE_CALL: {
for (size_t i=0; i<node.size(); i++)
{
compile(*node.child_at(i), program);
}
program.append(OPCODE_CALL_NATIVE, node.size() - 1);
} break;
// VALUES
// ======
case NODE_IDENT: {
std::string ident = node.repr();
auto sym = m_sym.find(ident);
assert(sym);
if (sym->prototype)
{
auto ref = std::make_shared<Constant>(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<Constant>
(TYPE_INT, std::stoi(node.repr())));
(TYPE_INT, node.loc(),
std::stoi(node.repr())));
program.append(OPCODE_LOAD_CONST, addr);
} break;

View File

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

View File

@ -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<int>(&(*m_value));
val)
switch (m_type)
{
return std::to_string(*val);
case TYPE_NIL: return "<nil>";
case TYPE_INT: return std::to_string(std::get<int>(*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();
}
}

View File

@ -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<std::variant<int>>;
using constant_t = std::optional<std::variant<int>>;
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;
};
}

View File

@ -1,4 +1,6 @@
#include "Lexer.hpp"
#include "src/Node.hpp"
#include <cctype>
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<ScanInfo> 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<text.size(); i++)
{
if (text.at(i) != m_source.at(m_cursor + i))
{
return std::nullopt;
}
}
return ScanInfo {
m_cursor + text.size(),
type,
has_value ? text : ""
};
}
std::optional<ScanInfo> 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
};
}
}

View File

@ -35,9 +35,20 @@ namespace zn
size_t m_cursor;
std::vector<scanner_t> 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<ScanInfo> scan_int();
std::optional<ScanInfo> scan_text(std::string const& text,
NodeType type,
bool has_value);
std::optional<ScanInfo> scan_ident();
};
}

View File

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

View File

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

24
src/NativeFunction.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "NativeFunction.hpp"
namespace zn
{
/*explicit*/ NativeFunction::NativeFunction(std::shared_ptr<Prototype>
prototype,
native_fn_t body)
: m_prototype { prototype }
, m_body { body }
{
}
/*virtual*/ NativeFunction::~NativeFunction()
{
}
std::shared_ptr<Constant>
NativeFunction::call(std::vector<std::shared_ptr<Constant>> const& args)
{
assert(m_body);
return m_body(args);
}
}

30
src/NativeFunction.hpp Normal file
View File

@ -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<Constant>;
using native_fn_t = std::function<const_t(std::vector<const_t>)>;
class NativeFunction
{
public:
explicit NativeFunction(std::shared_ptr<Prototype> prototype,
native_fn_t body);
virtual ~NativeFunction();
std::shared_ptr<Prototype> prototype() const { return m_prototype; }
std::shared_ptr<Constant>
call(std::vector<std::shared_ptr<Constant>> const& args);
private:
std::shared_ptr<Prototype> m_prototype;
native_fn_t m_body;
};
}
#endif

View File

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

View File

@ -77,6 +77,11 @@ namespace zn
std::shared_ptr<Node> Parser::parse_module()
{
if (m_tokens.empty())
{
return std::make_shared<Node>(NODE_MODULE, "", Loc {""});
}
auto node = mk_node(NODE_MODULE);
while (m_cursor < m_tokens.size())
@ -89,11 +94,17 @@ namespace zn
std::shared_ptr<Node> 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<Node> 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;
}
}

View File

@ -31,6 +31,7 @@ namespace zn
std::shared_ptr<Node> parse_module();
std::shared_ptr<Node> parse_expr();
std::shared_ptr<Node> parse_call();
};
}

View File

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

105
src/Prototype.cpp Normal file
View File

@ -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> Prototype::param(TypeSlot param)
{
m_params.push_back(param);
return shared_from_this();
}
std::shared_ptr<Prototype> Prototype::param(Type param)
{
TypeSlot slot;
slot.type = param;
m_params.push_back(slot);
return shared_from_this();
}
std::shared_ptr<Prototype> 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> 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> Prototype::ret(TypeSlot ret)
{
m_return = ret;
return shared_from_this();
}
std::shared_ptr<Prototype> Prototype::ret(Type ret)
{
TypeSlot slot;
slot.type = ret;
m_return = slot;
return shared_from_this();
}
std::shared_ptr<Prototype> 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();
}
}

50
src/Prototype.hpp Normal file
View File

@ -0,0 +1,50 @@
#ifndef zn_PROTOTYPE_HPP
#define zn_PROTOTYPE_HPP
#include "common.hpp"
#include "types.hpp"
#include <memory>
#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 = TYPE_NIL;
Tag tag = TAG_NONE;
};
class Prototype: public std::enable_shared_from_this<Prototype>
{
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<Prototype> param(TypeSlot param);
std::shared_ptr<Prototype> param(Type param);
std::shared_ptr<Prototype> param_any();
std::shared_ptr<Prototype> param_variadic();
std::shared_ptr<Prototype> ret(TypeSlot ret);
std::shared_ptr<Prototype> ret(Type ret);
std::shared_ptr<Prototype> ret_any();
std::string string() const;
private:
std::vector<TypeSlot> m_params;
TypeSlot m_return;
};
}
#endif

114
src/StaticPass.cpp Normal file
View File

@ -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; i<node.size(); i++)
{
execute(*node.child_at(i));
}
return TypeSlot {TYPE_NIL};
} break;
case NODE_CALL: {
std::string ident = node.child_at(0)->repr();
auto sym = m_sym.find(ident);
if (sym == std::nullopt)
{
m_logger.log<static_error>(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<static_error>(LOG_ERROR, node.loc(), ss.str());
}
size_t j = 0;
for (size_t i=1; i<node.size(); i++)
{
auto ty = execute(*node.child_at(i));
if ((ty.tag != proto->get_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<static_error>(LOG_ERROR, loc, ss.str());
}
}

30
src/StaticPass.hpp Normal file
View File

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

37
src/SymTable.cpp Normal file
View File

@ -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> prototype)
{
m_syms.push_back(Sym {name, addr, prototype});
}
std::optional<Sym> SymTable::find(std::string const& name)
{
for (size_t i=0; i<m_syms.size(); i++)
{
if (name == m_syms[i].name)
{
return m_syms[i];
}
}
return std::nullopt;
}
}

34
src/SymTable.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef zn_SYMTABLE_HPP
#define zn_SYMTABLE_HPP
#include "common.hpp"
#include "Prototype.hpp"
namespace zn
{
struct Sym {
std::string name;
size_t addr;
std::shared_ptr<Prototype> 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> prototype);
std::optional<Sym> find(std::string const& name);
private:
std::vector<Sym> m_syms;
};
}
#endif

View File

@ -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<std::shared_ptr<Constant>> args;
for (int i=0; i<param; i++)
{
size_t addr = pop();
args.insert(std::begin(args),
frame().program.constant(addr));
}
auto fun_ref = m_frames.back().program.constant(pop());
int ref = std::get<int>(*fun_ref->value());
auto fun = std::get<std::shared_ptr<NativeFunction>>
(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> constant)
{
frame().locals.push_back(constant);
size_t addr = frame().locals.size() - 1;
m_addr_mapping[index] = addr;
return addr;
}
std::shared_ptr<Constant> 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);

View File

@ -3,29 +3,44 @@
#include "common.hpp"
#include "Program.hpp"
#include "src/NativeFunction.hpp"
namespace zn
{
struct Frame {
Program& program;
std::vector<std::shared_ptr<Constant>> locals;
};
using global_var_t = std::variant<std::shared_ptr<NativeFunction>>;
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> constant);
std::shared_ptr<Constant> 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<Frame> m_frames;
std::vector<int> m_stack;
std::vector<global_var_t> m_globals;
std::unordered_map<int, size_t> m_addr_mapping;
size_t m_pc = 0;
Frame& frame();
void push(int value);
int pop();
};

54
src/Zarn.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <dlfcn.h>
#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> prototype,
native_fn_t body)
{
auto func = std::make_shared<NativeFunction>(prototype, body);
int addr = m_vm.store_global(func);
m_sym.declare_function(name, addr, prototype);
// auto ref = std::make_shared<Constant>(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);
}
}

29
src/Zarn.hpp Normal file
View File

@ -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> 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<void*> m_handlers;
};
}
#endif

8
src/config.in.hpp Normal file
View File

@ -0,0 +1,8 @@
#ifndef zn_CONFIG_HPP
#define zn_CONFIG_HPP
#include <filesystem>
#define ZN_VERSION "@version@"
#define ZN_LIBDIR std::filesystem::path("@libdir@")
#endif

16
src/types.hpp Normal file
View File

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

View File

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

View File

@ -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)");
}