ADD: bool literals.

main
bog 2023-09-11 01:05:29 +02:00
parent b188eba63a
commit f001a50d38
31 changed files with 1297 additions and 0 deletions

4
.gitignore vendored Normal file
View File

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

11
Makefile Normal file
View File

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

2
doc/grammar.bnf Normal file
View File

@ -0,0 +1,2 @@
MODULE ::= EXPR*
EXPR ::= bool

49
meson.build Normal file
View File

@ -0,0 +1,49 @@
project('grino',
'cpp',
version: '0.0.0',
default_options: [
'warning_level=3',
'cpp_std=c++17'
])
conf = configuration_data()
conf.set('version', meson.project_version())
configure_file(input: 'src/config.in.hpp',
output: 'config.hpp',
configuration: conf)
grino_src = static_library('grino',
sources: [
'src/Compiler.cpp',
'src/Lexer.cpp',
'src/Loc.cpp',
'src/Logger.cpp',
'src/Node.cpp',
'src/Parser.cpp',
'src/Program.cpp',
'src/VM.cpp',
'src/Value.cpp',
])
grino_dep = declare_dependency(link_with: grino_src)
executable('grino',
sources: [
'src/main.cpp'
],
dependencies: [
grino_dep
],
install: true)
executable('grino-tests',
sources: [
'tests/main.cpp',
'tests/Lexer.cpp',
'tests/Parser.cpp',
],
dependencies: [
grino_dep,
dependency('catch2')
])

42
src/Compiler.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "Compiler.hpp"
#include "Program.hpp"
namespace grino
{
/*explicit*/ Compiler::Compiler(Logger& logger)
: m_logger { logger }
{
}
/*virtual*/ Compiler::~Compiler()
{
}
void Compiler::compile(std::shared_ptr<Node> node,
Program& program)
{
switch (node->type())
{
case NODE_MODULE: {
for (size_t i=0; i<node->size(); i++)
{
compile(node->child(i).lock(), program);
}
} break;
case NODE_BOOL: {
std::string repr = node->repr();
auto value = Value::make_bool(repr == "true");
program.push_instr(OPCODE_LOAD_CONST,
program.push_constant(value));
} break;
default:
std::cerr << "cannot compile node '"
<< GRINO_TRIM(NodeTypeStr[node->type()], "NODE_")
<< "'" << std::endl;
abort();
}
}
}

26
src/Compiler.hpp Normal file
View File

@ -0,0 +1,26 @@
#ifndef grino_COMPILER_HPP
#define grino_COMPILER_HPP
#include "commons.hpp"
#include "Logger.hpp"
#include "Node.hpp"
namespace grino
{
class Program;
class Compiler
{
public:
explicit Compiler(Logger& logger);
virtual ~Compiler();
void compile(std::shared_ptr<Node> node,
Program& program);
private:
Logger& m_logger;
};
}
#endif

181
src/Lexer.cpp Normal file
View File

