From f001a50d38267ad33a5dfc4b7a7e35eb9a8ea35f Mon Sep 17 00:00:00 2001 From: bog Date: Mon, 11 Sep 2023 01:05:29 +0200 Subject: [PATCH] ADD: bool literals. --- .gitignore | 4 + Makefile | 11 +++ doc/grammar.bnf | 2 + meson.build | 49 +++++++++++++ src/Compiler.cpp | 42 +++++++++++ src/Compiler.hpp | 26 +++++++ src/Lexer.cpp | 181 ++++++++++++++++++++++++++++++++++++++++++++++ src/Lexer.hpp | 63 ++++++++++++++++ src/Loc.cpp | 14 ++++ src/Loc.hpp | 23 ++++++ src/Logger.cpp | 12 +++ src/Logger.hpp | 38 ++++++++++ src/Node.cpp | 55 ++++++++++++++ src/Node.hpp | 42 +++++++++++ src/Parser.cpp | 129 +++++++++++++++++++++++++++++++++ src/Parser.hpp | 37 ++++++++++ src/Program.cpp | 79 ++++++++++++++++++++ src/Program.hpp | 40 ++++++++++ src/VM.cpp | 75 +++++++++++++++++++ src/VM.hpp | 36 +++++++++ src/Value.cpp | 49 +++++++++++++ src/Value.hpp | 30 ++++++++ src/commons.hpp | 18 +++++ src/config.in.hpp | 6 ++ src/main.cpp | 99 +++++++++++++++++++++++++ src/mutils.hpp | 21 ++++++ src/opcodes.hpp | 15 ++++ src/types.hpp | 15 ++++ tests/Lexer.cpp | 53 ++++++++++++++ tests/Parser.cpp | 31 ++++++++ tests/main.cpp | 2 + 31 files changed, 1297 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 doc/grammar.bnf create mode 100644 meson.build create mode 100644 src/Compiler.cpp create mode 100644 src/Compiler.hpp create mode 100644 src/Lexer.cpp create mode 100644 src/Lexer.hpp create mode 100644 src/Loc.cpp create mode 100644 src/Loc.hpp create mode 100644 src/Logger.cpp create mode 100644 src/Logger.hpp create mode 100644 src/Node.cpp create mode 100644 src/Node.hpp create mode 100644 src/Parser.cpp create mode 100644 src/Parser.hpp create mode 100644 src/Program.cpp create mode 100644 src/Program.hpp create mode 100644 src/VM.cpp create mode 100644 src/VM.hpp create mode 100644 src/Value.cpp create mode 100644 src/Value.hpp create mode 100644 src/commons.hpp create mode 100644 src/config.in.hpp create mode 100644 src/main.cpp create mode 100644 src/mutils.hpp create mode 100644 src/opcodes.hpp create mode 100644 src/types.hpp create mode 100644 tests/Lexer.cpp create mode 100644 tests/Parser.cpp create mode 100644 tests/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02bfa53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.cache +build +*~* +*\#* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9f0a1ea --- /dev/null +++ b/Makefile @@ -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 diff --git a/doc/grammar.bnf b/doc/grammar.bnf new file mode 100644 index 0000000..370828b --- /dev/null +++ b/doc/grammar.bnf @@ -0,0 +1,2 @@ +MODULE ::= EXPR* +EXPR ::= bool diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..3d2b511 --- /dev/null +++ b/meson.build @@ -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') + ]) diff --git a/src/Compiler.cpp b/src/Compiler.cpp new file mode 100644 index 0000000..1c8c955 --- /dev/null +++ b/src/Compiler.cpp @@ -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, + Program& program) + { + switch (node->type()) + { + case NODE_MODULE: { + for (size_t i=0; isize(); 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(); + } + } +} diff --git a/src/Compiler.hpp b/src/Compiler.hpp new file mode 100644 index 0000000..a5b25bb --- /dev/null +++ b/src/Compiler.hpp @@ -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, + Program& program); + + private: + Logger& m_logger; + }; +} + +#endif diff --git a/src/Lexer.cpp b/src/Lexer.cpp new file mode 100644 index 0000000..8cd6eb8 --- /dev/null +++ b/src/Lexer.cpp @@ -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 Lexer::next() + { + std::optional 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(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(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 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 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()>; + + class Lexer + { + public: + explicit Lexer(Logger& logger, std::filesystem::path source_path); + virtual ~Lexer(); + + void scan(std::string const& source); + std::shared_ptr next(); + + private: + Logger& m_logger; + Loc m_loc; + std::vector m_scanners; + std::vector 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 scan_text(NodeType type, + std::string const& text, + bool has_value); + + std::optional scan_keyword(NodeType type, + std::string const& text, + bool has_value); + + }; +} + +#endif diff --git a/src/Loc.cpp b/src/Loc.cpp new file mode 100644 index 0000000..ac3fe74 --- /dev/null +++ b/src/Loc.cpp @@ -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() + { + } +} diff --git a/src/Loc.hpp b/src/Loc.hpp new file mode 100644 index 0000000..863bdee --- /dev/null +++ b/src/Loc.hpp @@ -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 diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..b5b230d --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,12 @@ +#include "Logger.hpp" + +namespace grino +{ + /*explicit*/ Logger::Logger() + { + } + + /*virtual*/ Logger::~Logger() + { + } +} diff --git a/src/Logger.hpp b/src/Logger.hpp new file mode 100644 index 0000000..d5b4eed --- /dev/null +++ b/src/Logger.hpp @@ -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 + void log(LogType type, Loc const& loc, std::string const& what); + private: + }; + + template + 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 diff --git a/src/Node.cpp b/src/Node.cpp new file mode 100644 index 0000000..30824d5 --- /dev/null +++ b/src/Node.cpp @@ -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) + { + m_children.push_back(node); + } + + std::weak_ptr 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(); + } +} diff --git a/src/Node.hpp b/src/Node.hpp new file mode 100644 index 0000000..74a2b49 --- /dev/null +++ b/src/Node.hpp @@ -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); + std::weak_ptr child(size_t index); + + std::string string() const; + + private: + NodeType m_type; + std::string m_repr; + Loc m_loc; + std::vector> m_children; + }; +} + +#endif diff --git a/src/Parser.cpp b/src/Parser.cpp new file mode 100644 index 0000000..f8ee5f0 --- /dev/null +++ b/src/Parser.cpp @@ -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 Parser::parse(std::string const& source) + { + std::shared_ptr token; + + m_lexer.scan(source); + + while ( (token = m_lexer.next()) ) + { + m_tokens.push_back(token); + } + + m_cursor = 0; + + return parse_module(); + } + + std::shared_ptr 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(LOG_ERROR, loc(), ss.str()); + } + + return consume(); + } + + std::shared_ptr 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 const& types) const + { + if (m_cursor + types.size() >= m_tokens.size()) + { + return false; + } + + for (size_t i=0; itype() != 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 Parser::parse_module() + { + auto node = std::make_shared(NODE_MODULE, "", loc()); + + while (m_cursor < m_tokens.size()) + { + node->add_child(parse_expr()); + } + + return node; + } + + std::shared_ptr 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(LOG_ERROR, loc(), ss.str()); + + return nullptr; + } + +} diff --git a/src/Parser.hpp b/src/Parser.hpp new file mode 100644 index 0000000..7a9bef1 --- /dev/null +++ b/src/Parser.hpp @@ -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 parse(std::string const& source); + + private: + Logger& m_logger; + Lexer& m_lexer; + std::vector> m_tokens; + size_t m_cursor; + + std::shared_ptr consume(NodeType type); + std::shared_ptr consume(); + bool type_is(NodeType type) const; + bool type_is(std::vector const& types) const; + Loc loc() const; + + std::shared_ptr parse_module(); + std::shared_ptr parse_expr(); + }; +} + +#endif diff --git a/src/Program.cpp b/src/Program.cpp new file mode 100644 index 0000000..48d214e --- /dev/null +++ b/src/Program.cpp @@ -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 param /*=std::nullopt*/) + { + m_instrs.push_back(Instr {opcode, param}); + return m_instrs.size() - 1; + } + + size_t Program::push_constant(std::shared_ptr 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 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(); + } +} diff --git a/src/Program.hpp b/src/Program.hpp new file mode 100644 index 0000000..fc17af3 --- /dev/null +++ b/src/Program.hpp @@ -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 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 param=std::nullopt); + + size_t push_constant(std::shared_ptr value); + std::shared_ptr constant(size_t index) const; + + std::string string() const; + + private: + std::vector m_instrs; + std::vector> m_constants; + }; +} + +#endif diff --git a/src/VM.cpp b/src/VM.cpp new file mode 100644 index 0000000..c6bdd8f --- /dev/null +++ b/src/VM.cpp @@ -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= STACK_SIZE) + { + m_logger.log(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; + } +} diff --git a/src/VM.hpp b/src/VM.hpp new file mode 100644 index 0000000..2da3dc7 --- /dev/null +++ b/src/VM.hpp @@ -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 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 diff --git a/src/Value.cpp b/src/Value.cpp new file mode 100644 index 0000000..4061f63 --- /dev/null +++ b/src/Value.cpp @@ -0,0 +1,49 @@ +#include "Value.hpp" + +namespace grino +{ + /*static*/ std::shared_ptr Value::make_nil() + { + auto value = std::make_shared(); + value->m_type = TYPE_NIL; + return value; + } + + /*static*/ std::shared_ptr Value::make_bool(bool val) + { + auto value = std::make_shared(); + value->m_type = TYPE_BOOL; + value->m_bool_val = val; + return value; + } + + std::string Value::string() const + { + switch (m_type) + { + case TYPE_NIL: return ""; + 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(); + } + } +} diff --git a/src/Value.hpp b/src/Value.hpp new file mode 100644 index 0000000..88fe4f2 --- /dev/null +++ b/src/Value.hpp @@ -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 make_nil(); + static std::shared_ptr 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 m_bool_val; + }; +} + +#endif diff --git a/src/commons.hpp b/src/commons.hpp new file mode 100644 index 0000000..36354ec --- /dev/null +++ b/src/commons.hpp @@ -0,0 +1,18 @@ +#ifndef grino_COMMONS_HPP +#define grino_COMMONS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.hpp" +#include "mutils.hpp" + +#endif diff --git a/src/config.in.hpp b/src/config.in.hpp new file mode 100644 index 0000000..dcba94a --- /dev/null +++ b/src/config.in.hpp @@ -0,0 +1,6 @@ +#ifndef grino_CONFIG_IN_HPP +#define grino_CONFIG_IN_HPP + +#define GRINO_VERSION "@version@" + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..b7eb340 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,99 @@ +#include +#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; +} diff --git a/src/mutils.hpp b/src/mutils.hpp new file mode 100644 index 0000000..3f28836 --- /dev/null +++ b/src/mutils.hpp @@ -0,0 +1,21 @@ +#ifndef grino_MUTILS_HPP +#define grino_MUTILS_HPP + +#include + +#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 diff --git a/src/opcodes.hpp b/src/opcodes.hpp new file mode 100644 index 0000000..29399bd --- /dev/null +++ b/src/opcodes.hpp @@ -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 diff --git a/src/types.hpp b/src/types.hpp new file mode 100644 index 0000000..0350d8d --- /dev/null +++ b/src/types.hpp @@ -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 diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp new file mode 100644 index 0000000..4bdc426 --- /dev/null +++ b/tests/Lexer.cpp @@ -0,0 +1,53 @@ +#include +#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); + } +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp new file mode 100644 index 0000000..ca74ed0 --- /dev/null +++ b/tests/Parser.cpp @@ -0,0 +1,31 @@ +#include +#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"); +} diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..4ed06df --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include