Compare commits

...

3 Commits

Author SHA1 Message Date
bog e8e4906cfc ADD: foreign functions. 2023-09-10 00:03:28 +02:00
bog 149f866172 ADD: int literal. 2023-09-09 15:09:43 +02:00
bog dfd65e5fc3 ADD: project settings. 2023-09-09 13:02:52 +02:00
45 changed files with 1964 additions and 0 deletions

4
.gitignore vendored Normal file
View File

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

12
Makefile Normal file
View File

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

6
core/core.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "lib/Function.hpp"
#include "lib/Loader.hpp"
extern "C" void lib(jk::Loader& loader)
{
}

4
doc/grammar.bnf Normal file
View File

@ -0,0 +1,4 @@
PROG ::= FUNCALL*
EXPR ::= LITERAL | FUNCALL
FUNCALL ::= opar ident EXPR* cpar
LITERAL ::= int | ident

13
lib/Code.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "Code.hpp"
namespace jk
{
/*explicit*/ Code::Code(foreign_t foreign)
: m_foreign { foreign }
{
}
/*virtual*/ Code::~Code()
{
}
}

22
lib/Code.hpp Normal file
View File

@ -0,0 +1,22 @@
#ifndef jk_CODE_HPP
#define jk_CODE_HPP
#include "commons.hpp"
#include "Function.hpp"
namespace jk
{
class Code
{
public:
explicit Code(foreign_t foreign);
virtual ~Code();
foreign_t foreign() const { return m_foreign; }
private:
foreign_t m_foreign;
};
}
#endif

80
lib/Compiler.cpp Normal file
View File

@ -0,0 +1,80 @@
#include "Compiler.hpp"
#include "lib/Opcodes.hpp"
#include "Code.hpp"
namespace jk
{
/*explicit*/ Compiler::Compiler(std::shared_ptr<SymTable> sym,
Logger& logger)
: m_sym { sym }
, m_logger(logger)
{
}
/*virtual*/ Compiler::~Compiler()
{
}
void Compiler::compile(std::shared_ptr<Node> node,
std::shared_ptr<Program> program)
{
switch (node->type())
{
case NODE_PROG: {
for (size_t i=0; i<node->size(); i++)
{
compile(node->child(i).lock(), program);
}
} break;
case NODE_FUNCALL: {
for (size_t i=0; i<node->size(); i++)
{
compile(node->child(i).lock(), program);
}
program->push_instr(OPCODE_CALL, node->size() - 1);
} break;
case NODE_IDENT: {
std::string ident = node->repr();
auto sym = m_sym->find(ident);
if (!sym)
{
m_logger.log<compile_error>(LOG_ERROR, node->loc(),
std::string()
+ "'"
+ ident
+ "' is undefined");
}
OpcodeType op_load = OPCODE_LOAD;
if (sym->is_global)
{
op_load = OPCODE_LOAD_GLOBAL;
}
if (sym->type->type() == TYPE_FUNCTION)
{
program->push_instr(op_load, sym->addr);
}
} break;
case NODE_INT: {
auto value = Value::make_int(std::stoi(node->repr()));
size_t addr = program->push_constant(value);
program->push_instr(OPCODE_PUSH_CONST, addr);
} break;
default:
std::cerr << "cannot compile unknown node '"
<< NodeTypeStr[node->type()] << "'" << std::endl;
abort();
}
}
}

31
lib/Compiler.hpp Normal file
View File

@ -0,0 +1,31 @@
#ifndef jk_COMPILER_HPP
#define jk_COMPILER_HPP
#include "commons.hpp"
#include "SymTable.hpp"
#include "Logger.hpp"
#include "Node.hpp"
#include "Program.hpp"
#include "VM.hpp"
namespace jk
{
JK_ERROR(compile_error);
class Compiler
{
public:
explicit Compiler(std::shared_ptr<SymTable> sym,
Logger& logger);
virtual ~Compiler();
void compile(std::shared_ptr<Node> node,
std::shared_ptr<Program> program);
private:
std::shared_ptr<SymTable> m_sym;
Logger& m_logger;
};
}
#endif

26
lib/Factory.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "Factory.hpp"
namespace jk
{
/*explicit*/ Factory::Factory(Logger& logger, std::filesystem::path path)
: m_logger { logger }
, m_path { path }
{
}
/*virtual*/ Factory::~Factory()
{
}
std::shared_ptr<Lexer> Factory::make_lexer()
{
Loc loc {m_path, 1, 0};
return std::make_shared<Lexer>(m_logger, loc);
}
std::shared_ptr<Parser> Factory::make_parser()
{
auto lexer = make_lexer();
return std::make_shared<Parser>(m_logger, lexer);
}
}

26
lib/Factory.hpp Normal file
View File

@ -0,0 +1,26 @@
#ifndef jk_FACTORY_HPP
#define jk_FACTORY_HPP
#include "Lexer.hpp"
#include "Parser.hpp"
#include "Logger.hpp"
#include "commons.hpp"
namespace jk
{
class Factory
{
public:
explicit Factory(Logger& logger, std::filesystem::path path);
virtual ~Factory();
std::shared_ptr<Lexer> make_lexer();
std::shared_ptr<Parser> make_parser();
private:
Logger& m_logger;
std::filesystem::path m_path;
};
}
#endif