@ -0,0 +1,181 @@
#include "Lexer.hpp"
#include "src/Node.hpp"
namespace grino
{
/*explicit*/ Lexer::Lexer(Logger& logger, std::filesystem::path source_path)
: m_logger { logger }
, m_loc {source_path, 1}
{
add_keyword(NODE_BOOL, "true", true);
add_keyword(NODE_BOOL, "false", true);
}
/*virtual*/ Lexer::~Lexer()
{
}
void Lexer::scan(std::string const& source)
{
m_cursor = 0;
m_source = source;
}
std::shared_ptr<Node> Lexer::next()
{
std::optional<ScanInfo> info;
skip_spaces();
for (auto const& scanner: m_scanners)
{
auto my_info = scanner();
if ((my_info && !info)
|| (my_info && info && my_info->cursor > info->cursor))
{
info = my_info;
}
}
if (info)
{
m_cursor = info->cursor;
return std::make_shared<Node>(info->node, info->repr, m_loc);
}
if (has_more(m_cursor))
{
std::string text;
while (has_more(m_cursor) && !is_sep(m_cursor))
{
text += at(m_cursor);
m_cursor++;
}
std::stringstream ss;
ss << "unknown symbol '" << text << "'";
m_logger.log<lexical_error>(LOG_ERROR, m_loc, ss.str());
}
return nullptr;
}
void Lexer::skip_spaces()
{
while (has_more(m_cursor)
&& std::isspace(at(m_cursor)))
{
if (at(m_cursor) == '\n')
{
m_loc = Loc {m_loc.path(), m_loc.line() + 1};
}
m_cursor++;
}
}
bool Lexer::is_sep(size_t index) const
{
if (index >= m_source.size())
{
return true;
}
char c = m_source[index];
if (std::isspace(c)) { return true; }
auto itr = std::find(std::begin(m_separators),
std::end(m_separators),
c);
return itr != std::end(m_separators);
}
bool Lexer::has_more(size_t index) const
{
return index < m_source.size();
}
char Lexer::at(size_t index) const
{
assert(has_more(index));
return m_source[index];
}
void Lexer::add_text(NodeType type,
std::string const& text,
bool has_value /* = false */)
{
m_scanners.push_back(std::bind(&Lexer::scan_text,
this, type,
text, has_value));
if (text.size() == 1)
{
}
}
void Lexer::add_keyword(NodeType type,
std::string const& text,
bool has_value /* = false */)
{
m_scanners.push_back(std::bind(&Lexer::scan_keyword,
this, type,
text, has_value));
}
std::optional<ScanInfo> Lexer::scan_text(NodeType type,
std::string const& text,
bool has_value)
{
if (!has_more(m_cursor + text.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_keyword(NodeType type,
std::string const& text,
bool has_value)
{
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;
}
}
if (m_cursor + text.size() < m_source.size()
&& !is_sep(m_cursor + text.size()))
{
return std::nullopt;
}
return ScanInfo {
m_cursor + text.size(),
type,
has_value ? text : ""
};
}
}

63
src/Lexer.hpp Normal file
View File

@ -0,0 +1,63 @@
#ifndef grino_LEXER_HPP
#define grino_LEXER_HPP
#include "commons.hpp"
#include "Node.hpp"
#include "Logger.hpp"
namespace grino
{
GRINO_ERROR(lexical_error);
struct ScanInfo {
size_t cursor;
NodeType node;
std::string repr;
};
using scanner_t = std::function<std::optional<ScanInfo>()>;
class Lexer
{
public:
explicit Lexer(Logger& logger, std::filesystem::path source_path);
virtual ~Lexer();
void scan(std::string const& source);
std::shared_ptr<Node> next();
private:
Logger& m_logger;
Loc m_loc;
std::vector<scanner_t> m_scanners;
std::vector<char> m_separators;
std::string m_source;
size_t m_cursor;
void skip_spaces();
bool is_sep(size_t index) const;
bool has_more(size_t index) const;
char at(size_t index) const;
void add_text(NodeType type,
std::string const& text,
bool has_value = false);
void add_keyword(NodeType type,
std::string const& text,
bool has_value = false);
std::optional<ScanInfo> scan_text(NodeType type,
std::string const& text,
bool has_value);
std::optional<ScanInfo> scan_keyword(NodeType type,
std::string const& text,
bool has_value);
};
}
#endif

14
src/Loc.cpp Normal file
View File

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

23
src/Loc.hpp Normal file
View File

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

12
src/Logger.cpp Normal file
View File

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

38
src/Logger.hpp Normal file
View File

@ -0,0 +1,38 @@
#ifndef grino_LOGGER_HPP
#define grino_LOGGER_HPP
#include "commons.hpp"
#include "src/mutils.hpp"
#include "Loc.hpp"
#define LOG_TYPE(G) \
G(LOG_ERROR)
namespace grino
{
GRINO_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 << "[" << GRINO_TRIM(LogTypeStr[type], "LOG_") << "] ";
ss << what;
throw T { ss.str() };
}
}
#endif

55
src/Node.cpp Normal file
View File

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

