Compare commits

..

3 Commits

Author SHA1 Message Date
bog 55c094e4ec ADD: native functions.
ADD: assert= and println core functions.
2023-09-18 17:33:04 +02:00
bog 8db23dbd3c ADD: program and vm for integers. 2023-09-17 21:36:58 +02:00
bog 04f57c631e ADD: parser for integers.
ADD: lexer for comments.
2023-09-17 19:26:22 +02:00
45 changed files with 2136 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*\#*
*~*
build
.cache

11
Makefile Normal file
View File

@ -0,0 +1,11 @@
.PHONY: build tests
build:
meson setup build
meson compile -C build
tests: build
build/zarn-tests
install: tests
meson install -C build

7
doc/grammar.bnf Normal file
View File

@ -0,0 +1,7 @@
MODULE ::= EXPR*
EXPR ::=
| 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);
}

81
meson.build Normal file
View File

@ -0,0 +1,81 @@
project('zarn',
'cpp',
version: '0.0.0',
default_options: [
'prefix=/usr',
'warning_level=3',
'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',
'src/Loc.cpp',
'src/Node.cpp',
'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'
],
dependencies: [
zarn_dep
],
install: true)
# TESTS
# =====
executable('zarn-tests',
sources: [
'tests/main.cpp',
'tests/Lexer.cpp',
'tests/Parser.cpp',
],
dependencies: [
zarn_dep,
dependency('catch2')
])

79
src/Compiler.cpp Normal file
View File

@ -0,0 +1,79 @@
#include "Compiler.hpp"
#include "src/Node.hpp"
#include "src/Program.hpp"
namespace zn
{
/*explicit*/ Compiler::Compiler(Logger& logger, SymTable& sym)
: m_logger { logger }
, m_sym { sym }
{
}
/*virtual*/ Compiler::~Compiler()
{
}
void Compiler::compile(Node const& node, Program& program)
{
switch (node.type())
{
// MODULES
// =======
case NODE_MODULE: {
for (size_t i=0; i<node.size(); i++)
{
compile(*node.child_at(i), program);
program.append(OPCODE_POP);
}
} 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, node.loc(),
std::stoi(node.repr())));
program.append(OPCODE_LOAD_CONST, addr);
} break;
default: {
std::cerr << "cannot compile node '"
<< node.string() << "'" << std::endl;
abort();
} break;
}
}
}

28
src/Compiler.hpp Normal file
View File

@ -0,0 +1,28 @@
#ifndef zn_COMPILER_HPP
#define zn_COMPILER_HPP
#include "common.hpp"
#include "Logger.hpp"
#include "Node.hpp"
#include "Program.hpp"
#include "SymTable.hpp"
namespace zn
{
ZN_ERROR(compile_error);
class Compiler
{
public:
explicit Compiler(Logger& logger, SymTable& sym);
virtual ~Compiler();
void compile(Node const& node, Program& program);
private:
Logger& m_logger;
SymTable& m_sym;
};
}
#endif

42
src/Constant.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "Constant.hpp"
namespace zn
{
/*explicit*/ Constant::Constant(Type type,
Loc const& loc,
constant_t value)
: m_type { type }
, m_loc { loc }
, m_value { value }
{
}
/*virtual*/ Constant::~Constant()
{
}
bool Constant::equals(Constant const& rhs) const
{
if (m_type != rhs.m_type)
{
return false;
}
return m_value == rhs.m_value;
}
std::string Constant::string() const
{
switch (m_type)
{
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;
}
}
}

34
src/Constant.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef zn_CONSTANT_HPP
#define zn_CONSTANT_HPP
#include "common.hpp"
#include "types.hpp"
#include "Loc.hpp"
namespace zn
{
using constant_t = std::optional<std::variant<int>>;
class Constant
{
public:
explicit Constant(Type type,
Loc const& loc,
constant_t value=std::nullopt);
virtual ~Constant();
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;
std::string string() const;
private:
Type m_type;
Loc m_loc;
constant_t m_value;
};
}
#endif

