Compare commits

...

5 Commits

Author SHA1 Message Date
bog f63cbf4cea ADD: blocks. 2023-09-19 12:50:12 +02:00
bog 820d26491a ADD: now expose module to macros. 2023-09-19 11:29:23 +02:00
bog 073cbb4618 ADD: vardecl. 2023-09-19 06:38:14 +02:00
bog 0aa9ebd952 ADD: test script. 2023-09-18 19:32:16 +02:00
bog ee1b52cae1 ADD: native macros and assert-fail macro. 2023-09-18 19:08:32 +02:00
24 changed files with 485 additions and 80 deletions

20
examples/block.zn Normal file
View File

@ -0,0 +1,20 @@
($ a 2)
(assert= 2 a)
(:
($ a 9)
(assert= 9 a)
(:
($ a 42)
(assert= 42 a)
)
(assert= 9 a)
)
(assert= 2 a)
(:($ b 43))
(assert-fail b)

43
examples/run.sh Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env bash
TOTAL=0
SUCCESS=0
for file in `find -name "*.zn"`
do
NAME=$(basename $file | cut -d'.' -f1)
MSG=$(zarn "$file" 2>&1 > /dev/null)
STATUS=$?
echo -en "\e[34m$NAME ... \e[0m"
if [ $STATUS -eq 0 ]
then
echo -e "\e[32mok\e[0m"
SUCCESS=$(($SUCCESS + 1))
else
echo -e "\e[31mko\e[0m"
echo "$MSG"
fi
TOTAL=$((TOTAL + 1))
done
FAILURE=$(($TOTAL - $SUCCESS))
if [ $SUCCESS -eq $TOTAL ]
then
echo -e "\e[32m======== All tests passed ========\e[0m"
else
echo -e "\e[31m======== $FAILURE tests failed ========\e[0m"
fi
echo -e "\e[0mok:\t$SUCCESS\e[0m"
echo -e "\e[0mko:\t$FAILURE\e[0m"
if [ $SUCCESS -eq $TOTAL ]
then
echo -e "\e[32mTOTAL:\t$TOTAL\e[0m"
else
echo -e "\e[31mTOTAL:\t$TOTAL\e[0m"
fi

8
examples/vardecl.zn Normal file
View File

@ -0,0 +1,8 @@
(assert-fail a)
($ a 34)
(assert-fail (a))
(assert= 34 a)
($ b ($ c 9))
(assert= 9 b)
(assert= 9 c)

View File

@ -2,6 +2,8 @@
#define COMMON_HPP
#include "../src/Zarn.hpp"
#include "../src/Module.hpp"
using namespace zn;
#define STDARGS std::vector<std::shared_ptr<Constant>>

54
libstd/macro.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "macro.hpp"
void assert_fail(Node const& node, Module& mod)
{
try
{
mod.static_pass().execute(*node.child_at(1));
mod.compiler().compile(*node.child_at(1), mod.program());
Loc loc = node.loc();
std::cerr << loc.file_path().string() << ":" << loc.line();
std::cerr << " ASSERTION FAILED" << std::endl;
exit(-1);
}
catch (std::exception const&)
{
}
size_t addr = mod.program().add_constant(std::make_shared<Constant>
(TYPE_NIL, node.loc(), 0));
mod.program().append(OPCODE_LOAD_CONST, addr);
}
void declare(Node const& node, Module &mod)
{
std::string ident = node.child_at(1)->repr();
mod.compiler().compile(*node.child_at(2), mod.program());
auto entry = mod.sym().find_any(ident);
assert(entry);
size_t addr = mod.sym().gen_addr();
mod.sym().declare(ident, addr, entry->scope);
mod.program().append(OPCODE_STORE_LOCAL, addr);
}
void block(Node const& node, Module& mod)
{
mod.sym().enter_scope();
for (size_t i=1; i<node.size(); i++)
{
mod.static_pass().execute(*node.child_at(i));
mod.compiler().compile(*node.child_at(i), mod.program());
if (i != node.size() - 1)
{
mod.program().append(OPCODE_POP);
}
}
mod.sym().leave_scope();
}

