From fbbbd4e9f4b8ed2afa84bbe53839788a16fd4f76 Mon Sep 17 00:00:00 2001 From: bog Date: Wed, 30 Aug 2023 20:06:26 +0200 Subject: [PATCH] ADD: int literals. --- .gitignore | 4 ++ Makefile | 13 ++++ doc/grammar.bnf | 4 ++ lib/Compiler.cpp | 52 ++++++++++++++++ lib/Compiler.hpp | 27 +++++++++ lib/Lexer.cpp | 134 +++++++++++++++++++++++++++++++++++++++++ lib/Lexer.hpp | 44 ++++++++++++++ lib/Node.cpp | 60 ++++++++++++++++++ lib/Node.hpp | 41 +++++++++++++ lib/Parser.cpp | 118 ++++++++++++++++++++++++++++++++++++ lib/Parser.hpp | 38 ++++++++++++ lib/Program.cpp | 56 +++++++++++++++++ lib/Program.hpp | 39 ++++++++++++ lib/SrcLoc.cpp | 19 ++++++ lib/SrcLoc.hpp | 25 ++++++++ lib/StaticPass.cpp | 33 ++++++++++ lib/StaticPass.hpp | 23 +++++++ lib/StatusLog.cpp | 23 +++++++ lib/StatusLog.hpp | 27 +++++++++ lib/Type.cpp | 23 +++++++ lib/Type.hpp | 29 +++++++++ lib/TypeResolver.cpp | 33 ++++++++++ lib/TypeResolver.hpp | 23 +++++++ lib/VM.cpp | 54 +++++++++++++++++ lib/VM.hpp | 28 +++++++++ lib/Value.cpp | 25 ++++++++ lib/Value.hpp | 26 ++++++++ lib/commons.hpp | 13 ++++ lib/mutils.hpp | 11 ++++ lib/opcodes.hpp | 15 +++++ meson.build | 53 ++++++++++++++++ src/Args.cpp | 79 ++++++++++++++++++++++++ src/Args.hpp | 29 +++++++++ src/Loader.cpp | 32 ++++++++++ src/Loader.hpp | 18 ++++++ src/main.cpp | 64 ++++++++++++++++++++ tests/Compiler.cpp | 61 +++++++++++++++++++ tests/Lexer.cpp | 41 +++++++++++++ tests/Parser.cpp | 51 ++++++++++++++++ tests/StaticPass.cpp | 36 +++++++++++ tests/TypeResolver.cpp | 43 +++++++++++++ tests/main.cpp | 2 + 42 files changed, 1569 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 doc/grammar.bnf create mode 100644 lib/Compiler.cpp create mode 100644 lib/Compiler.hpp create mode 100644 lib/Lexer.cpp create mode 100644 lib/Lexer.hpp create mode 100644 lib/Node.cpp create mode 100644 lib/Node.hpp create mode 100644 lib/Parser.cpp create mode 100644 lib/Parser.hpp create mode 100644 lib/Program.cpp create mode 100644 lib/Program.hpp create mode 100644 lib/SrcLoc.cpp create mode 100644 lib/SrcLoc.hpp create mode 100644 lib/StaticPass.cpp create mode 100644 lib/StaticPass.hpp create mode 100644 lib/StatusLog.cpp create mode 100644 lib/StatusLog.hpp create mode 100644 lib/Type.cpp create mode 100644 lib/Type.hpp create mode 100644 lib/TypeResolver.cpp create mode 100644 lib/TypeResolver.hpp create mode 100644 lib/VM.cpp create mode 100644 lib/VM.hpp create mode 100644 lib/Value.cpp create mode 100644 lib/Value.hpp create mode 100644 lib/commons.hpp create mode 100644 lib/mutils.hpp create mode 100644 lib/opcodes.hpp create mode 100644 meson.build create mode 100644 src/Args.cpp create mode 100644 src/Args.hpp create mode 100644 src/Loader.cpp create mode 100644 src/Loader.hpp create mode 100644 src/main.cpp create mode 100644 tests/Compiler.cpp create mode 100644 tests/Lexer.cpp create mode 100644 tests/Parser.cpp create mode 100644 tests/StaticPass.cpp create mode 100644 tests/TypeResolver.cpp create mode 100644 tests/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e705fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +*~* +*\#* +.cache \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..15670ab --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +BUILD_DIR=build + +.PHONY: $(BUILD_DIR) tests + +build: + meson setup $(BUILD_DIR) + meson compile -C $(BUILD_DIR) + +tests: $(BUILD_DIR) + ./$(BUILD_DIR)/roza-tests + +install: tests + meson install -C $(BUILD_DIR) diff --git a/doc/grammar.bnf b/doc/grammar.bnf new file mode 100644 index 0000000..4835859 --- /dev/null +++ b/doc/grammar.bnf @@ -0,0 +1,4 @@ +PROG ::= (INSTR (EOI INSTR)*)? +INSTR ::= EXPR +EXPR ::= BASE +BASE ::= int diff --git a/lib/Compiler.cpp b/lib/Compiler.cpp new file mode 100644 index 0000000..ca96aaf --- /dev/null +++ b/lib/Compiler.cpp @@ -0,0 +1,52 @@ +#include "Compiler.hpp" +#include "lib/opcodes.hpp" + +namespace roza +{ + /*explicit*/ Compiler::Compiler(StatusLog& log) + : m_log { log } + { + } + + /*virtual*/ Compiler::~Compiler() + { + } + + std::shared_ptr Compiler::compile(std::shared_ptr root) + { + auto program = std::make_shared(); + compile_node(root, program); + return program; + } + + void Compiler::compile_node(std::shared_ptr root, std::shared_ptr prog) + { + switch (root->type()) + { + case NODE_INT: { + auto value = std::make_shared(std::stoi(root->repr()), root->loc()); + prog->push_instr(OP_PUSH_CONST, prog->push_value(value)); + } break; + + case NODE_PROG: { + compile_children(root, prog); + } break; + + case NODE_INSTR: { + compile_children(root, prog); + prog->push_instr(OP_POP); + } break; + + default: + m_log.fatal(root->loc(), "cannot compile node '" + root->string() + "'."); + } + } + + void Compiler::compile_children(std::shared_ptr root, std::shared_ptr prog) + { + for (size_t i=0; isize(); i++) + { + compile_node(root->child(i), prog); + } + } +} diff --git a/lib/Compiler.hpp b/lib/Compiler.hpp new file mode 100644 index 0000000..fd47e5a --- /dev/null +++ b/lib/Compiler.hpp @@ -0,0 +1,27 @@ +#ifndef roza_COMPILER_HPP +#define roza_COMPILER_HPP + +#include "commons.hpp" +#include "Program.hpp" +#include "Node.hpp" +#include "opcodes.hpp" +#include "StatusLog.hpp" + +namespace roza +{ + class Compiler + { + public: + explicit Compiler(StatusLog& log); + virtual ~Compiler(); + + std::shared_ptr compile(std::shared_ptr root); + void compile_node(std::shared_ptr root, std::shared_ptr prog); + void compile_children(std::shared_ptr root, std::shared_ptr prog); + + private: + StatusLog& m_log; + }; +} + +#endif diff --git a/lib/Lexer.cpp b/lib/Lexer.cpp new file mode 100644 index 0000000..9cacc2e --- /dev/null +++ b/lib/Lexer.cpp @@ -0,0 +1,134 @@ +#include "Lexer.hpp" +#include "lib/Node.hpp" + +namespace roza +{ + /*explicit*/ Lexer::Lexer(StatusLog& log, SrcLoc loc) + : m_log { log } + , m_loc { loc } + { + 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; + + skip_blanks(); + + while (m_cursor < source.size()) + { + while (m_cursor < source.size() + && source[m_cursor] == '#') + { + while (m_cursor < source.size() + && source[m_cursor] != '\n') + { + m_cursor++; + } + + m_cursor++; + } + + skip_blanks(); + + ScanInfo info; + + std::shared_ptr node; + size_t cursor = m_cursor; + + for (auto& scanner: m_scanners) + { + ScanInfo info = scanner(); + + if (info.node && info.cursor > cursor) + { + node = info.node; + cursor = info.cursor; + } + } + + m_nodes.push_back(node); + m_cursor = cursor; + + skip_blanks(); + + if (!node) + { + std::string symb; + while (m_cursor < m_source.size() + && !std::isblank(m_source[m_cursor])) + { + symb += m_source[m_cursor]; + m_cursor++; + } + + m_log.fatal(m_loc + , std::string() + + "unexpected symbol '" + + symb + "'"); + } + + } + } + + void Lexer::skip_blanks() + { + while (m_cursor < m_source.size() + && std::isspace(m_source.at(m_cursor))) + { + if (m_source.at(m_cursor) == '\n') + { + m_loc.set_line(m_loc.line() + 1); + auto root = std::make_shared(NODE_EOI, "", m_loc); + m_nodes.push_back(root); + } + + m_cursor++; + } + } + + std::shared_ptr Lexer::get_or_nullptr(size_t index) const + { + if (index >= m_nodes.size()) + { + return nullptr; + } + + return m_nodes.at(index); + } + + ScanInfo Lexer::scan_int() const + { + size_t cursor = m_cursor; + std::string repr; + + if (m_source[cursor] == '-') + { + repr += "-"; + cursor++; + } + + while (cursor < m_source.size() + && std::isdigit(m_source[cursor])) + { + repr += m_source[cursor]; + cursor++; + } + + if (!repr.empty()) + { + return ScanInfo { + std::make_shared(NodeType::NODE_INT, repr, m_loc), + cursor + }; + } + + return ScanInfo {}; + } +} diff --git a/lib/Lexer.hpp b/lib/Lexer.hpp new file mode 100644 index 0000000..5220a73 --- /dev/null +++ b/lib/Lexer.hpp @@ -0,0 +1,44 @@ +#ifndef roza_LEXER_HPP +#define roza_LEXER_HPP + +#include "commons.hpp" +#include "Node.hpp" +#include "SrcLoc.hpp" +#include "StatusLog.hpp" + +namespace roza +{ + struct ScanInfo { + std::shared_ptr node = nullptr; + size_t cursor = 0; + }; + + using scanner = std::function; + + class Lexer + { + public: + explicit Lexer(StatusLog& log, SrcLoc loc); + virtual ~Lexer(); + + SrcLoc loc() const { return m_loc; } + size_t size() const { return m_nodes.size(); } + + void scan(std::string const& source); + void skip_blanks(); + std::shared_ptr get_or_nullptr(size_t index) const; + + private: + StatusLog& m_log; + SrcLoc m_loc; + + std::string m_source; + size_t m_cursor = 0; + std::vector> m_nodes; + std::vector m_scanners; + + ScanInfo scan_int() const; + }; +} + +#endif diff --git a/lib/Node.cpp b/lib/Node.cpp new file mode 100644 index 0000000..26fe2c1 --- /dev/null +++ b/lib/Node.cpp @@ -0,0 +1,60 @@ +#include "Node.hpp" + +namespace roza +{ + /*explicit*/ Node::Node(NodeType type, std::string const& repr, SrcLoc loc) + : m_type { type } + , m_repr { repr } + , m_loc { loc } + { + } + + /*virtual*/ Node::~Node() + { + } + + std::string Node::string() const + { + std::stringstream ss; + ss << std::string(NodeTypeStr[m_type]).substr(std::string("NODE_").size()); + + if (m_repr != "") + { + 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(); + } + + void Node::add_child(std::shared_ptr child) + { + assert(child); + m_children.push_back(std::move(child)); + } + + std::shared_ptr Node::child(size_t index) + { + assert(index < size()); + return m_children[index]; + } + + Node const& Node::child(size_t index) const + { + assert(index < size()); + return *m_children[index]; + } +} diff --git a/lib/Node.hpp b/lib/Node.hpp new file mode 100644 index 0000000..6c65437 --- /dev/null +++ b/lib/Node.hpp @@ -0,0 +1,41 @@ +#ifndef roza_NODE_HPP +#define roza_NODE_HPP + +#include "commons.hpp" +#include "SrcLoc.hpp" + +#define NODE_TYPE(G) \ + G(NODE_PROG), G(NODE_INSTR), G(NODE_EOI), \ + G(NODE_INT) + +namespace roza +{ + MAKE_ENUM(NODE_TYPE, NodeType); + + class Node + { + public: + explicit Node(NodeType type, std::string const& repr, SrcLoc loc); + virtual ~Node(); + + NodeType type() const { return m_type; } + std::string repr() const { return m_repr; } + SrcLoc loc() const { return m_loc; } + size_t size() const { return m_children.size(); } + + std::string string() const; + + void add_child(std::shared_ptr child); + + std::shared_ptr child(size_t index); + Node const& child(size_t index) const; + + private: + NodeType m_type; + std::string m_repr; + SrcLoc m_loc; + std::vector> m_children; + }; +} + +#endif diff --git a/lib/Parser.cpp b/lib/Parser.cpp new file mode 100644 index 0000000..b276e94 --- /dev/null +++ b/lib/Parser.cpp @@ -0,0 +1,118 @@ +#include "Parser.hpp" +#include "lib/Node.hpp" + +namespace roza +{ + /*explicit*/ Parser::Parser(Lexer& lexer, StatusLog& log) + : m_lexer { lexer } + , m_log { log } + { + } + + /*virtual*/ Parser::~Parser() + { + } + + std::shared_ptr Parser::parse() + { + m_cursor = 0; + auto root = parse_prog(); + + if (m_cursor < m_lexer.size()) + { + m_log.fatal(m_lexer.loc(), "unexpected end."); + } + + return root; + } + + std::shared_ptr Parser::node(size_t offset) const + { + return m_lexer.get_or_nullptr(m_cursor + offset); + } + + NodeType Parser::type(size_t offset) const + { + auto tok = m_lexer.get_or_nullptr(m_cursor + offset); + assert(tok); + return tok->type(); + } + + bool Parser::type_is(NodeType type, size_t offset) const + { + auto tok = m_lexer.get_or_nullptr(m_cursor + offset); + if (!tok) { return false; } + + return tok->type() == type; + } + + std::shared_ptr Parser::consume(NodeType ty) + { + if (!type_is(ty)) + { + m_log.fatal(node()->loc(), + std::string() + + "syntax error, expected '" + + NodeTypeStr[ty] + + "', got '" + + NodeTypeStr[type()] + + "'"); + return nullptr; + } + + auto ret = node(); + next(); + + return ret; + } + + void Parser::next() + { + m_cursor++; + } + + std::shared_ptr Parser::parse_prog() + { + if (m_lexer.size() == 0) + { + return std::make_shared(NODE_PROG, "", m_lexer.loc()); + } + + auto root = std::make_shared(NODE_PROG, "", node()->loc()); + root->add_child(parse_instr()); + + while (type_is(NODE_EOI)) + { + next(); + root->add_child(parse_instr()); + } + + return root; + } + + std::shared_ptr Parser::parse_instr() + { + auto lhs = parse_expr(); + + auto root = std::make_shared(NODE_INSTR, "", lhs->loc()); + root->add_child(lhs); + + return root; + } + + std::shared_ptr Parser::parse_expr() + { + return parse_base(); + } + + std::shared_ptr Parser::parse_base() + { + return parse_int(); + } + + std::shared_ptr Parser::parse_int() + { + auto root = consume(NODE_INT); + return root; + } +} diff --git a/lib/Parser.hpp b/lib/Parser.hpp new file mode 100644 index 0000000..06b1dd6 --- /dev/null +++ b/lib/Parser.hpp @@ -0,0 +1,38 @@ +#ifndef roza_PARSER_HPP +#define roza_PARSER_HPP + +#include "commons.hpp" +#include "Lexer.hpp" +#include "lib/StatusLog.hpp" + +namespace roza +{ + class Parser + { + public: + explicit Parser(Lexer& lexer, StatusLog& log); + virtual ~Parser(); + + std::shared_ptr parse(); + + private: + Lexer& m_lexer; + StatusLog& m_log; + size_t m_cursor = 0; + + std::shared_ptr node(size_t offset=0) const; + NodeType type(size_t offset=0) const; + bool type_is(NodeType type, size_t offset=0) const; + std::shared_ptr consume(NodeType type); + void next(); + + std::shared_ptr parse_prog(); + std::shared_ptr parse_instr(); + std::shared_ptr parse_expr(); + std::shared_ptr parse_base(); + std::shared_ptr parse_int(); + + }; +} + +#endif diff --git a/lib/Program.cpp b/lib/Program.cpp new file mode 100644 index 0000000..adf534f --- /dev/null +++ b/lib/Program.cpp @@ -0,0 +1,56 @@ +#include "Program.hpp" + +namespace roza +{ + /*explicit*/ Program::Program() + { + } + + /*virtual*/ Program::~Program() + { + } + + void Program::push_instr(Opcode opcode, std::optional param) + { + m_instrs.push_back({opcode, param}); + } + + size_t Program::push_value(std::shared_ptr value) + { + size_t sz = m_values.size(); + m_values.push_back(value); + return sz; + } + + Opcode Program::opcode(size_t index) const + { + assert(index < size()); + return m_instrs[index].opcode; + } + + std::optional Program::param(size_t index) const + { + assert(index < size()); + return m_instrs[index].param; + } + + std::string Program::string() const + { + std::stringstream ss; + size_t addr = 0; + + for (auto const& instr: m_instrs) + { + ss << addr + << "\t" + << (OpcodeStr[instr.opcode]) + << "\t" + << (instr.param != std::nullopt ? std::to_string(*instr.param) : "") + << "\n"; + } + + return ss.str(); + } + + +} diff --git a/lib/Program.hpp b/lib/Program.hpp new file mode 100644 index 0000000..baa3cb1 --- /dev/null +++ b/lib/Program.hpp @@ -0,0 +1,39 @@ +#ifndef roza_PROGRAM_HPP +#define roza_PROGRAM_HPP + +#include "commons.hpp" +#include "opcodes.hpp" +#include "Value.hpp" + +namespace roza +{ + using param_t = int; + + struct Instr { + Opcode opcode; + std::optional param; + }; + + class Program + { + public: + explicit Program(); + virtual ~Program(); + + size_t size() const { return m_instrs.size(); } + + void push_instr(Opcode opcode, std::optional param = std::nullopt); + size_t push_value(std::shared_ptr value); + + Opcode opcode(size_t index) const; + std::optional param(size_t index) const; + + std::string string() const; + + private: + std::vector m_instrs; + std::vector> m_values; + }; +} + +#endif diff --git a/lib/SrcLoc.cpp b/lib/SrcLoc.cpp new file mode 100644 index 0000000..d45c006 --- /dev/null +++ b/lib/SrcLoc.cpp @@ -0,0 +1,19 @@ +#include "SrcLoc.hpp" + +namespace roza +{ + /*explicit*/ SrcLoc::SrcLoc(std::filesystem::path source_file, int line) + : m_source_file { source_file } + , m_line { line } + { + } + + /*virtual*/ SrcLoc::~SrcLoc() + { + } + + void SrcLoc::set_line(int line) + { + m_line = line; + } +} diff --git a/lib/SrcLoc.hpp b/lib/SrcLoc.hpp new file mode 100644 index 0000000..f65af68 --- /dev/null +++ b/lib/SrcLoc.hpp @@ -0,0 +1,25 @@ +#ifndef roza_SRCLOC_HPP +#define roza_SRCLOC_HPP + +#include + +namespace roza +{ + class SrcLoc + { + public: + explicit SrcLoc(std::filesystem::path source_file = "???", int line=1); + virtual ~SrcLoc(); + + std::filesystem::path source_file() const { return m_source_file; } + int line() const { return m_line; } + + void set_line(int line); + + private: + std::filesystem::path m_source_file; + int m_line; + }; +} + +#endif diff --git a/lib/StaticPass.cpp b/lib/StaticPass.cpp new file mode 100644 index 0000000..56d2144 --- /dev/null +++ b/lib/StaticPass.cpp @@ -0,0 +1,33 @@ +#include "StaticPass.hpp" +#include "lib/Node.hpp" + +namespace roza +{ + /*explicit*/ StaticPass::StaticPass(StatusLog& log) + : m_log { log } + { + } + + /*virtual*/ StaticPass::~StaticPass() + { + } + + void StaticPass::check(std::shared_ptr root) + { + switch (root->type()) + { + case NODE_INT: break; + + case NODE_PROG: + case NODE_INSTR: { + for (size_t i=0; isize(); i++) + { + check(root->child(i)); + } + } break; + + default: + m_log.fatal(root->loc(), "cannot check node '" + root->string() + "'"); + } + } +} diff --git a/lib/StaticPass.hpp b/lib/StaticPass.hpp new file mode 100644 index 0000000..e62b0bf --- /dev/null +++ b/lib/StaticPass.hpp @@ -0,0 +1,23 @@ +#ifndef roza_STATICPASS_HPP +#define roza_STATICPASS_HPP + +#include "commons.hpp" +#include "StatusLog.hpp" +#include "Node.hpp" + +namespace roza +{ + class StaticPass + { + public: + explicit StaticPass(StatusLog& log); + virtual ~StaticPass(); + + void check(std::shared_ptr root); + + private: + StatusLog& m_log; + }; +} + +#endif diff --git a/lib/StatusLog.cpp b/lib/StatusLog.cpp new file mode 100644 index 0000000..9fb45d2 --- /dev/null +++ b/lib/StatusLog.cpp @@ -0,0 +1,23 @@ +#include "StatusLog.hpp" +#include + +namespace roza +{ + /*explicit*/ StatusLog::StatusLog() + { + } + + /*virtual*/ StatusLog::~StatusLog() + { + } + + void StatusLog::fatal(SrcLoc loc, std::string const& what) + { + std::stringstream ss; + + ss << loc.source_file().string() + << ":" << loc.line() << " [FATAL ERROR] " << what; + + throw std::runtime_error {ss.str()}; + } +} diff --git a/lib/StatusLog.hpp b/lib/StatusLog.hpp new file mode 100644 index 0000000..d910314 --- /dev/null +++ b/lib/StatusLog.hpp @@ -0,0 +1,27 @@ +#ifndef roza_STATUSLOG_HPP +#define roza_STATUSLOG_HPP + +#include "commons.hpp" +#include "SrcLoc.hpp" + +#define STATUS_LEVEL(G) \ + G(LEVEL_WARNING), \ + G(LEVEL_ERROR), \ + G(LEVEL_CRITICAL) + +namespace roza +{ + MAKE_ENUM(STATUS_LEVEL, Status); + + class StatusLog + { + public: + explicit StatusLog(); + virtual ~StatusLog(); + + void fatal(SrcLoc loc, std::string const& what); + private: + }; +} + +#endif diff --git a/lib/Type.cpp b/lib/Type.cpp new file mode 100644 index 0000000..255f3d6 --- /dev/null +++ b/lib/Type.cpp @@ -0,0 +1,23 @@ +#include "Type.hpp" + +namespace roza +{ + /*explicit*/ Type::Type(BaseType base) + : m_base { base } + { + } + + /*virtual*/ Type::~Type() + { + } + + bool Type::equals(BaseType rhs) const + { + return m_base == rhs; + } + + bool Type::equals(Type const& rhs) const + { + return m_base == rhs.m_base; + } +} diff --git a/lib/Type.hpp b/lib/Type.hpp new file mode 100644 index 0000000..1f4f835 --- /dev/null +++ b/lib/Type.hpp @@ -0,0 +1,29 @@ +#ifndef roza_TYPE_HPP +#define roza_TYPE_HPP + +#include "commons.hpp" + +#define BASE_TYPE(G) \ + G(TY_INT) + +namespace roza +{ + MAKE_ENUM(BASE_TYPE, BaseType) + + class Type + { + public: + explicit Type(BaseType base); + virtual ~Type(); + + BaseType base() const { return m_base; } + + bool equals(BaseType rhs) const; + bool equals(Type const& rhs) const; + + private: + BaseType m_base; + }; +} + +#endif diff --git a/lib/TypeResolver.cpp b/lib/TypeResolver.cpp new file mode 100644 index 0000000..1139d57 --- /dev/null +++ b/lib/TypeResolver.cpp @@ -0,0 +1,33 @@ +#include "TypeResolver.hpp" + +namespace roza +{ + /*explicit*/ TypeResolver::TypeResolver(StatusLog& log) + : m_log { log } + { + } + + /*virtual*/ TypeResolver::~TypeResolver() + { + } + + std::shared_ptr TypeResolver::find(std::shared_ptr root) + { + switch (root->type()) + { + case NODE_PROG: + case NODE_INSTR: { + return find(root->child(root->size() - 1)); + } break; + + case NODE_INT: { + return std::make_shared(BaseType::TY_INT); + } break; + + default: + m_log.fatal(root->loc(), "cannot find type of node '" + root->string() + "'"); + } + + return nullptr; + } +} diff --git a/lib/TypeResolver.hpp b/lib/TypeResolver.hpp new file mode 100644 index 0000000..e3d7045 --- /dev/null +++ b/lib/TypeResolver.hpp @@ -0,0 +1,23 @@ +#ifndef roza_TYPERESOLVER_HPP +#define roza_TYPERESOLVER_HPP + +#include "commons.hpp" +#include "Type.hpp" +#include "StatusLog.hpp" +#include "Node.hpp" + +namespace roza +{ + class TypeResolver + { + public: + explicit TypeResolver(StatusLog& log); + virtual ~TypeResolver(); + + std::shared_ptr find(std::shared_ptr root); + private: + StatusLog& m_log; + }; +} + +#endif diff --git a/lib/VM.cpp b/lib/VM.cpp new file mode 100644 index 0000000..70137c7 --- /dev/null +++ b/lib/VM.cpp @@ -0,0 +1,54 @@ +#include "VM.hpp" + +namespace roza +{ + /*explicit*/ VM::VM(StatusLog& log) + : m_log { log } + { + } + + /*virtual*/ VM::~VM() + { + } + + void VM::exec(std::shared_ptr program) + { + while (m_pc < program->size()) + { + switch (program->opcode(m_pc)) + { + case OP_PUSH_CONST: { + param_t param = *program->param(m_pc); + push(param); + m_pc++; + } break; + + case OP_POP: { + pop(); + m_pc++; + } break; + + default: + m_log.fatal(SrcLoc {}, + std::string() + + "cannot execute opcode '" + + OpcodeStr[program->opcode(m_pc)] + + "'"); break; + } + } + } + + void VM::push(param_t param) + { + m_stack.push_back(param); + } + + param_t VM::pop() + { + assert(m_stack.size() > 0); + + param_t res = m_stack.back(); + m_stack.pop_back(); + return res; + } +} diff --git a/lib/VM.hpp b/lib/VM.hpp new file mode 100644 index 0000000..bb37109 --- /dev/null +++ b/lib/VM.hpp @@ -0,0 +1,28 @@ +#ifndef roza_VM_HPP +#define roza_VM_HPP + +#include "commons.hpp" +#include "Program.hpp" +#include "StatusLog.hpp" + +namespace roza +{ + class VM + { + public: + explicit VM(StatusLog& log); + virtual ~VM(); + + void exec(std::shared_ptr program); + + private: + StatusLog& m_log; + std::vector m_stack; + size_t m_pc = 0; + + void push(param_t param); + param_t pop(); + }; +} + +#endif diff --git a/lib/Value.cpp b/lib/Value.cpp new file mode 100644 index 0000000..51d5648 --- /dev/null +++ b/lib/Value.cpp @@ -0,0 +1,25 @@ +#include "Value.hpp" + +namespace roza +{ + /*explicit*/ Value::Value(int value, SrcLoc loc) + : m_type { std::make_shared(BaseType::TY_INT) } + , m_int_val { value } + , m_loc { loc } + { + } + + /*virtual*/ Value::~Value() + { + } + + std::string Value::string() const + { + if (m_type->equals(TY_INT)) + { + return std::to_string(m_int_val); + } + + assert("cannot stringify unknown value " && 0); + } +} diff --git a/lib/Value.hpp b/lib/Value.hpp new file mode 100644 index 0000000..7276ccc --- /dev/null +++ b/lib/Value.hpp @@ -0,0 +1,26 @@ +#ifndef roza_VALUE_HPP +#define roza_VALUE_HPP + +#include "commons.hpp" +#include "SrcLoc.hpp" +#include "Type.hpp" + +namespace roza +{ + class Value + { + public: + explicit Value(int value, SrcLoc loc); + virtual ~Value(); + + std::shared_ptr type() const { return m_type; } + std::string string() const; + + private: + std::shared_ptr m_type; + int m_int_val; + SrcLoc m_loc; + }; +} + +#endif diff --git a/lib/commons.hpp b/lib/commons.hpp new file mode 100644 index 0000000..8be3083 --- /dev/null +++ b/lib/commons.hpp @@ -0,0 +1,13 @@ +#ifndef roza_COMMONS_HPP +#define roza_COMMONS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include "mutils.hpp" + +#endif diff --git a/lib/mutils.hpp b/lib/mutils.hpp new file mode 100644 index 0000000..f756607 --- /dev/null +++ b/lib/mutils.hpp @@ -0,0 +1,11 @@ +#ifndef roza_MUTILS_HPP +#define roza_MUTILS_HPP + +#define GEN_ENUM(X) X +#define GEN_STRING(X) #X + +#define MAKE_ENUM(MY, NAME) \ + enum NAME { MY(GEN_ENUM) }; \ + constexpr char const* NAME ## Str [] = { MY(GEN_STRING) }; \ + +#endif diff --git a/lib/opcodes.hpp b/lib/opcodes.hpp new file mode 100644 index 0000000..972e5b2 --- /dev/null +++ b/lib/opcodes.hpp @@ -0,0 +1,15 @@ +#ifndef roza_OPCODES_HPP +#define roza_OPCODES_HPP + +#include "commons.hpp" + +#define OPCODE(G) \ + G(OP_PUSH_CONST), \ + G(OP_POP) + +namespace roza +{ + MAKE_ENUM(OPCODE, Opcode) +} + +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..9457ff8 --- /dev/null +++ b/meson.build @@ -0,0 +1,53 @@ +project('roza', + 'cpp', + version: '0.0.0', + default_options: [ + 'warning_level=3', + 'cpp_std=c++17' + ]) + + +roza_lib = static_library( + 'roza', + sources: [ + 'lib/SrcLoc.cpp', + 'lib/StatusLog.cpp', + 'lib/Node.cpp', + 'lib/Lexer.cpp', + 'lib/Parser.cpp', + 'lib/Compiler.cpp', + 'lib/StaticPass.cpp', + 'lib/Program.cpp', + 'lib/VM.cpp', + 'lib/Type.cpp', + 'lib/Value.cpp', + 'lib/TypeResolver.cpp', + ] +) + +roza_dep = declare_dependency(link_with: roza_lib) + +executable('roza', + sources: [ + 'src/main.cpp', + 'src/Args.cpp', + 'src/Loader.cpp', + ], + dependencies: [ + roza_dep + ], + install: true) + +executable('roza-tests', + sources: [ + 'tests/main.cpp', + 'tests/Lexer.cpp', + 'tests/Parser.cpp', + 'tests/Compiler.cpp', + 'tests/TypeResolver.cpp', + 'tests/StaticPass.cpp', + ], + dependencies: [ + dependency('catch2'), + roza_dep + ]) diff --git a/src/Args.cpp b/src/Args.cpp new file mode 100644 index 0000000..b627c7c --- /dev/null +++ b/src/Args.cpp @@ -0,0 +1,79 @@ +#include +#include "Args.hpp" + +/*explicit*/ Args::Args(int argc, char** argv) +{ + for (int i=1; i 1) + { + if (argv[i - 1][0] == '-') + { + if (arg.empty() == false && arg[0] == '-') + { + m_values[argv[i - 1]] = ""; + } + else + { + m_values[argv[i - 1]] = arg; + } + } + } + + if (has_opt("-h", arg) || has_opt("--help", arg)) + { + m_help = true; + } + + if (has_opt("-v", arg) || has_opt("--version", arg)) + { + m_version = true; + } + + if (arg.empty() == false && arg[0] != '-') + { + m_inputs.push_back(arg); + } + } + + if (argc > 1 && argv[argc - 1][0] == '-') + { + m_values[argv[argc - 1]] = ""; + } + +} + +/*virtual*/ Args::~Args() +{ +} + +bool Args::has_opt(std::string const& opt, std::string const& str) const +{ + if (opt.size() == 2 && opt[0] == '-' + && str.size() > 0 && str[0] == '-' + && (str.size() < 2 || str[1] != '-')) + { + for (char c : str) + { + if (c == opt[1]) + { + return true; + } + } + } + + return str == opt; +} + +std::optional Args::get(std::string const& key) const +{ + if (auto itr = m_values.find(key); + itr != std::end(m_values)) + { + return itr->second; + } + + return std::nullopt; +} diff --git a/src/Args.hpp b/src/Args.hpp new file mode 100644 index 0000000..221f3b6 --- /dev/null +++ b/src/Args.hpp @@ -0,0 +1,29 @@ +#ifndef ARGS_HPP +#define ARGS_HPP + +#include +#include +#include +#include + +class Args +{ +public: + explicit Args(int argc, char** argv); + virtual ~Args(); + + bool help() const { return m_help; } + bool version() const { return m_version; } + std::vector inputs() const { return m_inputs; } + + std::optional get(std::string const& key) const; + +private: + bool m_help = false; + bool m_version = false; + std::vector m_inputs; + std::unordered_map m_values; + bool has_opt(std::string const& opt, std::string const& str) const; +}; + +#endif diff --git a/src/Loader.cpp b/src/Loader.cpp new file mode 100644 index 0000000..c7449e2 --- /dev/null +++ b/src/Loader.cpp @@ -0,0 +1,32 @@ +#include +#include +#include "Loader.hpp" + +/*explicit*/ Loader::Loader() +{ +} + +/*virtual*/ Loader::~Loader() +{ +} + +std::string Loader::load(std::filesystem::path file) const +{ + std::ifstream f {file}; + + if (!f) + { + std::cerr << "cannot find '" << file.string() << "'" << std::endl; + exit(-1); + } + + std::string content; + std::string line; + + while (std::getline(f, line)) + { + content += line + (f.eof() ? "" : "\n"); + } + + return content; +} diff --git a/src/Loader.hpp b/src/Loader.hpp new file mode 100644 index 0000000..fb51ac4 --- /dev/null +++ b/src/Loader.hpp @@ -0,0 +1,18 @@ +#ifndef LOADER_HPP +#define LOADER_HPP + +#include +#include + +class Loader +{ +public: + explicit Loader(); + virtual ~Loader(); + + std::string load(std::filesystem::path file) const; + +private: +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..63f266d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,64 @@ +#include +#include "Args.hpp" +#include "Loader.hpp" +#include "../lib/Lexer.hpp" +#include "../lib/Parser.hpp" +#include "../lib/StaticPass.hpp" +#include "../lib/Compiler.hpp" +#include "../lib/VM.hpp" +#include "lib/StatusLog.hpp" + +int main(int argc, char** argv) +{ + Args args { argc, argv }; + + if (args.help()) + { + std::cerr << "Usage: roza [OPTIONS] " << std::endl; + std::cerr << "Options:" << std::endl; + std::cerr << "\t--ast, show the AST" << std::endl; + std::cerr << "\t--code, show the bytecode" << std::endl; + return 0; + } + + if (args.version()) + { + std::cerr << "Roza v0.0.0" << std::endl; + return 0; + } + + Loader loader; + + if (args.inputs().size() > 0) + { + auto source = loader.load(args.inputs()[0]); + roza::SrcLoc loc {args.inputs()[0]}; + roza::StatusLog log; + + auto lexer = std::make_shared(log, loc); + auto parser = std::make_shared(*lexer, log); + auto static_pass = std::make_shared(log); + auto compiler = std::make_shared(log); + auto vm = std::make_shared(log); + + lexer->scan(source); + auto root = parser->parse(); + + if (args.get("--ast")) + { + std::cout << root->string() << std::endl; + } + + static_pass->check(root); + auto prog = compiler->compile(root); + + if (args.get("--code")) + { + std::cout << prog->string() << std::endl; + } + + vm->exec(prog); + } + + return 0; +} diff --git a/tests/Compiler.cpp b/tests/Compiler.cpp new file mode 100644 index 0000000..55ac703 --- /dev/null +++ b/tests/Compiler.cpp @@ -0,0 +1,61 @@ +#include +#include "../lib/Lexer.hpp" +#include "../lib/Parser.hpp" +#include "../lib/Compiler.hpp" +#include "lib/Program.hpp" +#include "lib/opcodes.hpp" + +class CompilerTest +{ +public: + explicit CompilerTest() {} + virtual ~CompilerTest() {} + + std::shared_ptr get_prog(std::string const& source) + { + roza::SrcLoc loc {"compiler_tests"}; + roza::StatusLog log; + roza::Lexer lexer {log, loc}; + roza::Parser parser {lexer, log}; + roza::Compiler compiler {log}; + + lexer.scan(source); + auto node = parser.parse(); + return compiler.compile(node); + } + + void compile_err(std::string const& source) + { + REQUIRE_THROWS(get_prog(source)); + } + + void test_prog(std::shared_ptr prog, + size_t index, + roza::Opcode op, + std::optional param = std::nullopt) + { + REQUIRE(index < prog->size()); + REQUIRE(op == prog->opcode(index)); + + if (param) + { + REQUIRE(param == prog->param(index)); + } + else + { + REQUIRE(!param); + REQUIRE(!prog->param(index)); + } + } + +protected: +}; + +TEST_CASE_METHOD(CompilerTest, "Compiler_int") +{ + auto prog = get_prog(" 43 "); + REQUIRE(2 == prog->size()); + + test_prog(prog, 0, roza::OP_PUSH_CONST, 0); + test_prog(prog, 1, roza::OP_POP); +} diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp new file mode 100644 index 0000000..0fc4494 --- /dev/null +++ b/tests/Lexer.cpp @@ -0,0 +1,41 @@ +#include +#include +#include "../lib/Lexer.hpp" +#include "lib/SrcLoc.hpp" +#include "lib/StatusLog.hpp" + +class LexerTest +{ +public: + explicit LexerTest() {} + virtual ~LexerTest() {} + + std::string get_str(size_t index) + { + auto node = m_lexer.get_or_nullptr(index); + + if (!node) { return ""; } + else { return node->string(); } + } + +protected: + roza::StatusLog m_log; + roza::Lexer m_lexer {m_log, roza::SrcLoc("lexer_tests")}; +}; + +TEST_CASE_METHOD(LexerTest, "Lexer_integers") +{ + m_lexer.scan("45 12 -3"); + REQUIRE("INT[45]" == get_str(0)); + REQUIRE("INT[12]" == get_str(1)); + REQUIRE("INT[-3]" == get_str(2)); + REQUIRE("" == get_str(3)); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_comments") +{ + m_lexer.scan("45 # 12 \n -3"); + REQUIRE("INT[45]" == get_str(0)); + REQUIRE("INT[-3]" == get_str(1)); + REQUIRE("" == get_str(2)); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp new file mode 100644 index 0000000..1337735 --- /dev/null +++ b/tests/Parser.cpp @@ -0,0 +1,51 @@ +#include +#include "../lib/Lexer.hpp" +#include "../lib/Parser.hpp" +#include "lib/SrcLoc.hpp" +#include "lib/StatusLog.hpp" + +class ParserTest +{ +public: + explicit ParserTest() {} + virtual ~ParserTest() {} + + void test_node(std::string const& oracle, std::string const& source) + { + roza::SrcLoc loc {"parser_tests"}; + roza::StatusLog log; + roza::Lexer lexer {log, loc}; + roza::Parser parser {lexer, log}; + + lexer.scan(source); + auto node = parser.parse(); + + REQUIRE(oracle == node->string()); + } + + void test_node_err(std::string const& oracle) + { + roza::SrcLoc loc {"parser_tests"}; + roza::StatusLog log; + roza::Lexer lexer {log, loc}; + roza::Parser parser {lexer, log}; + + lexer.scan(oracle); + REQUIRE_THROWS(parser.parse()); + } + +protected: +}; + +TEST_CASE_METHOD(ParserTest, "Parser_integers") +{ + test_node("PROG", ""); + test_node("PROG(INSTR(INT[27]))", "27"); + test_node("PROG(INSTR(INT[27]))", " 27 "); + + test_node("PROG(INSTR(INT[27])," + "INSTR(INT[9])," + "INSTR(INT[-99]))", "27 \n 9 \n -99"); + + test_node_err("32 14 -12"); +} diff --git a/tests/StaticPass.cpp b/tests/StaticPass.cpp new file mode 100644 index 0000000..52e7a2a --- /dev/null +++ b/tests/StaticPass.cpp @@ -0,0 +1,36 @@ +#include +#include "../lib/Lexer.hpp" +#include "../lib/Parser.hpp" +#include "../lib/StaticPass.hpp" + +class StaticPassTest +{ +public: + explicit StaticPassTest() {} + virtual ~StaticPassTest() {} + + void test_ok(std::string const& source) + { + roza::SrcLoc loc {"compiler_tests"}; + roza::StatusLog log; + roza::Lexer lexer {log, loc}; + roza::Parser parser {lexer, log}; + roza::StaticPass static_pass {log}; + + lexer.scan(source); + auto node = parser.parse(); + static_pass.check(node); + } + + void test_ko(std::string const& source) + { + REQUIRE_THROWS(test_ok(source)); + } + +protected: +}; + +TEST_CASE_METHOD(StaticPassTest, "StaticPass_integer") +{ + test_ok(" 43 "); +} diff --git a/tests/TypeResolver.cpp b/tests/TypeResolver.cpp new file mode 100644 index 0000000..83ea838 --- /dev/null +++ b/tests/TypeResolver.cpp @@ -0,0 +1,43 @@ +#include +#include "../lib/TypeResolver.hpp" +#include "../lib/Lexer.hpp" +#include "../lib/Parser.hpp" +#include "lib/Type.hpp" + +class TypeResolverTest +{ +public: + explicit TypeResolverTest() {} + virtual ~TypeResolverTest() {} + + std::shared_ptr get_ty(std::string const& source) + { + roza::SrcLoc loc {"compiler_tests"}; + roza::StatusLog log; + roza::Lexer lexer {log, loc}; + roza::Parser parser {lexer, log}; + roza::TypeResolver resolver {log}; + + lexer.scan(source); + auto node = parser.parse(); + return resolver.find(node); + } + + void test_ty(std::string const& source, std::shared_ptr ty) + { + REQUIRE(ty); + test_ty(source, *ty); + } + + void test_ty(std::string const& source, roza::Type const& ty) + { + REQUIRE(get_ty(source)->equals(ty)); + } + +protected: +}; + +TEST_CASE_METHOD(TypeResolverTest, "TypeResolver_") +{ + test_ty(" 42 ", std::make_shared(roza::BaseType::TY_INT)); +} 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