19
lib/Function.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "Function.hpp"
#include "Value.hpp"
namespace jk
{
/*explicit*/ Function::Function(foreign_t foreign)
: m_foreign { foreign }
{
}
/*virtual*/ Function::~Function()
{
}
value_t Function::call(std::vector<value_t> const& args)
{
return m_foreign(args);
}
}

26
lib/Function.hpp Normal file
View File

@ -0,0 +1,26 @@
#ifndef jk_FUNCTION_HPP
#define jk_FUNCTION_HPP
#include "commons.hpp"
namespace jk
{
class Value;
using value_t = std::shared_ptr<Value>;
using foreign_t = std::function<value_t(std::vector<value_t>)>;
class Function
{
public:
explicit Function(foreign_t foreign);
virtual ~Function();
value_t call(std::vector<value_t> const& args);
private:
foreign_t m_foreign;
};
}
#endif

212
lib/Lexer.cpp Normal file
View File

@ -0,0 +1,212 @@
#include "Lexer.hpp"
namespace jk
{
/*explicit*/ Lexer::Lexer(Logger& logger, Loc const& loc)
: m_logger { logger }
, m_loc { loc }
{
std::vector<std::tuple<NodeType, std::string, bool>> texts = {
{NODE_OPAR, "(", false},
{NODE_CPAR, ")", false}
};
for (auto text: texts)
{
m_scanners.push_back(std::bind(&Lexer::scan_text,
this,
std::get<0>(text),
std::get<1>(text),
std::get<2>(text)));
}
m_scanners.push_back(std::bind(&Lexer::scan_ident, this));
m_scanners.push_back(std::bind(&Lexer::scan_int, this));
}
/*virtual*/ Lexer::~Lexer()
{
}
void Lexer::scan(std::string const& source)
{
m_source = source;
m_cursor = 0;
}
std::shared_ptr<Node> Lexer::next()
{
skip_spaces();
while (more(m_cursor)
&& at(m_cursor) == '#')
{
while (more(m_cursor)
&& at(m_cursor) != '\n')
{
m_cursor++;
}
skip_spaces();
}
std::optional<ScanInfo> info;
for (auto scanner: m_scanners)
{
auto my_info = scanner();
if ((!info && my_info)
|| (info && my_info
&& my_info->cursor > info->cursor))
{
info = my_info;
}
}
if (info)
{
m_cursor = info->cursor;
return std::make_shared<Node>(info->type, info->repr, m_loc);
}
if (more(m_cursor))
{
std::string text;
while (more(m_cursor)
&& !std::isspace(at(m_cursor)))
{
text += at(m_cursor);
m_cursor++;
}
std::stringstream ss;
ss << "unknown text '" << text << "'";
m_logger.log<lexical_error>(LOG_ERROR, m_loc, ss.str());
}
return nullptr;
}
bool Lexer::more(size_t index) const
{
return index < m_source.size();
}
char Lexer::at(size_t index) const
{
assert(more(index));
return m_source[index];
}
void Lexer::skip_spaces()
{
while (more(m_cursor)
&& std::isspace(at(m_cursor)))
{
if (at(m_cursor) == '\n')
{
m_loc = Loc {
m_loc.path(),
m_loc.line() + 1,
m_loc.column()
};
}
m_cursor++;
}
}
std::optional<ScanInfo> Lexer::scan_int() const
{
size_t cursor = m_cursor;
std::string repr;
while (more(cursor)
&& std::isdigit(at(cursor)))
{
repr += at(cursor);
cursor++;
}
if (repr.empty() == false)
{
return ScanInfo {
cursor,
NODE_INT,
repr
};
}
return std::nullopt;
}
std::optional<ScanInfo> Lexer::scan_text(NodeType type,
std::string const& text,
bool has_value) const
{
if (m_cursor + text.size() > m_source.size())
{
return std::nullopt;
}
for (size_t i=0; i<text.size(); i++)
{
if (at(m_cursor + i) != text[i])
{
return std::nullopt;
}
}
return ScanInfo {
m_cursor + text.size(),
type,
has_value ? text : ""
};
}
std::optional<ScanInfo> Lexer::scan_ident() const
{
auto car = [](char c){
return std::isalpha(c)
|| c == '_'
|| c == '-'
|| c == '?'
|| c == '!'
|| c == '/';
};
auto cdr = [car](char c){
return car(c)
|| std::isdigit(c)
;
};
size_t cursor = m_cursor;
std::string repr;
if (!more(cursor)
|| !car(at(cursor)))
{
return std::nullopt;
}
repr += at(cursor);
cursor++;
while (more(cursor)
&& cdr(at(cursor)))
{
repr += at(cursor);
cursor++;
}
return ScanInfo {
cursor,
NODE_IDENT,
repr
};
}
}

51
lib/Lexer.hpp Normal file
View File

@ -0,0 +1,51 @@
#ifndef jk_LEXER_HPP
#define jk_LEXER_HPP
#include "commons.hpp"
#include "Logger.hpp"
#include "Node.hpp"
namespace jk
{
JK_ERROR(lexical_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();
Loc loc() const { return m_loc; }
void scan(std::string const& source);
std::shared_ptr<Node> next();
private:
Logger& m_logger;
Loc m_loc;
size_t m_cursor;
std::string m_source;
std::vector<scanner_t> m_scanners;
bool more(size_t index) const;
char at(size_t index) const;
void skip_spaces();
std::optional<ScanInfo> scan_int() const;
std::optional<ScanInfo> scan_text(NodeType type,
std::string const& text,
bool has_value) const;
std::optional<ScanInfo> scan_ident() const;
};
}
#endif