214
src/Lexer.cpp Normal file
View File

@ -0,0 +1,214 @@
#include "Lexer.hpp"
#include "src/Node.hpp"
#include <cctype>
namespace zn
{
/*explicit*/ Lexer::Lexer(Logger& logger, Loc const& loc)
: 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()
{
}
void Lexer::scan(std::string const& source)
{
m_source = source;
m_cursor = 0;
}
std::shared_ptr<Node> Lexer::try_next()
{
std::optional<ScanInfo> info;
skip_spaces();
while (m_cursor < m_source.size()
&& m_source[m_cursor] == ';')
{
while (m_cursor < m_source.size()
&& m_source[m_cursor] != '\n')
{
m_cursor++;
}
skip_spaces();
}
for (auto const& scanner: m_scanners)
{
auto myinfo = scanner();
if (myinfo &&
(info == std::nullopt
|| info->cursor < myinfo->cursor))
{
info = myinfo;
}
}
if (info == std::nullopt
&& m_cursor < m_source.size())
{
std::string tok;
while (m_cursor < m_source.size()
&& !std::isspace(m_source[m_cursor]))
{
tok += m_source[m_cursor];
m_cursor++;
}
m_logger.log<lex_error>(LOG_ERROR, m_loc,
"unexpected token '" + tok + "'");
}
if (info)
{
m_cursor = info->cursor;
return std::make_shared<Node>(info->type, info->repr, m_loc);
}
return nullptr;
}
std::vector<std::shared_ptr<Node>> Lexer::all()
{
std::vector<std::shared_ptr<Node>> result;
std::shared_ptr<Node> n;
while ( (n = try_next()) )
{
result.push_back(n);
}
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()
&& std::isspace(m_source[m_cursor]))
{
if (m_source[m_cursor] == '\n')
{
m_loc = Loc {m_loc.file_path(), m_loc.line() + 1};
}
m_cursor++;
}
}
std::optional<ScanInfo> Lexer::scan_int()
{
size_t cursor = m_cursor;
std::string repr;
if (cursor < m_source.size()
&& m_source[cursor] == '-')
{
repr += '-';
cursor++;
}
while (cursor < m_source.size()
&& std::isdigit(m_source[cursor]))
{
repr += m_source[cursor];
cursor++;
}
if (repr.empty() || repr.back() == '-')
{
return std::nullopt;
}
return ScanInfo {
cursor,
NODE_INT,
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
};
}
}

55
src/Lexer.hpp Normal file
View File

@ -0,0 +1,55 @@
#ifndef zn_LEXER_HPP
#define zn_LEXER_HPP
#include "common.hpp"
#include "Logger.hpp"
#include "Node.hpp"
#include "Loc.hpp"
namespace zn
{
ZN_ERROR(lex_error);
struct ScanInfo {
size_t cursor;
NodeType type;
std::string repr;
};
using scanner_t = std::function<std::optional<ScanInfo>()>;
class Lexer
{
public:
explicit Lexer(Logger& logger, Loc const& loc);
virtual ~Lexer();
void scan(std::string const& source);
std::shared_ptr<Node> try_next();
std::vector<std::shared_ptr<Node>> all();
private:
Logger& m_logger;
std::string m_source;
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();
};
}
#endif

14
src/Loc.cpp Normal file
View File

@ -0,0 +1,14 @@
#include "Loc.hpp"
namespace zn
{
/*explicit*/ Loc::Loc(std::filesystem::path file_path, int line)
: m_file_path { file_path }
, m_line { line }
{
}
/*virtual*/ Loc::~Loc()
{
}
}

24
src/Loc.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef zn_LOC_HPP
#define zn_LOC_HPP
#include "common.hpp"
namespace zn
{
class Loc
{
public:
explicit Loc(std::filesystem::path file_path, int line=1);
virtual ~Loc();
std::filesystem::path file_path() const { return m_file_path; }
int line() const { return m_line; }
private:
std::filesystem::path m_file_path;
int m_line;
};
}
#endif