42
src/Node.hpp Normal file
View File

@ -0,0 +1,42 @@
#ifndef grino_NODE_HPP
#define grino_NODE_HPP
#include "commons.hpp"
#include "Loc.hpp"
#define NODE_TYPE(G) \
G(NODE_MODULE), \
G(NODE_BOOL)
namespace grino
{
GRINO_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 loc() const { return m_loc; }
size_t size() const { return m_children.size(); }
void add_child(std::shared_ptr<Node> node);
std::weak_ptr<Node> child(size_t index);
std::string string() const;
private:
NodeType m_type;
std::string m_repr;
Loc m_loc;
std::vector<std::shared_ptr<Node>> m_children;
};
}
#endif

129
src/Parser.cpp Normal file
View File

@ -0,0 +1,129 @@
#include "Parser.hpp"
#include "src/mutils.hpp"
namespace grino
{
/*explicit*/ Parser::Parser(Logger& logger, 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> token;
m_lexer.scan(source);
while ( (token = m_lexer.next()) )
{
m_tokens.push_back(token);
}
m_cursor = 0;
return parse_module();
}
std::shared_ptr<Node> Parser::consume(NodeType type)
{
if (!type_is(type))
{
std::stringstream ss;
ss << "'" << GRINO_TRIM(NodeTypeStr[type], "NODE_")
<< "' expected, got '"
<< GRINO_TRIM(NodeTypeStr[m_tokens[m_cursor]->type()], "NODE_")
<< "'";
m_logger.log<syntax_error>(LOG_ERROR, loc(), ss.str());
}
return consume();
}
std::shared_ptr<Node> Parser::consume()
{
assert(m_cursor < m_tokens.size());
auto node = m_tokens[m_cursor];
m_cursor++;
return node;
}
bool Parser::type_is(NodeType type) const
{
if (m_cursor >= m_tokens.size())
{
return false;
}
return m_tokens[m_cursor]->type() == type;
}
bool Parser::type_is(std::vector<NodeType> const& types) const
{
if (m_cursor + types.size() >= m_tokens.size())
{
return false;
}
for (size_t i=0; i<types.size(); i++)
{
if (m_tokens[m_cursor + i]->type() != types[i])
{
return false;
}
}
return true;
}
Loc Parser::loc() const
{
if (m_cursor < m_tokens.size())
{
return m_tokens[m_cursor]->loc();
}
else if (m_tokens.empty() == false)
{
return m_tokens.back()->loc();
}
else
{
return Loc {"???", 1};
}
}
std::shared_ptr<Node> Parser::parse_module()
{
auto node = std::make_shared<Node>(NODE_MODULE, "", loc());
while (m_cursor < m_tokens.size())
{
node->add_child(parse_expr());
}
return node;
}
std::shared_ptr<Node> Parser::parse_expr()
{
if (type_is(NODE_BOOL))
{
return consume();
}
std::stringstream ss;
ss << "unexpected '"
<< GRINO_TRIM(NodeTypeStr[m_tokens[m_cursor]->type()], "NODE_")
<< "'";
m_logger.log<syntax_error>(LOG_ERROR, loc(), ss.str());
return nullptr;
}
}

37
src/Parser.hpp Normal file
View File

@ -0,0 +1,37 @@
#ifndef grino_PARSER_HPP
#define grino_PARSER_HPP
#include "commons.hpp"
#include "Lexer.hpp"
#include "src/mutils.hpp"
namespace grino
{
GRINO_ERROR(syntax_error);
class Parser
{
public:
explicit Parser(Logger& logger, Lexer& lexer);
virtual ~Parser();
std::shared_ptr<Node> parse(std::string const& source);
private:
Logger& m_logger;
Lexer& m_lexer;
std::vector<std::shared_ptr<Node>> m_tokens;
size_t m_cursor;
std::shared_ptr<Node> consume(NodeType type);
std::shared_ptr<Node> consume();
bool type_is(NodeType type) const;
bool type_is(std::vector<NodeType> const& types) const;
Loc loc() const;
std::shared_ptr<Node> parse_module();
std::shared_ptr<Node> parse_expr();
};
}
#endif