50
lib/Loader.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "Loader.hpp"
#include "lib/Function.hpp"
#include "lib/config.in.hpp"
#include <dlfcn.h>
namespace jk
{
/*explicit*/ Loader::Loader(std::shared_ptr<VM> vm,
std::shared_ptr<SymTable> sym)
: m_vm { vm }
, m_sym { sym }
{
}
/*virtual*/ Loader::~Loader()
{
}
void Loader::load()
{
for (auto entry: std::filesystem::directory_iterator(JOKO_LIBDIR))
{
if (entry.path().extension() == ".so")
{
void* handle = dlopen(entry.path().string().c_str(), RTLD_LAZY);
assert(handle);
typedef void(*func)(Loader&);
func f = (func) dlsym(handle, "lib");
assert(f);
f(*this);
}
}
}
void Loader::declare(std::string const& name, foreign_t body)
{
auto fun = std::make_shared<Function>(body);
auto fun_val = Value::make_function(fun);
size_t addr = m_vm->push_heap(fun_val);
auto ref = Value::make_ref(addr);
size_t global_addr = m_sym->declare_global(name,
fun_val->type().lock(),
Loc {"global", 1, 1});
m_vm->set_global(global_addr, ref);
}
}

27
lib/Loader.hpp Normal file
View File

@ -0,0 +1,27 @@
#ifndef jk_LOADER_HPP
#define jk_LOADER_HPP
#include "commons.hpp"
#include "VM.hpp"
#include "SymTable.hpp"
#include "Function.hpp"
namespace jk
{
class Loader
{
public:
explicit Loader(std::shared_ptr<VM> vm,
std::shared_ptr<SymTable> sym);
virtual ~Loader();
void load();
void declare(std::string const& name, foreign_t body);
private:
std::shared_ptr<VM> m_vm;
std::shared_ptr<SymTable> m_sym;
};
}
#endif

15
lib/Loc.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "Loc.hpp"
namespace jk
{
/*explicit*/ Loc::Loc(std::filesystem::path path, int line, int column)
: m_path { path }
, m_line { line }
, m_column { column }
{
}
/*virtual*/ Loc::~Loc()
{
}
}

25
lib/Loc.hpp Normal file
View File

@ -0,0 +1,25 @@
#ifndef jk_LOC_HPP
#define jk_LOC_HPP
#include "commons.hpp"
namespace jk
{
class Loc
{
public:
explicit Loc(std::filesystem::path path, int line, int column);
virtual ~Loc();
std::filesystem::path path() const { return m_path; }
int line() const { return m_line; }
int column() const { return m_column; }
private:
std::filesystem::path m_path;
int m_line;
int m_column;
};
}
#endif

12
lib/Logger.cpp Normal file
View File

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

39
lib/Logger.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef jk_LOGGER_HPP
#define jk_LOGGER_HPP
#include "commons.hpp"
#include "Loc.hpp"
#define LOG_TYPE(G) \
G(LOG_ERROR)
namespace jk
{
JK_ENUM(Log, LOG_TYPE);
class Logger
{
public:
explicit Logger();
virtual ~Logger();
template<typename T>
void log(LogType type, Loc const& loc, std::string const& what);
private:
};
template<typename T>
void Logger::log(LogType type, Loc const& loc, std::string const& what)
{
std::stringstream ss;
ss << loc.path().string() << ":" << loc.line();
ss << " " << (std::string(LogTypeStr[type])
.substr(std::string("LOG_").size()));
ss << " " << what;
throw T { ss.str() };
}
}
#endif