12
src/Logger.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "Logger.hpp"
namespace zn
{
/*explicit*/ Logger::Logger()
{
}
/*virtual*/ Logger::~Logger()
{
}
}

44
src/Logger.hpp Normal file
View File

@ -0,0 +1,44 @@
#ifndef zn_LOGGER_HPP
#define zn_LOGGER_HPP
#include "common.hpp"
#include "Loc.hpp"
#define LOG_CATEGORIES(G) \
G(LOG_ERROR), \
G(LOG_WARNING)
namespace zn
{
ZN_MK_ENUM(LogCat, LOG_CATEGORIES);
class Logger
{
public:
explicit Logger();
virtual ~Logger();
template <typename T>
void log(LogCat category,
Loc const& loc,
std::string const& what);
private:
};
template <typename T>
void Logger::log(LogCat category,
Loc const& loc,
std::string const& what)
{
std::stringstream ss;
ss << loc.file_path().string() << ":" << loc.line();
ss << " " << (LogCatStr[category] + strlen("LOG_"));
ss << " " << what;
throw T {ss.str()};
}
}
#endif

67
src/Module.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "Module.hpp"
#include "Lexer.hpp"
#include "Parser.hpp"
#include "Program.hpp"
#include "Compiler.hpp"
#include "StaticPass.hpp"
#include "VM.hpp"
#include "NativeFunction.hpp"
#include "config.hpp"
namespace zn
{
/*explicit*/ Module::Module(Logger& logger)
: m_logger { logger }
{
}
/*virtual*/ Module::~Module()
{
}
void Module::load_from_file(std::filesystem::path file_path)
{
std::string line;
std::ifstream file { file_path };
if (!file)
{
m_logger.log<module_error>(LOG_ERROR,
Loc {file_path},
"cannot load module '"
+ file_path.string() + "'");
}
m_source = "";
while (std::getline(file, line))
{
m_source += line + (file.eof() ? "":"\n");
}
Lexer lexer { m_logger, Loc {file_path} };
lexer.scan(m_source);
Parser parser { m_logger };
auto ast = parser.parse(lexer.all());
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);
}
std::string Module::string() const
{
std::stringstream ss;
ss << m_program.string() << "\n";
ss << m_vm.string() << "\n";
return ss.str();
}
}

34
src/Module.hpp Normal file
View File

@ -0,0 +1,34 @@
#ifndef zn_MODULE_HPP
#define zn_MODULE_HPP
#include "common.hpp"
#include "Logger.hpp"
#include "VM.hpp"
#include "Program.hpp"
#include "SymTable.hpp"
#include "Zarn.hpp"
namespace zn
{
ZN_ERROR(module_error);
class Module
{
public:
explicit Module(Logger& logger);
virtual ~Module();
void load_from_file(std::filesystem::path file_path);
std::string string() const;
private:
Logger& m_logger;
std::string m_source;
Program m_program;
VM m_vm { m_program };
SymTable m_sym;
Zarn m_zarn {m_sym, m_vm};
};
}
#endif

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

56
src/Node.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "Node.hpp"
namespace zn
{
/*explicit*/ Node::Node(NodeType type,
std::string const& repr,
Loc const& loc)
: m_type { type }
, m_repr { repr }
, m_loc { loc }
{
}
/*virtual*/ Node::~Node()
{
}
void Node::add_child(std::shared_ptr<Node> child)
{
assert(child);
m_children.push_back(child);
}
std::shared_ptr<Node> Node::child_at(size_t index) const
{
assert(index < size());
return m_children.at(index);
}
std::string Node::string() const
{
std::stringstream ss;
ss << (NodeTypeStr[m_type] + strlen("NODE_"));
if (!m_repr.empty())
{
ss << "[" << m_repr << "]";
}
if (size() > 0)
{
ss << "(";
std::string sep;
for (auto const& child: m_children)
{
ss << sep << child->string();
sep = ",";
}
ss << ")";
}
return ss.str();
}
}