79
src/Program.cpp Normal file
View File

@ -0,0 +1,79 @@
#include "Program.hpp"
#include "src/opcodes.hpp"
namespace grino
{
/*explicit*/ Program::Program()
{
}
/*virtual*/ Program::~Program()
{
}
Instr Program::get(size_t index) const
{
assert(index < size());
return m_instrs[index];
}
size_t Program::push_instr(OpcodeType opcode,
std::optional<size_t> param /*=std::nullopt*/)
{
m_instrs.push_back(Instr {opcode, param});
return m_instrs.size() - 1;
}
size_t Program::push_constant(std::shared_ptr<Value> value)
{
size_t addr = 0;
for (auto const& constant: m_constants)
{
if (value->equals(*constant))
{
return addr;
}
addr++;
}
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];
}
std::string Program::string() const
{
std::stringstream ss;
size_t addr = 0;
for (auto const& instr: m_instrs)
{
ss << addr << "\t" << GRINO_TRIM(OpcodeTypeStr[instr.opcode], "OPCODE_");
if (instr.param)
{
ss << "\t" << *instr.param;
}
ss << "\n";
}
addr = 0;
ss << "\n";
for (auto const& value: m_constants)
{
ss << addr << "\t" << value->string() << "\n";
addr++;
}
return ss.str();
}
}

40
src/Program.hpp Normal file
View File