57
lib/Node.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "Node.hpp"
namespace jk
{
/*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)
{
m_children.push_back(child);
}
std::weak_ptr<Node> Node::child(size_t index) const
{
assert(index < size());
return m_children[index];
}
std::string Node::string() const
{
std::stringstream ss;
ss << std::string(NodeTypeStr[m_type])
.substr(std::string("NODE_").size());
if (m_repr.empty() == false)
{
ss << "[" << m_repr << "]";
}
if (size() > 0)
{
ss << "(";
std::string sep;
for (auto child: m_children)
{
ss << sep << child->string();
sep = ",";
}
ss << ")";
}
return ss.str();
}
}

44
lib/Node.hpp Normal file
View File

@ -0,0 +1,44 @@
#ifndef jk_NODE_HPP
#define jk_NODE_HPP
#include "commons.hpp"
#include "Loc.hpp"
#define NODE_TYPE(G) \
G(NODE_PROG), \
G(NODE_INT), \
G(NODE_OPAR), \
G(NODE_CPAR), \
G(NODE_IDENT), \
G(NODE_FUNCALL),
namespace jk
{
JK_ENUM(Node, NODE_TYPE);
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 const& loc() const { return m_loc; }
size_t size() const { return m_children.size(); }
void add_child(std::shared_ptr<Node> child);
std::weak_ptr<Node> child(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

19
lib/Opcodes.hpp Normal file
View File

@ -0,0 +1,19 @@
#ifndef jk_OPCODES_HPP
#define jk_OPCODES_HPP
#include "commons.hpp"
#define OPCODES(G) \
G(OPCODE_PUSH_CONST), \
G(OPCODE_CALL), \
G(OPCODE_LOAD), \
G(OPCODE_STORE), \
G(OPCODE_LOAD_GLOBAL), \
G(OPCODE_MK_FUNCTION)
namespace jk
{
JK_ENUM(Opcode, OPCODES);
}
#endif

142
lib/Parser.cpp Normal file
View File

@ -0,0 +1,142 @@
#include "Parser.hpp"
namespace jk
{
/*explicit*/ Parser::Parser(Logger& logger, std::shared_ptr<Lexer> lexer)
: m_logger { logger }
, m_lexer { lexer }
{
}
/*virtual*/ Parser::~Parser()
{
}
std::shared_ptr<Node> Parser::parse(std::string const& source)
{
std::shared_ptr<Node> tok;
m_cursor = 0;
m_lexer->scan(source);
while ( (tok=m_lexer->next()) )
{
m_tokens.push_back(tok);
}
if (m_tokens.empty())
{
return std::make_shared<Node>(NODE_PROG, "",
Loc {m_lexer->loc().path(), 1, 1});
}
return parse_prog();
}
std::shared_ptr<Node> Parser::current() const
{
assert(m_cursor < m_tokens.size());
return m_tokens[m_cursor];
}
Loc Parser::loc() const
{
return current()->loc();
}
std::shared_ptr<Node> Parser::consume()
{
assert(m_cursor < m_tokens.size());
auto tok = m_tokens[m_cursor];
m_cursor++;
return tok;
}
std::shared_ptr<Node> Parser::consume(NodeType type)
{
assert(m_cursor < m_tokens.size());
if (m_tokens[m_cursor]->type() != type)
{
std::stringstream ss;
ss << "expected '"
<< std::string(NodeTypeStr[type])
.substr(std::string("NODE_").size())
<< "', got '"
<< std::string(NodeTypeStr[m_tokens[m_cursor]->type()])
.substr(std::string("NODE_").size())
<< "'";
m_logger.log<syntax_error>(LOG_ERROR,
m_tokens[m_cursor]->loc(),
ss.str());
}
return consume();
}
bool Parser::type_is(NodeType type, int lookahead) const
{
if (m_cursor + lookahead >= m_tokens.size())
{
return false;
}
return m_tokens[m_cursor + lookahead]->type() == type;
}
std::shared_ptr<Node> Parser::parse_prog()
{
auto root = std::make_shared<Node>(NODE_PROG, "", loc());
while (m_cursor < m_tokens.size())
{
root->add_child(parse_funcall());
}
return root;
}
std::shared_ptr<Node> Parser::parse_expr()
{
if (type_is(NODE_OPAR))
{
return parse_funcall();
}
return parse_literal();
}
std::shared_ptr<Node> Parser::parse_funcall()
{
auto root = std::make_shared<Node>(NODE_FUNCALL, "", loc());
consume(NODE_OPAR);
while (!type_is(NODE_CPAR))
{
root->add_child(parse_expr());
}
consume(NODE_CPAR);
return root;
}
std::shared_ptr<Node> Parser::parse_literal()
{
if (type_is(NODE_INT)
|| type_is(NODE_IDENT))
{
return consume();
}
m_logger.log<syntax_error>(LOG_ERROR,
loc(),
std::string()
+ "unexpected token '"
+ current()->repr()
+ "'");
abort();
}
}

39
lib/Parser.hpp Normal file
View File

@ -0,0 +1,39 @@
#ifndef jk_PARSER_HPP
#define jk_PARSER_HPP
#include "commons.hpp"
#include "Logger.hpp"
#include "Lexer.hpp"
namespace jk
{
JK_ERROR(syntax_error);
class Parser
{
public:
explicit Parser(Logger& logger, std::shared_ptr<Lexer> lexer);
virtual ~Parser();
std::shared_ptr<Node> parse(std::string const& source);
private:
Logger& m_logger;
std::shared_ptr<Lexer> m_lexer;
std::vector<std::shared_ptr<Node>> m_tokens;
size_t m_cursor;
std::shared_ptr<Node> current() const;
Loc loc() const;
std::shared_ptr<Node> consume();
std::shared_ptr<Node> consume(NodeType type);
bool type_is(NodeType type, int lookahead=0) const;
std::shared_ptr<Node> parse_prog();
std::shared_ptr<Node> parse_expr();
std::shared_ptr<Node> parse_funcall();
std::shared_ptr<Node> parse_literal();
};
}
#endif

61
lib/Program.cpp Normal file
View File

@ -0,0 +1,61 @@
#include "Program.hpp"
namespace jk
{
/*explicit*/ Program::Program()
{
}
/*virtual*/ Program::~Program()
{
}
Instr Program::get(size_t index) const
{
assert(index < size());
return m_instrs[index];
}
void Program::push_instr(OpcodeType opcode,
std::optional<param_t> param /*= std::nullopt*/)
{
m_instrs.push_back(Instr { opcode, param });
}
std::string Program::string() const
{
std::stringstream ss;
size_t addr = 0;
for (auto const& instr: m_instrs)
{
ss << addr << "\t"
<< std::string(OpcodeTypeStr[instr.opcode])
.substr(std::string("OPCODE_").size());
if (instr.param)
{
ss << "\t" << *instr.param;
}
ss << std::endl;
addr++;
}
return ss.str();
}
size_t Program::push_constant(std::shared_ptr<Value> value)
{
m_constants.push_back(value);
return m_constants.size() - 1;
}
std::shared_ptr<Value> Program::constant(size_t index) const
{
assert(index < m_constants.size());
return m_constants[index];
}
}