45
src/Node.hpp Normal file
View File

@ -0,0 +1,45 @@
#ifndef zn_NODE_HPP
#define zn_NODE_HPP
#include "common.hpp"
#include "Loc.hpp"
#define NODE_TYPES(G) \
G(NODE_MODULE), \
G(NODE_INT), \
G(NODE_OPAR), \
G(NODE_CPAR), \
G(NODE_IDENT), \
G(NODE_CALL),
namespace zn
{
ZN_MK_ENUM(NodeType, NODE_TYPES);
class Node
{
public:
explicit Node(NodeType type,
std::string const& repr,
Loc const& loc);
virtual ~Node();
NodeType type() const { return m_type; }
std::string repr() const { return m_repr; }
Loc loc() const { return m_loc; }
size_t size() const { return m_children.size(); }
void add_child(std::shared_ptr<Node> child);
std::shared_ptr<Node> child_at(size_t index) const;
std::string string() const;
private:
NodeType m_type;
std::string m_repr;
Loc m_loc;
std::vector<std::shared_ptr<Node>> m_children;
};
}
#endif

134
src/Parser.cpp Normal file
View File

@ -0,0 +1,134 @@
#include "Parser.hpp"
#include "src/Logger.hpp"
#include "src/Node.hpp"
namespace zn
{
/*explicit*/ Parser::Parser(Logger& logger)
: m_logger { logger }
{
}
/*virtual*/ Parser::~Parser()
{
}
std::shared_ptr<Node>
Parser::parse(std::vector<std::shared_ptr<Node>> tokens)
{
m_tokens = tokens;
m_cursor = 0;
return parse_module();
}
std::shared_ptr<Node> Parser::mk_node(NodeType type)
{
return std::make_shared<Node>(type, "", m_tokens[m_cursor]->loc());
}
bool Parser::type_is(std::vector<NodeType> types) const
{
if (types.size() + m_cursor > m_tokens.size())
{
return false;
}
for (size_t i=0; i<types.size(); i++)
{
if (types[i] != m_tokens[m_cursor + i]->type())
{
return false;
}
}
return true;
}
bool Parser::type_is(NodeType type) const
{
return type_is(std::vector<NodeType>{type});
}
std::shared_ptr<Node> Parser::consume(NodeType type)
{
if (!type_is(type))
{
std::stringstream ss;
ss << "expected '"
<< (NodeTypeStr[type] + strlen("NODE_"))
<< "', got '"
<< (NodeTypeStr[m_tokens[m_cursor]->type()] + strlen("NODE_"))
<< "'";
m_logger.log<syntax_error>(LOG_ERROR,
m_tokens[m_cursor]->loc(),
ss.str());
}
return consume();
}
std::shared_ptr<Node> Parser::consume()
{
m_cursor++;
return m_tokens[m_cursor - 1];
}
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())
{
node->add_child(parse_expr());
}
return node;
}
std::shared_ptr<Node> Parser::parse_expr()
{
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()
<< "'";
m_logger.log<syntax_error>(LOG_ERROR,
m_tokens[m_cursor]->loc(),
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;
}
}

39
src/Parser.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef zn_PARSER_HPP
#define zn_PARSER_HPP
#include "common.hpp"
#include "Logger.hpp"
#include "Node.hpp"
namespace zn
{
ZN_ERROR(syntax_error);
class Parser
{
public:
explicit Parser(Logger& logger);
virtual ~Parser();
std::shared_ptr<Node>
parse(std::vector<std::shared_ptr<Node>> tokens);
private:
Logger& m_logger;
std::vector<std::shared_ptr<Node>> m_tokens;
size_t m_cursor;
std::shared_ptr<Node> mk_node(NodeType type);
bool type_is(std::vector<NodeType> types) const;
bool type_is(NodeType type) const;
std::shared_ptr<Node> consume(NodeType type);
std::shared_ptr<Node> consume();
std::shared_ptr<Node> parse_module();
std::shared_ptr<Node> parse_expr();
std::shared_ptr<Node> parse_call();
};
}
#endif