@ -0,0 +1,40 @@
#ifndef grino_PROGRAM_HPP
#define grino_PROGRAM_HPP
#include "commons.hpp"
#include "Value.hpp"
#include "opcodes.hpp"
namespace grino
{
struct Instr {
OpcodeType opcode;
std::optional<size_t> param;
};
class Program
{
public:
explicit Program();
virtual ~Program();
size_t size() const { return m_instrs.size(); }
Instr get(size_t index) const;
size_t push_instr(OpcodeType opcode,
std::optional<size_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

75
src/VM.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "VM.hpp"
#include "src/opcodes.hpp"
namespace grino
{
/*explicit*/ VM::VM(Logger& logger)
: m_logger { logger }
{
}
/*virtual*/ VM::~VM()
{
}
void VM::run(Program& program)
{
m_bp = 0;
m_sp = 0;
m_pc = 0;
while (m_pc < program.size())
{
Instr instr = program.get(m_pc);
switch (instr.opcode)
{
case OPCODE_LOAD_CONST: {
push(*instr.param);
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_sp; i++)
{
ss << i << "\t" << m_stack[i] << "\n";
}
return ss.str();
}
void VM::push(size_t addr)
{
if (m_sp >= STACK_SIZE)
{
m_logger.log<execution_error>(LOG_ERROR,
Loc {"-", 1},
"stack overflow");
}
m_stack[m_sp] = addr;
m_sp++;
}
size_t VM::pop()
{
assert(m_sp > 0);
size_t addr = m_stack[m_sp - 1];
m_sp--;
return addr;
}
}

36
src/VM.hpp Normal file
View File

@ -0,0 +1,36 @@
#ifndef grino_VM_HPP
#define grino_VM_HPP
#include "commons.hpp"
#include "Logger.hpp"
#include "Program.hpp"
namespace grino
{
constexpr size_t STACK_SIZE = 4096;
GRINO_ERROR(execution_error);
class VM
{
public:
explicit VM(Logger& logger);
virtual ~VM();
void run(Program& program);
std::string string() const;
private:
Logger& m_logger;
std::array<size_t, STACK_SIZE> m_stack;
size_t m_sp; /* stack pointer */
size_t m_bp; /* base pointer */
size_t m_pc; /* program counter */
void push(size_t addr);
size_t pop();
};
}
#endif

49
src/Value.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "Value.hpp"
namespace grino
{
/*static*/ std::shared_ptr<Value> Value::make_nil()
{
auto value = std::make_shared<Value>();
value->m_type = TYPE_NIL;
return value;
}
/*static*/ std::shared_ptr<Value> Value::make_bool(bool val)
{
auto value = std::make_shared<Value>();
value->m_type = TYPE_BOOL;
value->m_bool_val = val;
return value;
}
std::string Value::string() const
{
switch (m_type)
{
case TYPE_NIL: return "<nil>";
case TYPE_BOOL: return *m_bool_val ? "true" : "false";
default:
std::cerr << "cannot stringify value "
<< TypeTypeStr[m_type] << std::endl;
abort();
}
}
bool Value::equals(Value const& other) const
{
if (m_type != other.m_type) { return false; }
switch (m_type)
{
case TYPE_NIL: return true;
case TYPE_BOOL: return *m_bool_val == *other.m_bool_val;
default:
std::cerr << "cannot compare equality with value "
<< TypeTypeStr[m_type] << std::endl;
abort();
}
}
}

30
src/Value.hpp Normal file
View File

@ -0,0 +1,30 @@
#ifndef grino_VALUE_HPP
#define grino_VALUE_HPP
#include "commons.hpp"
#include "types.hpp"
namespace grino
{
class Value
{
public:
static std::shared_ptr<Value> make_nil();
static std::shared_ptr<Value> make_bool(bool val);
explicit Value() = default;
virtual ~Value() = default;
TypeType type() const { return m_type; }
bool as_bool() const { return *m_bool_val; }
std::string string() const;
bool equals(Value const& other) const;
private:
TypeType m_type = TYPE_NIL;
std::optional<bool> m_bool_val;
};
}
#endif

18
src/commons.hpp Normal file
View File

@ -0,0 +1,18 @@
#ifndef grino_COMMONS_HPP
#define grino_COMMONS_HPP
#include <cassert>
#include <fstream>
#include <iostream>
#include <filesystem>
#include <vector>
#include <array>
#include <memory>
#include <functional>
#include <optional>
#include <sstream>
#include "config.hpp"
#include "mutils.hpp"
#endif

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

@ -0,0 +1,6 @@
#ifndef grino_CONFIG_IN_HPP
#define grino_CONFIG_IN_HPP
#define GRINO_VERSION "@version@"
#endif

99
src/main.cpp Normal file
View File

@ -0,0 +1,99 @@
#include <getopt.h>
#include "commons.hpp"
#include "Node.hpp"
#include "Lexer.hpp"
#include "Parser.hpp"
#include "Compiler.hpp"
#include "Program.hpp"
#include "VM.hpp"
#include "Logger.hpp"
int main(int argc, char** argv)
{
bool debug_mode = false;
static struct option options[] = {
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{"debug", no_argument, 0, 'd'},
{0, 0, 0, 0}
};
int option_index = 0;
int c = getopt_long(argc, argv, "hvd", options, &option_index);
switch (c)
{
case 'h': {
std::cout << "Usage: grino [OPTION]... source" << std::endl;
std::cout << "OPTIONS:" << std::endl;
std::cout << "\t" << "-d, --debug, "
<< "activate debug mode" << std::endl;
std::cout << "\t" << "-h, --help, "
<< "show this message" << std::endl;
std::cout << "\t" << "-v, --version, "
<< "show grino version" << std::endl;
exit(0);
} break;
case 'v': {
std::cout << "grino version: " << GRINO_VERSION << std::endl;
std::cout << "License: " << "GPLv3 or later (see LICENSE)"<< std::endl;
exit(0);
} break;
case 'd': {
debug_mode = true;
} break;
}
if (optind < argc)
{
std::string source;
{
std::ifstream file { argv[optind] };
assert(file);
std::string line;
while (std::getline(file, line))
{ source += line + (file.eof() ? "" : "\n"); }
}
grino::Logger logger;
grino::Lexer lexer {logger, argv[optind]};
grino::Parser parser {logger, lexer};
auto ast = parser.parse(source);
if (debug_mode)
{
std::cout << "--- ast ---" << std::endl;
std::cout << ast->string() << std::endl;
}
grino::Compiler compiler {logger};
grino::Program program;
compiler.compile(ast, program);
if (debug_mode)
{
std::cout << "--- program ---" << std::endl;
std::cout << program.string() << std::endl;
}
grino::VM vm {logger};
vm.run(program);
if (debug_mode)
{
std::cout << "--- stack ---" << std::endl;
std::cout << vm.string() << std::endl;
}
}
return 0;
}

21
src/mutils.hpp Normal file
View File

@ -0,0 +1,21 @@
#ifndef grino_MUTILS_HPP
#define grino_MUTILS_HPP
#include <stdexcept>
#define GEN_ENUM(X) X
#define GEN_STR(X) #X
#define GRINO_ENUM(PREFIX, TYPE) \
enum PREFIX ## Type { TYPE(GEN_ENUM) }; \
constexpr char const* PREFIX ## TypeStr [] = { TYPE(GEN_STR) }
#define GRINO_TRIM(SRC, TOREMOVE) \
std::string(SRC).substr(std::string(TOREMOVE).size())
#define GRINO_ERROR(NAME) \
struct NAME : public std::runtime_error { \
NAME (std::string const& what) : std::runtime_error { what } {} \
}
#endif

15
src/opcodes.hpp Normal file
View File

@ -0,0 +1,15 @@
#ifndef grino_OPCODES_HPP
#define grino_OPCODES_HPP
#include "commons.hpp"
#include "src/mutils.hpp"
#define OPCODES(G) \
G(OPCODE_LOAD_CONST)
namespace grino
{
GRINO_ENUM(Opcode, OPCODES);
}
#endif

15
src/types.hpp Normal file
View File

@ -0,0 +1,15 @@
#ifndef grino_TYPES_HPP
#define grino_TYPES_HPP
#include "commons.hpp"
#define TYPES(G) \
G(TYPE_NIL), \
G(TYPE_BOOL)
namespace grino
{
GRINO_ENUM(Type, TYPES);
}
#endif

53
tests/Lexer.cpp Normal file
View File

@ -0,0 +1,53 @@
#include <catch2/catch.hpp>
#include "../src/Lexer.hpp"
class LexerTest
{
public:
explicit LexerTest() {}
virtual ~LexerTest() {}
void test_next(grino::Lexer& lexer, std::string const& oracle)
{
auto node = lexer.next();
REQUIRE(node != nullptr);
REQUIRE(oracle == node->string());
}
void test_end(grino::Lexer& lexer)
{
auto node = lexer.next();
REQUIRE(node == nullptr);
}
protected:
grino::Logger m_logger;
};
TEST_CASE_METHOD(LexerTest, "Lexer_errors")
{
grino::Lexer lexer {m_logger, "tests/lexer"};
lexer.scan(" § ");
REQUIRE_THROWS_AS(lexer.next(), grino::lexical_error);
}
TEST_CASE_METHOD(LexerTest, "Lexer_booleans")
{
grino::Lexer lexer {m_logger, "tests/lexer"};
SECTION("space around")
{
lexer.scan(" true false ");
test_next(lexer, "BOOL[true]");
test_next(lexer, "BOOL[false]");
test_end(lexer);
}
SECTION("no space around")
{
lexer.scan("true false");
test_next(lexer, "BOOL[true]");
test_next(lexer, "BOOL[false]");
test_end(lexer);
}
}

31
tests/Parser.cpp Normal file
View File

@ -0,0 +1,31 @@
#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)
{
auto root = m_parser.parse(source);
REQUIRE(oracle == root->string());
}
protected:
grino::Logger m_logger;
grino::Lexer m_lexer {m_logger, "tests/parser"};
grino::Parser m_parser {m_logger, m_lexer};
};
TEST_CASE_METHOD(ParserTest, "Parser_empty")
{
test_parse("MODULE", "");
}
TEST_CASE_METHOD(ParserTest, "Parser_booleans")
{
test_parse("MODULE(BOOL[true],BOOL[false])", "true false");
}

2
tests/main.cpp Normal file
View File

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