41
lib/Program.hpp Normal file
View File

@ -0,0 +1,41 @@
#ifndef jk_PROGRAM_HPP
#define jk_PROGRAM_HPP
#include "commons.hpp"
#include "Opcodes.hpp"
#include "Value.hpp"
namespace jk
{
using param_t = size_t;
struct Instr {
OpcodeType opcode;
std::optional<param_t> param;
};
class Program
{
public:
explicit Program();
virtual ~Program();
size_t size() const { return m_instrs.size(); }
Instr get(size_t index) const;
void push_instr(OpcodeType opcode,
std::optional<param_t> param = std::nullopt);
size_t push_constant(std::shared_ptr<Value> value);
std::shared_ptr<Value> constant(size_t index) const;
std::string string() const;
private:
std::vector<Instr> m_instrs;
std::vector<std::shared_ptr<Value>> m_constants;
};
}
#endif

37
lib/StaticPass.cpp Normal file
View File

@ -0,0 +1,37 @@
#include "StaticPass.hpp"
namespace jk
{
/*explicit*/ StaticPass::StaticPass(std::shared_ptr<SymTable> sym,
Logger& logger)
: m_sym { sym }
, m_logger { logger }
{
}
/*virtual*/ StaticPass::~StaticPass()
{
}
void StaticPass::pass(std::shared_ptr<Node> node)
{
switch (node->type())
{
case NODE_PROG: {
for (size_t i=0; i<node->size(); i++)
{
pass(node->child(i).lock());
}
} break;
case NODE_FUNCALL: {
} break;
default:
std::cerr << "cannot static_pass unknown node '"
<< NodeTypeStr[node->type()] << "'" << std::endl;
abort();
}
}
}

24
lib/StaticPass.hpp Normal file
View File

@ -0,0 +1,24 @@
#ifndef jk_STATICPASS_HPP
#define jk_STATICPASS_HPP
#include "commons.hpp"
#include "SymTable.hpp"
#include "Node.hpp"
namespace jk
{
class StaticPass
{
public:
explicit StaticPass(std::shared_ptr<SymTable> sym, Logger& logger);
virtual ~StaticPass();
void pass(std::shared_ptr<Node> node);
private:
std::shared_ptr<SymTable> m_sym;
Logger& m_logger;
};
}
#endif

64
lib/SymTable.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "SymTable.hpp"
namespace jk
{
/*explicit*/ SymTable::SymTable(Logger& logger)
: m_logger { logger }
{
}
/*virtual*/ SymTable::~SymTable()
{
}
size_t
SymTable::declare(std::string const& name,
std::shared_ptr<Type> type,
Loc const& loc)
{
if (find(name))
{
m_logger.log<symbolic_error>(LOG_ERROR,
loc,
std::string()
+ "'"
+ name
+ "' is already defined");
}
size_t id = m_syms.size();
m_syms.push_back(Sym {
name,
type,
loc,
false,
id
});
return id;
}
size_t
SymTable::declare_global(std::string const& name,
std::shared_ptr<Type> type,
Loc const& loc)
{
size_t addr = declare(name, type, loc);
m_syms[addr].is_global = true;
return addr;
}
std::optional<Sym> SymTable::find(std::string const& name) const
{
for (auto const& sym: m_syms)
{
if (sym.name == name)
{
return sym;
}
}
return std::nullopt;
}
}

43
lib/SymTable.hpp Normal file
View File

@ -0,0 +1,43 @@
#ifndef jk_SYMTABLE_HPP
#define jk_SYMTABLE_HPP
#include "commons.hpp"
#include "Type.hpp"
#include "Logger.hpp"
namespace jk
{
struct Sym {
std::string name;
std::shared_ptr<Type> type;
Loc loc;
bool is_global = false;
size_t addr;
};
JK_ERROR(symbolic_error);
class SymTable
{
public:
explicit SymTable(Logger& logger);
virtual ~SymTable();
size_t declare(std::string const& name,
std::shared_ptr<Type> type,
Loc const& loc);
size_t declare_global(std::string const& name,
std::shared_ptr<Type> type,
Loc const& loc);
std::optional<Sym> find(std::string const& name) const;
private:
Logger& m_logger;
std::vector<Sym> m_syms;
size_t m_addr = 0;
};
}
#endif

13
lib/Type.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "Type.hpp"
namespace jk
{
/*explicit*/ Type::Type(TypeType type)
: m_type { type }
{
}
/*virtual*/ Type::~Type()
{
}
}

30
lib/Type.hpp Normal file
View File

@ -0,0 +1,30 @@
#ifndef jk_TYPE_HPP
#define jk_TYPE_HPP
#include "commons.hpp"
#define TYPE_TYPE(G) \
G(TYPE_NIL), \
G(TYPE_INT), \
G(TYPE_FUNCTION), \
G(TYPE_CODE), \
G(TYPE_REF)
namespace jk
{
JK_ENUM(Type, TYPE_TYPE);
class Type
{
public:
explicit Type(TypeType type);
virtual ~Type();
TypeType type() const { return m_type; }
private:
TypeType m_type;
};
}
#endif

141
lib/VM.cpp Normal file
View File