68
src/Program.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "Program.hpp"
namespace zn
{
/*explicit*/ Program::Program()
{
}
/*virtual*/ Program::~Program()
{
}
void Program::append(Opcode opcode, int param)
{
m_instrs.push_back(Instr {opcode, param});
}
Instr Program::instr(size_t index) const
{
assert(index < size());
return m_instrs[index];
}
size_t Program::add_constant(std::shared_ptr<Constant> constant)
{
for (size_t i=0; i<const_size(); i++)
{
if (m_constant_pool[i]->equals(*constant))
{
return i;
}
}
m_constant_pool.push_back(constant);
return m_constant_pool.size() - 1;
}
std::shared_ptr<Constant> Program::constant(size_t index) const
{
assert(index < const_size());
return m_constant_pool[index];
}
std::string Program::string() const
{
std::stringstream ss;
ss << "======== Instructions ========\n";
for (size_t i=0; i<size(); i++)
{
ss << i
<< "\t"
<< (OpcodeStr[m_instrs[i].opcode] + strlen("OPCODE_"))
<< "\t" << (m_instrs[i].param == NO_PARAM ? ""
: std::to_string(m_instrs[i].param))
<< "\n";
}
ss << "======== Constant Pool ========\n";
for (size_t i=0; i<const_size(); i++)
{
ss << i << "\t" << m_constant_pool[i]->string() << "\n";
}
return ss.str();
}
}

50
src/Program.hpp Normal file
View File

@ -0,0 +1,50 @@
#ifndef zn_PROGRAM_HPP
#define zn_PROGRAM_HPP
#include "common.hpp"
#include "Constant.hpp"
#define NO_PARAM (-1)
#define OPCODES(G) \
G(OPCODE_LOAD_CONST), \
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
{
ZN_MK_ENUM(Opcode, OPCODES);
struct Instr {
Opcode opcode;
int param;
};
class Program
{
public:
explicit Program();
virtual ~Program();
size_t size() const { return m_instrs.size(); }
size_t const_size() const { return m_constant_pool.size(); }
void append(Opcode opcode, int param=NO_PARAM);
Instr instr(size_t index) const;
size_t add_constant(std::shared_ptr<Constant> constant);
std::shared_ptr<Constant> constant(size_t index) const;
std::string string() const;
private:
std::vector<Instr> m_instrs;
std::vector<std::shared_ptr<Constant>> m_constant_pool;
};
}
#endif

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

156
src/VM.cpp Normal file
View File

@ -0,0 +1,156 @@
#include "VM.hpp"
#include "src/Program.hpp"
namespace zn
{
/*explicit*/ VM::VM(Program& program)
{
m_pc = 0;
Frame f = {program, {}};
m_frames.push_back(f);
}
/*virtual*/ VM::~VM()
{
}
void VM::execute(Program& program)
{
m_pc = 0;
while (m_pc < program.size())
{
auto instr = program.instr(m_pc);
execute(program, instr.opcode, instr.param);
}
}
void VM::execute(Program& program, Opcode opcode, int param)
{
switch (opcode)
{
case OPCODE_LOAD_CONST: {
push(param);
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]
<< "'" << std::endl;
abort();
} break;
}
}
std::string VM::string() const
{
std::stringstream ss;
ss << "======== VM Stack ========\n";
size_t addr = 0;
for (int val: m_stack)
{
ss << addr << " " << val << "\n";
addr++;
}
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);
}
int VM::pop()
{
assert(m_stack.size() > 0);
int value = m_stack.back();
m_stack.pop_back();
return value;
}
}

49
src/VM.hpp Normal file
View File

@ -0,0 +1,49 @@
#ifndef zn_VM_HPP
#define zn_VM_HPP
#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(Program& program);
virtual ~VM();
void execute(Program& program);
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();
};
}
#endif

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