12
libstd/macro.hpp Normal file
View File

@ -0,0 +1,12 @@
#ifndef MACRO_HPP
#define MACRO_HPP
#include "common.hpp"
void assert_fail(Node const& node, Module& mod);
void declare(Node const& node, Module& mod);
void block(Node const& node, Module& mod);
#endif

View File

@ -1,5 +1,7 @@
#include "common.hpp"
#include "fun.hpp"
#include "macro.hpp"
#include "../src/types.hpp"
extern "C" void lib(Zarn& zarn)
{
@ -16,4 +18,8 @@ extern "C" void lib(Zarn& zarn)
->param_any()
->ret(TYPE_NIL),
assert_eq);
zarn.register_macro("assert-fail", assert_fail);
zarn.register_macro("$", declare);
zarn.register_macro(":", block);
}

View File

@ -37,6 +37,7 @@ zarn_lib = shared_library('zarn',
'src/SymTable.cpp',
'src/NativeFunction.cpp',
'src/Prototype.cpp',
'src/NativeMacro.cpp',
'src/Zarn.cpp',
],
install: true)
@ -49,6 +50,7 @@ shared_library('zarn-std',
sources: [
'libstd/std.cpp',
'libstd/fun.cpp',
'libstd/macro.cpp',
],
dependencies: [
zarn_dep

View File

@ -1,11 +1,16 @@
#include "Compiler.hpp"
#include "src/Node.hpp"
#include "src/Program.hpp"
#include "Module.hpp"
#include "Node.hpp"
#include "Program.hpp"
#include "NativeMacro.hpp"
namespace zn
{
/*explicit*/ Compiler::Compiler(Logger& logger, SymTable& sym)
: m_logger { logger }
/*explicit*/ Compiler::Compiler(Module& mod,
Logger& logger,
SymTable& sym)
: m_mod { mod }
, m_logger { logger }
, m_sym { sym }
{
}
@ -14,6 +19,11 @@ namespace zn
{
}
void Compiler::add_macro(std::shared_ptr<NativeMacro> macro)
{
m_macros.push_back(macro);
}
void Compiler::compile(Node const& node, Program& program)
{
switch (node.type())
@ -31,12 +41,44 @@ namespace zn
// FUNCTION STUFF
// ==============
case NODE_CALL: {
std::string ident = node.child_at(0)->repr();
auto sym = m_sym.find(ident);
if (sym)
{
for (size_t i=0; i<node.size(); i++)
{
compile(*node.child_at(i), program);
}
program.append(OPCODE_CALL_NATIVE, node.size() - 1);
}
else
{
bool found_macro = false;
for (auto macro: m_macros)
{
if (macro->name() == ident)
{
macro->execute(node, m_mod);
found_macro = true;
break;
}
}
if (!found_macro)
{
std::stringstream ss;
ss << "cannot call undefined function '"
<< ident
<< "'";
m_logger.log<compile_error>(LOG_ERROR,
node.loc(),
ss.str());
}
}
} break;
// VALUES
@ -44,19 +86,30 @@ namespace zn
case NODE_IDENT: {
std::string ident = node.repr();
auto sym = m_sym.find(ident);
assert(sym);
if (!sym)
{
std::stringstream ss;
ss << "cannot use undefined variable '"
<< ident
<< "'";
m_logger.log<compile_error>(LOG_ERROR,
node.loc(),
ss.str());
}
if (sym->prototype)
{
auto ref = std::make_shared<Constant>(TYPE_REF,
node.loc(),
(int) sym->addr);
(int) *sym->addr);
size_t val = program.add_constant(ref);
program.append(OPCODE_LOAD_CONST, val);
}
else
{
program.append(OPCODE_LOAD_LOCAL, sym->addr);
program.append(OPCODE_LOAD_LOCAL, *sym->addr);
}
} break;

View File

@ -11,17 +11,23 @@ namespace zn
{
ZN_ERROR(compile_error);
class NativeMacro;
class Module;
class Compiler
{
public:
explicit Compiler(Logger& logger, SymTable& sym);
explicit Compiler(Module& mod, Logger& logger, SymTable& sym);
virtual ~Compiler();
void add_macro(std::shared_ptr<NativeMacro> macro);
void compile(Node const& node, Program& program);
private:
Module& m_mod;
Logger& m_logger;
SymTable& m_sym;
std::vector<std::shared_ptr<NativeMacro>> m_macros;
};
}

View File

@ -31,6 +31,10 @@ namespace zn
{
case TYPE_NIL: return "<nil>";
case TYPE_INT: return std::to_string(std::get<int>(*m_value));
case TYPE_REF: {
return "<ref " + std::to_string(std::get<int>(*m_value)) + ">";
} break;
default: {
std::cerr << "cannot stringify "
<< (TypeStr[m_type] + strlen("TYPE_"))

View File

@ -1,4 +1,5 @@
#include "Module.hpp"
#include "Zarn.hpp"
#include "Lexer.hpp"
#include "Parser.hpp"
#include "Program.hpp"
@ -48,11 +49,8 @@ namespace zn
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_static_pass.execute(*ast);
m_compiler.compile(*ast, m_program);
m_vm.execute(m_program);
}

View File

@ -6,6 +6,8 @@
#include "VM.hpp"
#include "Program.hpp"
#include "SymTable.hpp"
#include "StaticPass.hpp"
#include "Compiler.hpp"
#include "Zarn.hpp"
namespace zn
@ -18,6 +20,12 @@ namespace zn
explicit Module(Logger& logger);
virtual ~Module();
Compiler& compiler() { return m_compiler; }
StaticPass& static_pass() { return m_static_pass; }
Program& program() { return m_program; }
SymTable& sym() { return m_sym; }
void load_from_file(std::filesystem::path file_path);
std::string string() const;
@ -27,7 +35,9 @@ namespace zn
Program m_program;
VM m_vm { m_program };
SymTable m_sym;
Zarn m_zarn {m_sym, m_vm};
StaticPass m_static_pass {m_logger, m_sym};
Compiler m_compiler {*this, m_logger, m_sym};
Zarn m_zarn {m_compiler, m_sym, m_vm};
};
}

21
src/NativeMacro.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "NativeMacro.hpp"
namespace zn
{
/*explicit*/ NativeMacro::NativeMacro(std::string const& name,
native_macro_t macro)
: m_name { name }
, m_macro { macro }
{
}
/*virtual*/ NativeMacro::~NativeMacro()
{
}
void NativeMacro::execute(Node const& node,
Module& mod)
{
m_macro(node, mod);
}
}

36
src/NativeMacro.hpp Normal file
View File

@ -0,0 +1,36 @@
#ifndef zn_NATIVEMACRO_HPP
#define zn_NATIVEMACRO_HPP
#include "common.hpp"
#include "Compiler.hpp"
#include "SymTable.hpp"
#include "Node.hpp"
namespace zn
{
class Module;
using native_macro_t = std::function<void
(Node const&,
Module&)>;
class NativeMacro
{
public:
explicit NativeMacro(std::string const& name, native_macro_t macro);
virtual ~NativeMacro();
std::string name() const { return m_name; }
void execute(Node const& node,
Module&);
private:
std::string m_name;
native_macro_t m_macro;
};
}
#endif

View File

@ -27,19 +27,33 @@ namespace zn
case NODE_CALL: {
std::string ident = node.child_at(0)->repr();
auto sym = m_sym.find(ident);
auto sym = m_sym.find_any(ident);
if (ident == "$")
{
std::string varname = node.child_at(1)->repr();
auto vartype = execute(*node.child_at(2));
m_sym.prepare(varname, *vartype.type);
return vartype;
}
if (sym == std::nullopt)
{
m_logger.log<static_error>(LOG_ERROR,
node.loc(),
"cannot call unknown function '"
+ ident
+ "'");
return TypeSlot {TYPE_NIL, TAG_ANY};
}
auto proto = sym->prototype;
assert(proto);
if (!proto)
{
std::stringstream ss;
ss << "cannot call a none function variable '"
<< ident
<< "'";
m_logger.log<static_error>(LOG_ERROR, node.loc(), ss.str());
}
if (proto->get_param_count() != node.size() - 1
&& proto->get_param(proto->get_param_count() - 1).tag
@ -79,6 +93,19 @@ namespace zn
return proto->get_ret();
} break;
case NODE_IDENT: {
std::string ident = node.repr();
auto sym = m_sym.find_any(ident);
if (sym) assert(sym->scope <= m_sym.scope());
if (sym == std::nullopt)
{
std::stringstream ss;
ss << "undeclared var '" << ident << "'";
m_logger.log<static_error>(LOG_ERROR, node.loc(), ss.str());
}
return TypeSlot {sym->type};
} break;
case NODE_INT: {
return TypeSlot {TYPE_INT};
} break;

View File

@ -17,6 +17,7 @@ namespace zn
virtual ~StaticPass();
TypeSlot execute(Node const& node);
private:
Logger& m_logger;
SymTable& m_sym;

View File

@ -10,28 +10,115 @@ namespace zn
{
}
void SymTable::declare(std::string const& name, size_t addr)
void SymTable::prepare(std::string const& name, Type type)
{
m_syms.push_back(Sym {name, addr});
m_syms.push_back(Sym {name, std::nullopt, type, nullptr, m_scope});
}
void SymTable::declare(std::string const& name,
size_t addr,
int scope)
{
auto itr = std::find_if(std::begin(m_syms),
std::end(m_syms),
[name, scope](auto const& sym){
return sym.name == name
&& sym.scope == scope;
});
if (itr == std::end(m_syms))
{
abort();
}
itr->addr = 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});
m_syms.push_back(Sym {name, addr, TYPE_FUNCTION, prototype, m_scope});
}
std::optional<Sym> SymTable::find(std::string const& name)
{
std::optional<size_t> best;
for (size_t i=0; i<m_syms.size(); i++)
{
if (name == m_syms[i].name)
if (name == m_syms[i].name
&& m_syms[i].addr
&& (best == std::nullopt ||
(m_syms[i].scope > m_syms[*best].scope
&& m_syms[*best].scope < m_scope)))
{
return m_syms[i];
best = i;
}
}
if (best)
{
return m_syms[*best];
}
return std::nullopt;
}
std::optional<Sym> SymTable::find_any(std::string const& name)
{
std::optional<size_t> best;
for (size_t i=0; i<m_syms.size(); i++)
{
if (name == m_syms[i].name
&& m_syms[i].scope <= m_scope
&& (best == std::nullopt ||
(m_syms[i].scope > m_syms[*best].scope
&& m_syms[*best].scope <= m_scope)))
{
best = i;
}
}
if (best)
{
return m_syms[*best];
}
return std::nullopt;
}
size_t SymTable::gen_addr()
{
static size_t addr = 0;
addr++;
return addr;
}
void SymTable::enter_scope()
{
m_scope++;
}
void SymTable::leave_scope()
{
m_scope--;
}
std::string SymTable::string() const
{
std::stringstream ss;
ss << "======== " << m_scope << " ========\n";
for (auto sym: m_syms)
{
ss << sym.scope << "\t" << sym.name << " "
<< (sym.addr ? "[" + std::to_string(*sym.addr) + "]" : "[nil]")
<< "\n";
}
return ss.str();
}
}

