diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe97e96 --- /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..dd5fde4 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: build tests + +build: + meson setup build + meson compile -C build + +tests: build + build/skemla-tests + +install: tests + meson install -C build diff --git a/doc/grammar.bnf b/doc/grammar.bnf new file mode 100644 index 0000000..bedb461 --- /dev/null +++ b/doc/grammar.bnf @@ -0,0 +1,2 @@ +PROG ::= BUILTIN* +BUILTINS ::= int | float | bool | string diff --git a/lib/Compiler.cpp b/lib/Compiler.cpp new file mode 100644 index 0000000..0ad53d9 --- /dev/null +++ b/lib/Compiler.cpp @@ -0,0 +1,57 @@ +#include "Compiler.hpp" +#include "Program.hpp" +#include "Value.hpp" +#include "Node.hpp" +#include "lib/opcodes.hpp" + +namespace sk +{ + /*explicit*/ Compiler::Compiler() + { + } + + /*virtual*/ Compiler::~Compiler() + { + } + + void Compiler::compile(std::shared_ptr node, + std::shared_ptr program) + { + switch (node->type()) + { + case NODE_PROG: { + for (size_t i=0; isize(); i++) + { + compile(node->child(i).lock(), program); + } + } break; + + case NODE_INT: { + auto val = Value::make_int(std::stoi(node->repr())); + program->push_instr(OPCODE_PUSH_CONST, program->push_value(val)); + } break; + + case NODE_FLOAT: { + auto val = Value::make_float(std::stof(node->repr())); + program->push_instr(OPCODE_PUSH_CONST, program->push_value(val)); + } break; + + case NODE_BOOL: { + auto val = Value::make_bool(node->repr() == "true"); + program->push_instr(OPCODE_PUSH_CONST, program->push_value(val)); + } break; + + case NODE_STRING: { + auto val = Value::make_string(node->repr()); + program->push_instr(OPCODE_PUSH_CONST, program->push_value(val)); + } break; + + default: + throw std::runtime_error {std::string() + + "cannot compile unknown node '" + + NodeTypeStr[node->type()] + + "'"}; + } + } + +} diff --git a/lib/Compiler.hpp b/lib/Compiler.hpp new file mode 100644 index 0000000..5008385 --- /dev/null +++ b/lib/Compiler.hpp @@ -0,0 +1,23 @@ +#ifndef sk_COMPILER_HPP +#define sk_COMPILER_HPP + +#include "commons.hpp" + +namespace sk +{ + class Program; + class Node; + + class Compiler + { + public: + explicit Compiler(); + virtual ~Compiler(); + + void compile(std::shared_ptr node, + std::shared_ptr program); + private: + }; +} + +#endif diff --git a/lib/Factory.cpp b/lib/Factory.cpp new file mode 100644 index 0000000..8851a0a --- /dev/null +++ b/lib/Factory.cpp @@ -0,0 +1,28 @@ +#include "Factory.hpp" + +namespace sk +{ + /*explicit*/ Factory::Factory(std::filesystem::path path + , Logger& logger) + : m_path { path } + , m_logger { logger } + { + } + + /*virtual*/ Factory::~Factory() + { + } + + std::shared_ptr Factory::make_lexer() + { + auto lexer = std::make_shared(m_path, m_logger); + return lexer; + } + + std::shared_ptr Factory::make_parser() + { + auto lexer = make_lexer(); + auto parser = std::make_shared(lexer, m_logger); + return parser; + } +} diff --git a/lib/Factory.hpp b/lib/Factory.hpp new file mode 100644 index 0000000..4008003 --- /dev/null +++ b/lib/Factory.hpp @@ -0,0 +1,26 @@ +#ifndef sk_FACTORY_HPP +#define sk_FACTORY_HPP + +#include "commons.hpp" +#include "Logger.hpp" +#include "Lexer.hpp" +#include "Parser.hpp" + +namespace sk +{ + class Factory + { + public: + explicit Factory(std::filesystem::path path, Logger& logger); + virtual ~Factory(); + + std::shared_ptr make_lexer(); + std::shared_ptr make_parser(); + + private: + std::filesystem::path m_path; + Logger& m_logger; + }; +} + +#endif diff --git a/lib/Lexer.cpp b/lib/Lexer.cpp new file mode 100644 index 0000000..336544d --- /dev/null +++ b/lib/Lexer.cpp @@ -0,0 +1,286 @@ +#include "Lexer.hpp" +#include "lib/Node.hpp" + +namespace sk +{ + /*explicit*/ Lexer::Lexer(std::filesystem::path path, Logger& logger) + : m_loc {path, 1} + , m_logger { logger } + { + std::vector> texts = { + }; + + std::vector> keywords = { + {"true", NODE_BOOL, true}, + {"false", NODE_BOOL, true} + }; + + for (auto entry: texts) + { + m_scanners.push_back(std::bind(&Lexer::scan_text, + this, + std::get<0>(entry), + std::get<1>(entry), + std::get<2>(entry))); + } + + for (auto entry: keywords) + { + m_scanners.push_back(std::bind(&Lexer::scan_keyword, + this, + std::get<0>(entry), + std::get<1>(entry), + std::get<2>(entry))); + } + + m_scanners.push_back(std::bind(&Lexer::scan_string, this)); + m_scanners.push_back(std::bind(&Lexer::scan_float, 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 Lexer::next() + { + size_t cursor = m_cursor; + std::shared_ptr node; + + skip_spaces(); + + while (m_cursor < m_source.size() + && m_source[m_cursor] == ';') + { + while (m_cursor < m_source.size() + && m_source[m_cursor] != '\n') + { + m_cursor++; + } + + skip_spaces(); + } + + for (auto scanner: m_scanners) + { + ScanInfo info = scanner(); + + if (info.ok && info.cursor > cursor) + { + node = std::make_shared(info.type, info.repr, m_loc); + cursor = info.cursor; + } + } + + if (!node + && m_cursor < m_source.size()) + { + std::stringstream ss; + std::string text; + size_t cursor = m_cursor; + while (cursor < m_source.size() + && !std::isspace(m_source[cursor])) + { + text += m_source[cursor]; + cursor++; + } + + ss << "invalid token '" << text << "'."; + + m_logger.log(m_loc, + LOGGER_ERROR, + ss); + } + + m_cursor = cursor; + + return node; + } + + void Lexer::skip_spaces() + { + while (m_cursor < m_source.size() + && std::isspace(m_source[m_cursor])) + { + if (m_source[m_cursor] == '\n') + { + m_loc = Loc {m_loc.path(), m_loc.line() + 1}; + } + + m_cursor++; + } + } + + ScanInfo Lexer::scan_int() + { + size_t cursor = m_cursor; + std::string repr; + + while (cursor < m_source.size() + && std::isdigit(m_source[cursor])) + { + repr += m_source[cursor]; + cursor++; + } + + if (repr.empty() == false) + { + return ScanInfo { + true, + cursor, + repr, + NODE_INT + }; + } + + return ScanInfo {}; + } + + ScanInfo Lexer::scan_float() + { + size_t cursor = m_cursor; + std::string repr; + + while (cursor < m_source.size() + && std::isdigit(m_source[cursor])) + { + repr += m_source[cursor]; + cursor++; + } + + if (cursor >= m_source.size() + || m_source[cursor] != '.') + { + return ScanInfo {}; + } + + repr += "."; + cursor++; + + while (cursor < m_source.size() + && std::isdigit(m_source[cursor])) + { + repr += m_source[cursor]; + cursor++; + } + + if (repr.empty() == false && repr[0] != '.' + && repr[repr.size() - 1] != '.') + { + return ScanInfo { + true, + cursor, + repr, + NODE_FLOAT + }; + } + + return ScanInfo {}; + } + + ScanInfo Lexer::scan_text(std::string const& text, + NodeType type, + bool value) + { + if (m_cursor + text.size() > m_source.size()) + { + return ScanInfo {}; + } + + for (size_t i=0; i m_source.size()) + { + return ScanInfo {}; + } + + for (size_t i=0; i= m_source.size() + || m_source[cursor] != '\'') + { + return ScanInfo { + }; + } + + cursor++; + repr += "'"; + + while (cursor < m_source.size() + && m_source[cursor] != '\'') + { + repr += m_source[cursor]; + cursor++; + } + + if (cursor >= m_source.size() + || m_source[cursor] != '\'') + { + return ScanInfo { + }; + } + + cursor++; + repr += "'"; + + return ScanInfo { + true, + cursor, + repr, + NODE_STRING + }; + } +} diff --git a/lib/Lexer.hpp b/lib/Lexer.hpp new file mode 100644 index 0000000..18cc8a9 --- /dev/null +++ b/lib/Lexer.hpp @@ -0,0 +1,54 @@ +#ifndef sk_LEXER_HPP +#define sk_LEXER_HPP + +#include "commons.hpp" +#include "Node.hpp" +#include "Logger.hpp" + +namespace sk +{ + SK_ERROR(lexical_error); + + struct ScanInfo { + bool ok = false; + size_t cursor = 0; + std::string repr; + NodeType type; + }; + + using scanner_t = std::function; + + class Lexer + { + public: + explicit Lexer(std::filesystem::path path, Logger& logger); + virtual ~Lexer(); + + Loc loc() const { return m_loc; } + + void scan(std::string const& source); + std::shared_ptr next(); + + private: + Loc m_loc; + Logger& m_logger; + std::string m_source; + size_t m_cursor = 0; + std::vector m_scanners; + + void skip_spaces(); + ScanInfo scan_int(); + ScanInfo scan_float(); + ScanInfo scan_text(std::string const& text, + NodeType type, + bool value); + + ScanInfo scan_keyword(std::string const& text, + NodeType type, + bool value); + + ScanInfo scan_string(); + }; +} + +#endif diff --git a/lib/Loc.cpp b/lib/Loc.cpp new file mode 100644 index 0000000..7d96fd6 --- /dev/null +++ b/lib/Loc.cpp @@ -0,0 +1,14 @@ +#include "Loc.hpp" + +namespace sk +{ + /*explicit*/ Loc::Loc(std::filesystem::path path, int line) + : m_path { path } + , m_line { line } + { + } + + /*virtual*/ Loc::~Loc() + { + } +} diff --git a/lib/Loc.hpp b/lib/Loc.hpp new file mode 100644 index 0000000..834afb1 --- /dev/null +++ b/lib/Loc.hpp @@ -0,0 +1,23 @@ +#ifndef sk_LOC_HPP +#define sk_LOC_HPP + +#include "commons.hpp" + +namespace sk +{ + 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/lib/Logger.cpp b/lib/Logger.cpp new file mode 100644 index 0000000..f9791af --- /dev/null +++ b/lib/Logger.cpp @@ -0,0 +1,12 @@ +#include "Logger.hpp" + +namespace sk +{ + /*explicit*/ Logger::Logger() + { + } + + /*virtual*/ Logger::~Logger() + { + } +} diff --git a/lib/Logger.hpp b/lib/Logger.hpp new file mode 100644 index 0000000..bae40b4 --- /dev/null +++ b/lib/Logger.hpp @@ -0,0 +1,54 @@ +#ifndef sk_LOGGER_HPP +#define sk_LOGGER_HPP + +#include "commons.hpp" + +#include "Loc.hpp" + +#define LOGGER_TYPE(G) \ + G(LOGGER_ERROR) + +namespace sk +{ + SK_ENUM(Logger, LOGGER_TYPE); + + class Logger + { + public: + explicit Logger(); + virtual ~Logger(); + + template + void log(Loc loc, LoggerType type, std::stringstream const& what); + + template + void log(Loc loc, LoggerType type, std::string const& what); + private: + }; + + template + void Logger::log(Loc loc, LoggerType type, std::stringstream const& what) + { + log(loc, type, what.str()); + } + + template + void Logger::log(Loc loc, LoggerType type, std::string const& what) + { + std::string type_str = std::string(LoggerTypeStr[type]) + .substr(std::string("LOGGER_").size()); + std::stringstream msg; + + switch (type) + { + case LOGGER_ERROR: { + msg << loc.path().string() << ":" << loc.line(); + msg << " " << type_str << " " << what; + throw T {msg.str()}; + } break; + + } + } +} + +#endif diff --git a/lib/Node.cpp b/lib/Node.cpp new file mode 100644 index 0000000..65b8be7 --- /dev/null +++ b/lib/Node.cpp @@ -0,0 +1,54 @@ +#include "Node.hpp" + +namespace sk +{ + /*explicit*/ Node::Node(NodeType type, std::string const repr, Loc loc) + : m_type { type } + , m_repr { repr } + , m_loc { loc } + { + } + + /*virtual*/ Node::~Node() + { + } + + void Node::add_child(std::shared_ptr child) + { + m_children.push_back(child); + } + + std::weak_ptr 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()) + { + ss << "[" << m_repr << "]"; + } + + if (!m_children.empty()) + { + ss << "("; + std::string sep; + + for (auto child: m_children) + { + ss << sep << child->string(); + sep = ","; + } + + ss << ")"; + } + + return ss.str(); + } +} diff --git a/lib/Node.hpp b/lib/Node.hpp new file mode 100644 index 0000000..189262e --- /dev/null +++ b/lib/Node.hpp @@ -0,0 +1,43 @@ +#ifndef sk_NODE_HPP +#define sk_NODE_HPP + +#include "commons.hpp" +#include "Loc.hpp" + +#define NODE_TYPE(G) \ + G(NODE_PROG), \ + G(NODE_INT), \ + G(NODE_FLOAT), \ + G(NODE_BOOL), \ + G(NODE_STRING), + +namespace sk +{ + SK_ENUM(Node, NODE_TYPE); + + class Node + { + public: + explicit Node(NodeType type, std::string const repr, Loc 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 child); + std::weak_ptr child(size_t index) const; + + std::string string() const; + + private: + NodeType m_type; + std::string m_repr; + Loc m_loc; + + std::vector> m_children; + }; +} + +#endif diff --git a/lib/Parser.cpp b/lib/Parser.cpp new file mode 100644 index 0000000..6730c32 --- /dev/null +++ b/lib/Parser.cpp @@ -0,0 +1,99 @@ +#include "Parser.hpp" +#include "lib/Node.hpp" + +namespace sk +{ + /*explicit*/ Parser::Parser(std::shared_ptr lexer, Logger& logger) + : m_lexer { lexer } + , m_logger { logger } + { + } + + /*virtual*/ Parser::~Parser() + { + } + + std::shared_ptr Parser::parse(std::string const& source) + { + m_lexer->scan(source); + std::shared_ptr node; + + while ( (node = m_lexer->next()) ) + { + m_tokens.push_back(node); + } + + return parse_prog(); + } + + std::shared_ptr Parser::consume() + { + assert(m_cursor < m_tokens.size()); + + auto root = m_tokens[m_cursor]; + m_cursor++; + return root; + } + + std::shared_ptr Parser::consume(NodeType type) + { + if (type != m_tokens[m_cursor]->type()) + { + std::stringstream ss; + ss << "expected '" + << NodeTypeStr[type] + << "' node, got '" + << NodeTypeStr[m_tokens[m_cursor]->type()] + << "'"; + + m_logger.log(m_tokens[m_cursor]->loc(), + LOGGER_ERROR, + ss); + } + + return consume(); + } + + bool Parser::type_is(NodeType type) + { + return m_cursor < m_tokens.size() + && m_tokens[m_cursor]->type() == type; + } + + Loc Parser::loc() const + { + assert(m_cursor < m_tokens.size()); + return m_tokens[m_cursor]->loc(); + } + + std::shared_ptr Parser::parse_prog() + { + auto root = std::make_shared(NODE_PROG, "", m_lexer->loc()); + + while (m_cursor < m_tokens.size()) + { + root->add_child(parse_builtins()); + } + + return root; + } + + std::shared_ptr Parser::parse_builtins() + { + if (type_is(NODE_INT) + || type_is(NODE_FLOAT) + || type_is(NODE_BOOL) + || type_is(NODE_STRING)) + { + return consume(); + } + + std::stringstream ss; + ss << "unknown node of type '" + << NodeTypeStr[m_tokens[m_cursor]->type()] + << "'"; + + m_logger.log(loc(), LOGGER_ERROR, ss); + return nullptr; + } +} diff --git a/lib/Parser.hpp b/lib/Parser.hpp new file mode 100644 index 0000000..3a6016a --- /dev/null +++ b/lib/Parser.hpp @@ -0,0 +1,34 @@ +#ifndef sk_PARSER_HPP +#define sk_PARSER_HPP + +#include "commons.hpp" +#include "Lexer.hpp" + +namespace sk +{ + SK_ERROR(syntax_error); + + class Parser + { + public: + explicit Parser(std::shared_ptr lexer, Logger& logger); + virtual ~Parser(); + + std::shared_ptr parse(std::string const& source); + private: + std::shared_ptr m_lexer; + Logger& m_logger; + std::vector> m_tokens; + size_t m_cursor = 0; + + std::shared_ptr consume(); + std::shared_ptr consume(NodeType type); + bool type_is(NodeType type); + Loc loc() const; + + std::shared_ptr parse_prog(); + std::shared_ptr parse_builtins(); + }; +} + +#endif diff --git a/lib/Program.cpp b/lib/Program.cpp new file mode 100644 index 0000000..d46f968 --- /dev/null +++ b/lib/Program.cpp @@ -0,0 +1,74 @@ +#include "Program.hpp" +#include "Value.hpp" + +namespace sk +{ + /*explicit*/ Program::Program() + { + } + + /*virtual*/ Program::~Program() + { + } + + std::string Program::string() const + { + std::stringstream ss; + size_t i = 0; + + for (auto const& instr: m_instrs) + { + ss << i << "\t"; + ss << std::string(OpcodeTypeStr[instr.opcode]) + .substr(std::string("OPCODE_").size()); + + if (instr.param) + { + ss << "\t" << *instr.param; + } + + ss << "\n"; + i++; + } + + return ss.str(); + } + + void Program::push_instr(OpcodeType opcode, + std::optional param) + { + m_instrs.push_back({opcode, param}); + } + + Instr const& Program::instr(size_t index) const + { + assert(index < size()); + return m_instrs[index]; + } + + Instr& Program::instr(size_t index) + { + assert(index < size()); + return m_instrs[index]; + } + + size_t Program::push_value(std::shared_ptr value) + { + for (size_t i=0; iequals(*m_values[i])) + { + return i; + } + } + + m_values.push_back(value); + return m_values.size() - 1; + } + + std::shared_ptr Program::value(size_t index) + { + assert(index < m_values.size()); + return m_values[index]; + } +} diff --git a/lib/Program.hpp b/lib/Program.hpp new file mode 100644 index 0000000..317c186 --- /dev/null +++ b/lib/Program.hpp @@ -0,0 +1,42 @@ +#ifndef sk_PROGRAM_HPP +#define sk_PROGRAM_HPP + +#include "commons.hpp" +#include "opcodes.hpp" + +namespace sk +{ + struct Instr { + OpcodeType opcode; + std::optional param; + }; + + class Value; + + class Program + { + public: + explicit Program(); + virtual ~Program(); + + std::string string() const; + + size_t size() const { return m_instrs.size(); } + size_t value_size() const { return m_values.size(); } + + void push_instr(OpcodeType opcode, + std::optional param = std::nullopt); + + Instr const& instr(size_t index) const; + Instr& instr(size_t index); + + size_t push_value(std::shared_ptr value); + std::shared_ptr value(size_t index); + + private: + std::vector m_instrs; + std::vector> m_values; + }; +} + +#endif diff --git a/lib/Type.cpp b/lib/Type.cpp new file mode 100644 index 0000000..8b9a83d --- /dev/null +++ b/lib/Type.cpp @@ -0,0 +1,13 @@ +#include "Type.hpp" + +namespace sk +{ + /*explicit*/ Type::Type(TypeType type) + : m_type { type } + { + } + + /*virtual*/ Type::~Type() + { + } +} diff --git a/lib/Type.hpp b/lib/Type.hpp new file mode 100644 index 0000000..7d10059 --- /dev/null +++ b/lib/Type.hpp @@ -0,0 +1,29 @@ +#ifndef sk_TYPE_HPP +#define sk_TYPE_HPP + +#include "commons.hpp" + +#define TYPE_TYPE(G) \ + G(TYPE_INT), \ + G(TYPE_FLOAT), \ + G(TYPE_BOOL), \ + G(TYPE_STRING), + +namespace sk +{ + SK_ENUM(Type, TYPE_TYPE); + + class Type + { + public: + explicit Type(TypeType type); + virtual ~Type(); + + TypeType type() const { return m_type; } + + private: + TypeType m_type; + }; +} + +#endif diff --git a/lib/VM.cpp b/lib/VM.cpp new file mode 100644 index 0000000..c728b02 --- /dev/null +++ b/lib/VM.cpp @@ -0,0 +1,86 @@ +#include "VM.hpp" +#include "Value.hpp" +#include "lib/opcodes.hpp" + +namespace sk +{ + /*explicit*/ VM::VM() + { + } + + /*virtual*/ VM::~VM() + { + } + + std::string VM::string() const + { + std::stringstream ss; + + ss << "--- constants ---" << "\n"; + for (size_t i=0; ivalue_size(); i++) + { + ss << i << "\t" << m_frames.back().program->value(i)->string() + << "\n"; + } + + ss << "--- stack ---" << "\n"; + + for (size_t i=0; i program) + { + Frame frame; + frame.program = program; + m_frames.push_back(frame); + m_pc = 0; + + exec(); + } + + void VM::exec() + { + while (m_pc < program()->size()) + { + Instr instr = program()->instr(m_pc); + + switch (instr.opcode) + { + + case OPCODE_PUSH_CONST: { + push(*instr.param); + m_pc++; + } break; + + default: + throw std::runtime_error {std::string() + + "vm cannot execute unknown opcode '" + + OpcodeTypeStr[instr.opcode] + + "'"}; + } + } + } + + std::shared_ptr VM::program() const + { + return m_frames.back().program; + } + + void VM::push(param_t param) + { + m_stack.push_back(param); + } + + param_t VM::pop() + { + assert(m_stack.empty() == false); + auto param = m_stack.back(); + m_stack.pop_back(); + return param; + } +} diff --git a/lib/VM.hpp b/lib/VM.hpp new file mode 100644 index 0000000..f9c18b9 --- /dev/null +++ b/lib/VM.hpp @@ -0,0 +1,37 @@ +#ifndef sk_VM_HPP +#define sk_VM_HPP + +#include "commons.hpp" +#include "Program.hpp" + +namespace sk +{ + struct Frame { + std::shared_ptr program; + }; + + class VM + { + public: + explicit VM(); + virtual ~VM(); + + std::string string() const; + void exec(std::shared_ptr program); + + private: + std::vector m_frames; + std::vector m_stack; + + size_t m_pc = 0; + + void exec(); + + std::shared_ptr program() const; + + 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..e9b31b7 --- /dev/null +++ b/lib/Value.cpp @@ -0,0 +1,80 @@ +#include "Value.hpp" +#include "Type.hpp" + +namespace sk +{ + /*static*/ std::shared_ptr + Value::make_int(int value) + { + auto res = + std::make_shared(std::make_shared(TYPE_INT)); + res->m_int_val = value; + return res; + } + + /*static*/ std::shared_ptr + Value::make_float(float value) + { + auto res = + std::make_shared(std::make_shared(TYPE_FLOAT)); + res->m_float_val = value; + return res; + } + + /*static*/ std::shared_ptr + Value::make_bool(bool value) + { + auto res = + std::make_shared(std::make_shared(TYPE_BOOL)); + res->m_bool_val = value; + return res; + } + + /*static*/ std::shared_ptr + Value::make_string(std::string const& value) + { + auto res = + std::make_shared(std::make_shared(TYPE_STRING)); + res->m_string_val = value; + return res; + } + + /*explicit*/ Value::Value(std::shared_ptr type) + : m_type { type } + { + } + + /*virtual*/ Value::~Value() + { + } + + bool Value::equals(Value const& rhs) const + { + return m_int_val == rhs.m_int_val + && m_float_val == rhs.m_float_val + && m_bool_val == rhs.m_bool_val + && m_string_val == rhs.m_string_val; + } + + std::string Value::string() const + { + switch (m_type->type()) + { + case TYPE_INT: return std::to_string(as_int()); + case TYPE_FLOAT: return std::to_string(as_float()); + case TYPE_BOOL: return as_bool() ? "true" : "false"; + case TYPE_STRING: return as_string().substr(1, as_string().size()-2); + + default: + throw std::runtime_error {std::string() + + "cannot stringify unknown type '" + + TypeTypeStr[m_type->type()] + + "'"}; + } + } + + std::weak_ptr Value::type() const + { + return m_type; + } +} diff --git a/lib/Value.hpp b/lib/Value.hpp new file mode 100644 index 0000000..3ac176a --- /dev/null +++ b/lib/Value.hpp @@ -0,0 +1,42 @@ +#ifndef sk_VALUE_HPP +#define sk_VALUE_HPP + +#include "commons.hpp" + +namespace sk +{ + class Type; + + class Value + { + public: + static std::shared_ptr make_int(int value); + static std::shared_ptr make_float(float value); + static std::shared_ptr make_bool(bool value); + static std::shared_ptr make_string(std::string const& value); + + explicit Value(std::shared_ptr type); + virtual ~Value(); + + bool equals(Value const& rhs) const; + + std::string string() const; + + + std::weak_ptr type() const; + + int as_int() const { return *m_int_val; } + float as_float() const { return *m_float_val; } + bool as_bool() const { return *m_bool_val; } + std::string as_string() const { return *m_string_val; } + + private: + std::shared_ptr m_type; + std::optional m_int_val; + std::optional m_float_val; + std::optional m_string_val; + std::optional m_bool_val; + }; +} + +#endif diff --git a/lib/commons.hpp b/lib/commons.hpp new file mode 100644 index 0000000..7e60759 --- /dev/null +++ b/lib/commons.hpp @@ -0,0 +1,19 @@ +#ifndef sk_COMMONS_HPP +#define sk_COMMONS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mutils.hpp" + +using param_t = size_t; + +#endif diff --git a/lib/mutils.hpp b/lib/mutils.hpp new file mode 100644 index 0000000..668069b --- /dev/null +++ b/lib/mutils.hpp @@ -0,0 +1,18 @@ +#ifndef sk_MUTILS_HPP +#define sk_MUTILS_HPP + +#include + +#define ENUM_ENUM(X) X +#define ENUM_STRING(X) #X + +#define SK_ENUM(PREFIX, ENUM) \ + enum PREFIX ## Type { ENUM(ENUM_ENUM) }; \ + constexpr char const* PREFIX ## TypeStr [] = { ENUM(ENUM_STRING) } + +#define SK_ERROR(NAME) \ + struct NAME : public std::runtime_error { \ + NAME (std::string const& what) : std::runtime_error(what) {} \ + } + +#endif diff --git a/lib/opcodes.hpp b/lib/opcodes.hpp new file mode 100644 index 0000000..256892f --- /dev/null +++ b/lib/opcodes.hpp @@ -0,0 +1,11 @@ +#ifndef sk_OPCODES_HPP +#define sk_OPCODES_HPP + +#include "commons.hpp" + +#define OPCODE(G) \ + G(OPCODE_PUSH_CONST) + +SK_ENUM(Opcode, OPCODE); + +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..cd3d412 --- /dev/null +++ b/meson.build @@ -0,0 +1,45 @@ +project('skemla', + 'cpp', + version: '0.0.0', + default_options: [ + 'warning_level=3', + 'cpp_std=c++17' + ]) + +skemla_lib = static_library('skemla', sources: [ + 'lib/Node.cpp', + 'lib/Lexer.cpp', + 'lib/Parser.cpp', + 'lib/Loc.cpp', + 'lib/Logger.cpp', + 'lib/Factory.cpp', + + 'lib/Type.cpp', + 'lib/Value.cpp', + + 'lib/Compiler.cpp', + 'lib/Program.cpp', + 'lib/VM.cpp', +]) + +skemla_dep = declare_dependency(link_with: skemla_lib) + +executable('skemla', + sources: [ + 'src/main.cpp' + ], + dependencies: [ + skemla_dep + ], + install: true) + +executable('skemla-tests', + sources: [ + 'tests/main.cpp', + 'tests/Lexer.cpp', + 'tests/Parser.cpp', + ], + dependencies: [ + skemla_dep, + dependency('catch2') + ]) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ada81e0 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include "lib/Factory.hpp" +#include "lib/Compiler.hpp" +#include "lib/VM.hpp" + +int main(int argc, char** argv) +{ + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0}, + }; + + while (true) + { + int option_index = 0; + int c = getopt_long(argc, argv + , "hv" + , long_options, &option_index); + + if (c == -1) + { + break; + } + + switch (c) + { + case 'h': { + std::cout << "Usage: skemla [OPTIONS] filename" << std::endl; + std::cout << "OPTIONS:" << std::endl; + std::cout << "\t" << "-h, --help" + << "\t" << "Show this help message" << std::endl; + std::cout << "\t" << "-v, --version" + << "\t" << "Show Skemla version" << std::endl; + + return 0; + } break; + + case 'v': { + std::cout << "skemla v0.0.0" << std::endl; + return 0; + } break; + + default: abort(); + } + } + + std::string source; + + // Load Sources + { + std::ifstream file {argv[optind]}; + + if(!file) + { + std::cerr << "cannot open file '" + << argv[optind] << "'" << std::endl; + abort(); + } + + std::string line; + + while (std::getline(file, line)) + { + source += line + (file.eof() ? "" : "\n"); + } + } + + sk::Logger logger; + sk::Factory factory {argv[optind], logger}; + + auto parser = factory.make_parser(); + auto root = parser->parse(source); + + sk::Compiler compiler; + auto program = std::make_shared(); + compiler.compile(root, program); + + sk::VM vm; + vm.exec(program); + + std::cout << vm.string() << std::endl; + + return 0; +} diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp new file mode 100644 index 0000000..ff53fa9 --- /dev/null +++ b/tests/Lexer.cpp @@ -0,0 +1,87 @@ +#include +#include "../lib/Factory.hpp" + +class LexerTest +{ +public: + explicit LexerTest() {} + virtual ~LexerTest() {} + + void test_next(sk::Lexer& lexer, std::string const& oracle) + { + auto token = lexer.next(); + REQUIRE(token); + REQUIRE(oracle == token->string()); + } + + void test_end(sk::Lexer& lexer) + { + auto token = lexer.next(); + REQUIRE(nullptr == token); + } + +protected: + sk::Logger m_logger; + sk::Factory m_factory {"tests/lexer", m_logger}; +}; + +TEST_CASE_METHOD(LexerTest, "Lexer_unknown_token") +{ + auto lexer = m_factory.make_lexer(); + lexer->scan(" ยง "); + + REQUIRE_THROWS_AS(lexer->next(), sk::lexical_error); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_int") +{ + auto lexer = m_factory.make_lexer(); + + lexer->scan(" 47 29 3 "); + test_next(*lexer, "INT[47]"); + test_next(*lexer, "INT[29]"); + test_next(*lexer, "INT[3]"); + test_end(*lexer); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_float") +{ + auto lexer = m_factory.make_lexer(); + + lexer->scan(" 27.0 3.33 "); + test_next(*lexer, "FLOAT[27.0]"); + test_next(*lexer, "FLOAT[3.33]"); + test_end(*lexer); +} + + +TEST_CASE_METHOD(LexerTest, "Lexer_bools") +{ + auto lexer = m_factory.make_lexer(); + + lexer->scan(" true false "); + test_next(*lexer, "BOOL[true]"); + test_next(*lexer, "BOOL[false]"); + test_end(*lexer); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_string") +{ + auto lexer = m_factory.make_lexer(); + + lexer->scan(" ' true' 'false ' 'both' "); + test_next(*lexer, "STRING[' true']"); + test_next(*lexer, "STRING['false ']"); + test_next(*lexer, "STRING['both']"); + test_end(*lexer); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_comments") +{ + auto lexer = m_factory.make_lexer(); + + lexer->scan(" ' true' ;; 'false ' \n 'both' "); + test_next(*lexer, "STRING[' true']"); + test_next(*lexer, "STRING['both']"); + test_end(*lexer); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp new file mode 100644 index 0000000..251a53a --- /dev/null +++ b/tests/Parser.cpp @@ -0,0 +1,22 @@ +#include +#include "../lib/Factory.hpp" + +class ParserTest +{ +public: + explicit ParserTest() {} + virtual ~ParserTest() {} + +protected: + sk::Logger m_logger; + sk::Factory m_factory {"tests/parser", m_logger}; +}; + +TEST_CASE_METHOD(ParserTest, "Parser_built-in") +{ + auto parser = m_factory.make_parser(); + auto root = parser->parse(" 34 'hello' false"); + + REQUIRE("PROG(INT[34],STRING['hello'],BOOL[false])" + == root->string()); +} 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