@ -0,0 +1,141 @@
#include "VM.hpp"
#include "Function.hpp"
#include "Code.hpp"
#include "lib/Opcodes.hpp"
namespace jk
{
/*explicit*/ VM::VM()
{
}
/*virtual*/ VM::~VM()
{
}
void VM::execute(std::shared_ptr<Program> program)
{
Frame frame;
frame.program = program;
m_frames.push_back(frame);
while (m_pc < m_frames.back().program->size())
{
Instr instr = m_frames.back().program->get(m_pc);
switch (instr.opcode)
{
case OPCODE_MK_FUNCTION: {
auto code = program->constant(pop());
auto function = std::make_shared<Function>
(code->as_code()->foreign());
auto value = Value::make_function(function);
size_t addr = push_heap(value);
auto ref = Value::make_ref(addr);
push(program->push_constant(ref));
m_pc++;
} break;
case OPCODE_PUSH_CONST: {
push(*instr.param);
m_pc++;
} break;
case OPCODE_LOAD: {
auto value = m_frames.back().locals[*instr.param];
push(program->push_constant(value));
m_pc++;
} break;
case OPCODE_STORE: {
auto idx = pop();
m_frames.back().locals[*instr.param] =
program->constant(idx);
m_pc++;
} break;
case OPCODE_LOAD_GLOBAL: {
auto value = m_globals[*instr.param];
push(program->push_constant(value));
m_pc++;
} break;
case OPCODE_CALL: {
std::vector<std::shared_ptr<Value>> args;
for (size_t i=0; i<*instr.param; i++)
{
args.insert(std::begin(args), program->constant(pop()));
}
auto ref_val = program->constant(pop());
auto ref = ref_val->as_ref();
auto fun_val = heap(ref);
auto fun = fun_val->as_function();
auto result = fun->call(args);
push(program->push_constant(result));
m_pc++;
} break;
default:
std::cerr << "cannot execute unknown opcode "
<< OpcodeTypeStr[instr.opcode]
<< std::endl;
abort();
}
}
}
std::string VM::string() const
{
std::stringstream ss;
for (size_t i=0; i<m_stack.size(); i++)
{
ss << i << "\t" << m_stack[i] << std::endl;
}
return ss.str();
}
size_t VM::push_heap(std::shared_ptr<Value> value)
{
m_heap.push_back(value);
return m_heap.size() - 1;
}
std::shared_ptr<Value> VM::heap(size_t addr)
{
assert(addr < m_heap.size());
return m_heap[addr];
}
void VM::set_global(size_t addr, std::shared_ptr<Value> value)
{
m_globals[addr] = value;
}
void VM::push(param_t param)
{
m_stack.push_back(param);
}
param_t VM::pop()
{
auto param = m_stack.back();
m_stack.pop_back();
return param;
}
}

41
lib/VM.hpp Normal file
View File

@ -0,0 +1,41 @@
#ifndef jk_VM_HPP
#define jk_VM_HPP
#include "commons.hpp"
#include "Program.hpp"
namespace jk
{
struct Frame {
std::shared_ptr<Program> program;
std::unordered_map<int, std::shared_ptr<Value>> locals;
};
class VM
{
public:
explicit VM();
virtual ~VM();
void execute(std::shared_ptr<Program> program);
std::string string() const;
size_t push_heap(std::shared_ptr<Value> value);
std::shared_ptr<Value> heap(size_t addr);
void set_global(size_t addr, std::shared_ptr<Value> value);
private:
size_t m_pc = 0;
std::vector<Frame> m_frames;
std::vector<param_t> m_stack;
std::vector<std::shared_ptr<Value>> m_heap;
std::unordered_map<size_t, std::shared_ptr<Value>> m_globals;
void push(param_t param);
param_t pop();
};
}
#endif

80
lib/Value.cpp Normal file
View File

@ -0,0 +1,80 @@
#include "Value.hpp"
#include "Type.hpp"
#include "Function.hpp"
#include "Code.hpp"
namespace jk
{
/*static*/ std::shared_ptr<Value> Value::make_nil()
{
auto value = std::make_shared<Value>();
value->m_type = std::make_shared<Type>(TYPE_NIL);
return value;
}
/*static*/ std::shared_ptr<Value> Value::make_int(int val)
{
auto value = std::make_shared<Value>();
value->m_type = std::make_shared<Type>(TYPE_INT);
value->m_int_val = val;
return value;
}
/*static*/ std::shared_ptr<Value>
Value::make_function(std::shared_ptr<Function> val)
{
auto value = std::make_shared<Value>();
value->m_type = std::make_shared<Type>(TYPE_FUNCTION);
value->m_function_val = val;
return value;
}
/*static*/ std::shared_ptr<Value>
Value::make_code(std::shared_ptr<Code> val)
{
auto value = std::make_shared<Value>();
value->m_type = std::make_shared<Type>(TYPE_CODE);
value->m_code_val = val;
return value;
}
/*static*/ std::shared_ptr<Value>
Value::make_ref(size_t val)
{
auto value = std::make_shared<Value>();
value->m_type = std::make_shared<Type>(TYPE_REF);
value->m_ref_val = val;
return value;
}
std::shared_ptr<Function> Value::as_function() const
{
return m_function_val;
}
std::shared_ptr<Code> Value::as_code() const
{
return m_code_val;
}
std::weak_ptr<Type> Value::type() const
{
return m_type;
}
std::string Value::string() const
{
switch (m_type->type())
{
case TYPE_NIL: return "<nil>";
case TYPE_INT: return std::to_string(*m_int_val);
default:
std::cerr << "cannot stringify value '"
<< TypeTypeStr[m_type->type()]
<< "'"
<< std::endl;
abort();
}
}
}