View File

@ -3,13 +3,16 @@
#include "common.hpp"
#include "Prototype.hpp"
#include "Node.hpp"
namespace zn
{
struct Sym {
std::string name;
size_t addr;
std::optional<size_t> addr;
Type type;
std::shared_ptr<Prototype> prototype = nullptr;
int scope = 0;
};
class SymTable
@ -18,16 +21,30 @@ namespace zn
explicit SymTable();
virtual ~SymTable();
void declare(std::string const& name, size_t addr);
int scope() const { return m_scope; }
size_t size() const { return m_syms.size(); }
void prepare(std::string const& name, Type type);
void declare(std::string const& name,
size_t addr,
int scope);
void declare_function(std::string const& name,
size_t addr,
std::shared_ptr<Prototype> prototype);
std::optional<Sym> find(std::string const& name);
std::optional<Sym> find_any(std::string const& name);
size_t gen_addr();
void enter_scope();
void leave_scope();
std::string string() const;
private:
std::vector<Sym> m_syms;
int m_scope = 0;
};
}

View File

@ -35,8 +35,14 @@ namespace zn
} break;
case OPCODE_LOAD_LOCAL: {
auto constant = frame().locals[to_vm_addr(param)];
push(program.add_constant(constant));
auto constant = load_local(param);
size_t addr = program.add_constant(constant);
push(addr);
m_pc++;
} break;
case OPCODE_STORE_LOCAL: {
store_local(param, frame().program.constant(top()));
m_pc++;
} break;
@ -97,17 +103,14 @@ namespace zn
size_t VM::store_local(int index, std::shared_ptr<Constant> constant)
{
frame().locals.push_back(constant);
frame().locals[index] = 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];
return m_frames.back().locals.at(index);
}
size_t VM::store_global(global_var_t var)
@ -116,26 +119,6 @@ namespace zn
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();
@ -153,4 +136,11 @@ namespace zn
m_stack.pop_back();
return value;
}
int VM::top()
{
assert(m_stack.size() > 0);
int value = m_stack.back();
return value;
}
}