31
src/common.hpp Normal file
View File

@ -0,0 +1,31 @@
#ifndef zn_COMMON_HPP
#define zn_COMMON_HPP
#include <stdexcept>
#define ZN_GEN_ENUM(X) X
#define ZN_GEN_STRING(X) #X
#define ZN_MK_ENUM(PREFIX, ENUM) \
enum PREFIX { ENUM(ZN_GEN_ENUM) }; \
constexpr char const* PREFIX ## Str [] = { ENUM(ZN_GEN_STRING) }
#define ZN_ERROR(NAME) \
struct NAME : public std::runtime_error { \
NAME (std::string const& what) : std::runtime_error(what) {} \
}
#include <cassert>
#include <fstream>
#include <variant>
#include <iostream>
#include <functional>
#include <unordered_map>
#include <optional>
#include <vector>
#include <memory>
#include <string>
#include <cstring>
#include <filesystem>
#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

72
src/main.cpp Normal file
View File

@ -0,0 +1,72 @@
#include <iostream>
#include <getopt.h>
#include "Module.hpp"
int main(int argc, char** argv)
{
bool debug_mode = false;
while (true)
{
static struct option options[] = {
{"debug", no_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt_index;
int c = getopt_long(argc, argv, "hd", options, &opt_index);
if (c == -1) { break; }
switch (c)
{
case 'h': {
std::stringstream ss;
ss << "Usage: zarn [OPTIONS]... <source_file>" << "\n";
ss << "\n\t" << "[OPTIONS]" << "\n";
ss << "\t" << "-d, --debug"
<< "\t" << "show debug informations." << "\n";
ss << "\t" << "-h, --help"
<< "\t" << "show this message." << "\n";
std::cout << ss.str() << std::endl;
return 0;
} break;
case 'd': {
debug_mode = true;
} break;
default: break;
}
}
if (optind < argc)
{
zn::Logger logger;
zn::Module mod { logger };
if (debug_mode)
{
mod.load_from_file(argv[optind]);
std::cout << mod.string() << std::endl;
}
else
{
try
{
mod.load_from_file(argv[optind]);
}
catch(std::exception const& err)
{
std::cerr << err.what() << std::endl;
return -1;
}
}
}
return 0;
}

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

43
tests/Lexer.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <catch2/catch.hpp>
#include "../src/Lexer.hpp"
class LexerTest
{
public:
explicit LexerTest() {}
virtual ~LexerTest() {}
void test_next(zn::Lexer& lexer, std::string const& oracle)
{
auto node = lexer.try_next();
INFO("expected " << oracle << " got nullptr");
REQUIRE(nullptr != node);
REQUIRE(oracle == node->string());
}
protected:
zn::Logger m_logger;
zn::Loc m_loc {"tests/lexer"};
zn::Lexer m_lexer { m_logger, m_loc };
};
TEST_CASE_METHOD(LexerTest, "Lexer_int")
{
m_lexer.scan(" 3 -2 167 ");
test_next(m_lexer, "INT[3]");
test_next(m_lexer, "INT[-2]");
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());
}

37
tests/Parser.cpp Normal file
View File

@ -0,0 +1,37 @@
#include <catch2/catch.hpp>
#include "../src/Parser.hpp"
#include "../src/Lexer.hpp"
class ParserTest
{
public:
explicit ParserTest() {}
virtual ~ParserTest() {}
void test_parse(std::string const& oracle,
std::string const& source)
{
zn::Logger logger;
zn::Loc loc {"tests/parser"};
zn::Lexer lexer { logger, loc };
lexer.scan(source);
std::vector<std::shared_ptr<zn::Node>> tokens = lexer.all();
zn::Parser parser {logger};
auto node = parser.parse(tokens);
REQUIRE(oracle == node->string());
}
protected:
};
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)");
}

2
tests/main.cpp Normal file
View File

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>