44
lib/Value.hpp Normal file
View File

@ -0,0 +1,44 @@
#ifndef jk_VALUE_HPP
#define jk_VALUE_HPP
#include "commons.hpp"
namespace jk
{
class Type;
class Function;
class Code;
class Value
{
public:
static std::shared_ptr<Value> make_nil();
static std::shared_ptr<Value> make_int(int val);
static std::shared_ptr<Value> make_function(std::shared_ptr<Function>
val);
static std::shared_ptr<Value> make_code(std::shared_ptr<Code> val);
static std::shared_ptr<Value> make_ref(size_t val);
explicit Value() = default;
virtual ~Value() = default;
int as_int() const { return *m_int_val; }
size_t as_ref() const { return *m_ref_val; }
std::shared_ptr<Function> as_function() const;
std::shared_ptr<Code> as_code() const;
std::weak_ptr<Type> type() const;
std::string string() const;
private:
std::shared_ptr<Type> m_type;
std::optional<int> m_int_val;
std::shared_ptr<Function> m_function_val;
std::shared_ptr<Code> m_code_val;
std::optional<size_t> m_ref_val;
};
}
#endif

19
lib/commons.hpp Normal file
View File

@ -0,0 +1,19 @@
#ifndef jk_COMMONS_HPP
#define jk_COMMONS_HPP
#include <cassert>
#include <functional>
#include <optional>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <memory>
#include <string>
#include <sstream>
#include <fstream>
#include <filesystem>
#include "mutils.hpp"
#include "config.hpp"
#endif

9
lib/config.in.hpp Normal file
View File

@ -0,0 +1,9 @@
#ifndef jk_CONFIG_IN_HPP
#define jk_CONFIG_IN_HPP
#include <filesystem>
#define JOKO_VERSION "@version@"
#define JOKO_LIBDIR std::filesystem::path("@libdir@")
#endif

18
lib/mutils.hpp Normal file
View File

@ -0,0 +1,18 @@
#ifndef jk_MUTILS_HPP
#define jk_MUTILS_HPP
#include <stdexcept>
#define ENUM_ENUM(X) X
#define ENUM_STRING(X) #X
#define JK_ENUM(PREFIX, DECL) \
enum PREFIX ## Type { DECL(ENUM_ENUM) }; \
constexpr char const* PREFIX ## TypeStr [] { DECL(ENUM_STRING) }
#define JK_ERROR(NAME) \
struct NAME : public std::runtime_error { \
NAME (std::string const& what): std::runtime_error {what} {} \
}
#endif

77
meson.build Normal file
View File

@ -0,0 +1,77 @@
project('joko',
'cpp',
version: '0.0.0',
default_options: [
'warning_level=3',
'cpp_std=c++17'
])
joko_libdir = get_option('prefix') / get_option('libdir') / 'joko'
conf = configuration_data()
conf.set('version', meson.project_version())
conf.set('libdir', joko_libdir)
configure_file(
input: 'lib/config.in.hpp',
output: 'config.hpp',
configuration: conf
)
joko_lib = static_library(
'joko',
sources: [
'lib/Node.cpp',
'lib/Loc.cpp',
'lib/Factory.cpp',
'lib/Logger.cpp',
'lib/Lexer.cpp',
'lib/Parser.cpp',
'lib/Type.cpp',
'lib/Value.cpp',
'lib/Function.cpp',
'lib/Code.cpp',
'lib/SymTable.cpp',
'lib/StaticPass.cpp',
'lib/Compiler.cpp',
'lib/Program.cpp',
'lib/VM.cpp',
'lib/Loader.cpp',
],
dependencies: [
])
joko_dep = declare_dependency(link_with: joko_lib)
joko_core_lib = shared_library('joko-core',
sources: [
'core/core.cpp'
],
dependencies: [
joko_dep
],
install: true,
install_dir: joko_libdir)
executable('joko',
sources: [
'src/main.cpp',
],
dependencies: [
joko_dep
],
install: true)
executable('joko-tests',
sources: [
'tests/main.cpp',
'tests/Lexer.cpp',
'tests/Parser.cpp',
],
dependencies: [
joko_dep,
dependency('catch2')
])

116
src/main.cpp Normal file
View File