View File

@ -9,7 +9,7 @@ namespace zn
{
struct Frame {
Program& program;
std::vector<std::shared_ptr<Constant>> locals;
std::unordered_map<int, std::shared_ptr<Constant>> locals;
};
using global_var_t = std::variant<std::shared_ptr<NativeFunction>>;
@ -30,19 +30,16 @@ namespace zn
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();
int top();
};
}

View File

@ -4,18 +4,19 @@
namespace zn
{
/*explicit*/ Zarn::Zarn(SymTable& sym, VM& vm)
: m_sym { sym }
/*explicit*/ Zarn::Zarn(Compiler& compiler, SymTable& sym, VM& vm)
: m_compiler { compiler }
, m_sym { sym }
, m_vm { vm }
{
}
/*virtual*/ Zarn::~Zarn()
{
for (void* handler: m_handlers)
{
// dlclose(handler);
}
//for (void* handler: m_handlers)
// {
// // dlclose(handler);
// }
m_handlers.clear();
}
@ -27,12 +28,16 @@ namespace zn
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::register_macro(std::string const& name,
native_macro_t macro)
{
auto m = std::make_shared<NativeMacro>(name, macro);
m_compiler.add_macro(m);
}
void Zarn::load_std_library()
{
load_library(ZN_LIBDIR / "libzarn-std.so");

View File

@ -4,22 +4,27 @@
#include "common.hpp"
#include "SymTable.hpp"
#include "VM.hpp"
#include "NativeMacro.hpp"
namespace zn
{
class Zarn
{
public:
explicit Zarn(SymTable& sym, VM& vm);
explicit Zarn(Compiler& compiler, SymTable& sym, VM& vm);
virtual ~Zarn();
void register_function(std::string const& name,
std::shared_ptr<Prototype> prototype,
native_fn_t body);
void register_macro(std::string const& name,
native_macro_t macro);
void load_std_library();
void load_library(std::filesystem::path lib_path);
private:
Compiler& m_compiler;
SymTable& m_sym;
VM& m_vm;
std::vector<void*> m_handlers;

View File

@ -6,7 +6,8 @@
#define TYPES(G) \
G(TYPE_NIL), \
G(TYPE_INT), \
G(TYPE_REF)
G(TYPE_REF), \
G(TYPE_FUNCTION)
namespace zn
{