@ -0,0 +1,116 @@
#include <iostream>
#include <getopt.h>
#include "config.hpp"
#include "../lib/Lexer.hpp"
#include "../lib/Parser.hpp"
#include "../lib/SymTable.hpp"
#include "../lib/StaticPass.hpp"
#include "../lib/Compiler.hpp"
#include "../lib/VM.hpp"
#include "lib/Value.hpp"
#include "../lib/Loader.hpp"
int main(int argc, char** argv)
{
int index;
bool debug_mode = false;
struct option options[] = {
{"debug", no_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
int c = getopt_long(argc, argv, "dhv", options, &index);
switch (c)
{
case 'd': debug_mode = true; break;
case 'h': {
std::cout << "Usage: joko [OPTIONS] source_file" << std::endl;
std::cout << "OPTIONS" << std::endl;
std::cout << "\t" << "-h, --help"
<< "\t" << "show this message." << std::endl;
std::cout << "\t" << "-v, --version"
<< "\t" << "show joko version." << std::endl;
return 0;
} break;
case 'v': {
std::cout << "--- Joko Programming Language ---" << std::endl;
std::cout << "[License]: " << "GPL v3 (see LICENSE)" << std::endl;
std::cout << "[Version]: " << JOKO_VERSION << std::endl;
std::cout << "[Lib directory]: " << JOKO_LIBDIR << std::endl;
return 0;
} break;
}
if (optind < argc)
{
jk::Logger logger;
jk::Loc loc {argv[optind], 1, 1};
std::ifstream file { argv[optind] };
if (!file)
{
std::stringstream ss;
ss << "cannot find file '" << argv[optind] << "'";
logger.log<std::runtime_error>(jk::LOG_ERROR, loc, ss.str());
}
std::string source;
std::string line;
while (std::getline(file, line))
{
source += line + (file.eof() ? "" : "\n");
}
auto lexer = std::make_shared<jk::Lexer>(logger, loc);
auto parser = std::make_shared<jk::Parser>(logger, lexer);
auto ast = parser->parse(source);
if (debug_mode)
{
std::cout << "--- ast ---" << std::endl;
std::cout << ast->string() << std::endl;
}
auto sym = std::make_shared<jk::SymTable>(logger);
auto static_pass = std::make_shared<jk::StaticPass>(sym, logger);
static_pass->pass(ast);
auto compiler = std::make_shared<jk::Compiler>(sym, logger);
auto program = std::make_shared<jk::Program>();
auto vm = std::make_shared<jk::VM>();
auto loader = std::make_shared<jk::Loader>(vm, sym);
loader->load();
compiler->compile(ast, program);
if (debug_mode)
{
std::cout << "--- program ---" << std::endl;
std::cout << program->string() << std::endl;
}
vm->execute(program);
if (debug_mode)
{
std::cout << "--- stack ---" << std::endl;
std::cout << vm->string() << std::endl;
}
return 0;
}
return 0;
}

67
tests/Lexer.cpp Normal file
View File

@ -0,0 +1,67 @@
#include <catch2/catch.hpp>
#include "../lib/Lexer.hpp"
#include "../lib/Factory.hpp"
class LexerTest
{
public:
explicit LexerTest() {}
virtual ~LexerTest() {}
void test_next(jk::Lexer& lexer, std::string const& oracle)
{
auto token = lexer.next();
REQUIRE(token);
REQUIRE(oracle == token->string());
}
void test_end(jk::Lexer& lexer)
{
auto token = lexer.next();
REQUIRE(!token);
}
protected:
jk::Logger m_logger;
};
TEST_CASE_METHOD(LexerTest, "Lexer_int")
{
auto lexer = jk::Factory(m_logger, "tests/lexer").make_lexer();
lexer->scan("4 128 333");
test_next(*lexer, "INT[4]");
test_next(*lexer, "INT[128]");
test_next(*lexer, "INT[333]");
test_end(*lexer);
}
TEST_CASE_METHOD(LexerTest, "Lexer_comments")
{
auto lexer = jk::Factory(m_logger, "tests/lexer").make_lexer();
lexer->scan("4 # 128 \n 333");
test_next(*lexer, "INT[4]");
test_next(*lexer, "INT[333]");
test_end(*lexer);
}
TEST_CASE_METHOD(LexerTest, "Lexer_error")
{
auto lexer = jk::Factory(m_logger, "tests/lexer").make_lexer();
lexer->scan(" § ");
REQUIRE_THROWS_AS(lexer->next(), jk::lexical_error);
}
TEST_CASE_METHOD(LexerTest, "Lexer_funcall")
{
auto lexer = jk::Factory(m_logger, "tests/lexer").make_lexer();
lexer->scan(" )( salut salut! salut? _salut -salut salut/monde");
test_next(*lexer, "CPAR");
test_next(*lexer, "OPAR");
test_next(*lexer, "IDENT[salut]");
test_next(*lexer, "IDENT[salut!]");
test_next(*lexer, "IDENT[salut?]");
test_next(*lexer, "IDENT[_salut]");
test_next(*lexer, "IDENT[-salut]");
test_next(*lexer, "IDENT[salut/monde]");
test_end(*lexer);
}

36
tests/Parser.cpp Normal file
View File

@ -0,0 +1,36 @@
#include <catch2/catch.hpp>
#include "../lib/Parser.hpp"
#include "../lib/Factory.hpp"
class ParserTest
{
public:
explicit ParserTest() {}
virtual ~ParserTest() {}
void test_parser(std::string const& oracle, std::string const& source)
{
auto parser = jk::Factory(m_logger, "tests/parser").make_parser();
auto root = parser->parse(source);
REQUIRE(oracle == root->string());
}
protected:
jk::Logger m_logger;
};
TEST_CASE_METHOD(ParserTest, "Parser_funcall")
{
test_parser("PROG",
" ");
test_parser("PROG(FUNCALL(IDENT[hello]))",
" (hello) ");
test_parser("PROG(FUNCALL(IDENT[hello],INT[1],INT[2]))",
" (hello 1 2) ");
test_parser("PROG(FUNCALL(IDENT[hello],INT[1],INT[2],IDENT[bim!]))",
" (hello 1 2 bim!) ");
}

2
tests/main.cpp Normal file
View File

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