From f81284fd37e52780ad9a048db470c556a82b5cb5 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Fri, 31 Jul 2026 15:58:09 +1000 Subject: [PATCH] initial commit --- meson.build | 11 ++ src/compiler/compiler.cpp | 0 src/compiler/compiler.hpp | 0 src/ir/ir.cpp | 0 src/ir/ir.hpp | 0 src/lexer/lexer.cpp | 258 ++++++++++++++++++++++++++++++++++++++ src/lexer/lexer.hpp | 125 ++++++++++++++++++ src/main.cpp | 10 ++ src/parser/parser.cpp | 190 ++++++++++++++++++++++++++++ src/parser/parser.hpp | 95 ++++++++++++++ 10 files changed, 689 insertions(+) create mode 100644 meson.build create mode 100644 src/compiler/compiler.cpp create mode 100644 src/compiler/compiler.hpp create mode 100644 src/ir/ir.cpp create mode 100644 src/ir/ir.hpp create mode 100644 src/lexer/lexer.cpp create mode 100644 src/lexer/lexer.hpp create mode 100644 src/main.cpp create mode 100644 src/parser/parser.cpp create mode 100644 src/parser/parser.hpp diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..5a200a6 --- /dev/null +++ b/meson.build @@ -0,0 +1,11 @@ +project('solstice', 'cpp', version : '0.1.2') + +sources = files( + 'src/main.cpp', + 'src/lexer/lexer.cpp', + 'src/parser/parser.cpp', + 'src/ir/ir.cpp', + 'src/compiler/compiler.cpp' +) + +executable('solsc', sources) diff --git a/src/compiler/compiler.cpp b/src/compiler/compiler.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/compiler/compiler.hpp b/src/compiler/compiler.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/ir/ir.cpp b/src/ir/ir.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/ir/ir.hpp b/src/ir/ir.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/lexer/lexer.cpp b/src/lexer/lexer.cpp new file mode 100644 index 0000000..b9c23f8 --- /dev/null +++ b/src/lexer/lexer.cpp @@ -0,0 +1,258 @@ +#include "lexer.hpp" +#include +#include + +using TT = Solstice::TokenType; + +namespace Solstice { + std::unordered_map stringToTokenType = { + {"type", TT::Kw_Type}, + + {"#cimport", TT::Hash_CImport}, + {"#effect", TT::Hash_Effect}, + + {"::", TT::Assign_Bind}, + {"=", TT::Assign_Set}, + {":", TT::Assign_Type}, + + {"->", TT::Action_Function}, + + {"{", TT::OpenCurly}, + {"}", TT::CloseCurly}, + {"(", TT::OpenParen}, + {")", TT::CloseParen}, + {"<", TT::OpenSpiky}, + {">", TT::CloseSpiky}, + + {"+", TT::Math_Add}, + {"-", TT::Math_Subtract}, + {"*", TT::Math_Multiply}, + {"/", TT::Math_Divide} + }; + + + std::optional Literal::getString() { + if (std::holds_alternative(data)) { + return std::get(data); + } + return {}; + } + + std::optional Literal::getInt() { + if (std::holds_alternative(data)) { + return std::get(data); + } + return {}; + } + + std::optional Literal::getDouble() { + if (std::holds_alternative(data)) { + return std::get(data); + } + return {}; + } + + std::optional Literal::getChar() { + if (std::holds_alternative(data)) { + return std::get(data); + } + return {}; + } + + std::optional Literal::getBool() { + if (std::holds_alternative(data)) { + return std::get(data); + } + return {}; + } + + + std::optional Token::getLiteral() { + if (std::holds_alternative(data)) { + return std::get(data); + } + return {}; + } + + std::optional Token::getIdentifier() { + if (std::holds_alternative(data)) { + return std::get(data); + } + return {}; + } + + + + std::optional Lexer::peek(std::size_t ahead) { + if (current + ahead >= input.size()) { + return {}; + } + return input[current + ahead]; + } + std::optional Lexer::consume() { + if (current + 1 >= input.size()) { + return {}; + } + return input[current++]; + } + + Token Lexer::processToken(const std::string& buf) { + if (stringToTokenType.find(buf) != stringToTokenType.end()) { + return stringToTokenType[buf]; + } + try { + int64_t num = std::stoll(buf); + return Token(num); + } catch (const std::out_of_range& e) { + throw std::runtime_error("Integer is out of range"); + } catch (const std::invalid_argument& e) { + // keep going + } + + try { + double num = std::stod(buf); + return Token(num); + } catch (const std::out_of_range& e) { + throw std::runtime_error("Integer is out of range"); + } catch (const std::invalid_argument& e) { + // keep going + } + + if (buf == "true") { + return Token(true); + } + + if (buf == "false") { + return Token(false); + } + + // assume identifier + return Token(buf); + + } + + const std::vector& Lexer::lex() { + + std::string buf = ""; + + for (;;) { + auto next = consume(); + if (!next.has_value()) { + break; + } + + switch (*next) { + + case '\t': + case '\r': + case ' ': { + if (!buf.empty()) { + output.push_back(processToken(buf)); + buf.clear(); + } + + break; + } + + case '\n': { + if (!buf.empty()) { + output.push_back(processToken(buf)); + buf.clear(); + } + + output.push_back(TT::NewLine); + + break; + } + + // Strings + case '"': { + if (!buf.empty()) { + output.push_back(processToken(buf)); + buf.clear(); + } + for (;;) { + next = consume(); + if (!next.has_value()) { + throw std::runtime_error("unterminated string"); + } + if (*next == '"') { + output.push_back(Literal(buf)); + buf.clear(); + break; + } + + buf += *next; + } + break; + } + + case ':': { + if (!buf.empty()) { + output.push_back(processToken(buf)); + buf.clear(); + } + + if (peek() && *peek() == ':') { + output.push_back(TT::Assign_Bind); + consume(); + } else { + output.push_back(TT::Assign_Type); + } + break; + } + + case '-': { + if (!buf.empty()) { + output.push_back(processToken(buf)); + buf.clear(); + } + + if (peek() && *peek() == '>') { + output.push_back(TT::Action_Function); + consume(); + } else { + output.push_back(TT::Math_Subtract); + } + break; + } + + // all the delimiters + case '<': + case '>': + case '{': + case '}': + case '(': + case ')': + case '+': + case '*': + case '/': + { + if (!buf.empty()) { + output.push_back(processToken(buf)); + buf.clear(); + } + output.push_back(processToken(std::string(1, *next))); + break; + } + + default: { + buf.push_back(*next); + break; + } + + } + } + + // process the last thingy in the buffer + if (!buf.empty()) { + output.push_back(processToken(buf)); + buf.clear(); + } + + return output; + + } + + +} diff --git a/src/lexer/lexer.hpp b/src/lexer/lexer.hpp new file mode 100644 index 0000000..90ba010 --- /dev/null +++ b/src/lexer/lexer.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Solstice { + + enum class TokenType { + None, + + // keywords + Kw_Type, + + // hashops + Hash_CImport, + Hash_Effect, + + // assignment + Assign_Bind, // :: + Assign_Set, // = + Assign_Type, // : + + // actions + Action_Function, // -> + + // math + Math_Add, + Math_Subtract, + Math_Multiply, + Math_Divide, + + // comparison + Comparison_Equal, + Comparison_NotEqual, + Comparison_GreaterThan, + Comparison_LesserThan, + + // structural characters + OpenCurly, + CloseCurly, + OpenParen, + CloseParen, + OpenSpiky, + CloseSpiky, + NewLine, + + // other + Literal, + Identifier, + }; + + enum class LiteralType { + None, String, Int, Double, Char, Bool + }; + + extern std::unordered_map stringToTokenType; + + class Literal { + std::variant data; + + public: + + LiteralType type = LiteralType::None; + + std::optional getString(); + std::optional getInt(); + std::optional getDouble(); + std::optional getChar(); + std::optional getBool(); + + Literal(const std::string& in) : type(LiteralType::String), data(in) {} + Literal(int64_t in) : type(LiteralType::Int), data(in) {} + Literal(double in) : type(LiteralType::Double), data(in) {} + Literal(char in) : type(LiteralType::Char), data(in) {} + Literal(bool in) : type(LiteralType::Bool), data(in) {} + + Literal() : type(LiteralType::None) {} + + }; + + class Token { + + std::variant data; + + public: + + std::optional getLiteral(); + std::optional getIdentifier(); + + TokenType type = TokenType::None; + + Token() = delete; + Token(TokenType type) : type(type) {} + + Token(const Literal& literal) : type(TokenType::Literal), data(literal) {} + Token(const std::string& identifier) : type(TokenType::Identifier), data(identifier) {} + + }; + + class Lexer { + + std::string input; + std::vector output; + + std::size_t current = 0; + + std::optional peek(std::size_t ahead = 0); + std::optional consume(); + + static Token processToken(const std::string& buf); + + public: + + Lexer() = delete; + Lexer(const std::string& input) : input(input) {} + + const std::vector& lex(); + + }; + +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9142062 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,10 @@ +#include "lexer/lexer.hpp" +#include "parser/parser.hpp" + +int main() { + Solstice::Lexer lexer{"2 + 2"}; + auto lexed = lexer.lex(); + + Solstice::Parser parser{lexed}; + auto parsed = parser.parse(); +} diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp new file mode 100644 index 0000000..e79835c --- /dev/null +++ b/src/parser/parser.cpp @@ -0,0 +1,190 @@ +#include "parser.hpp" +#include +#include + +using TT = Solstice::TokenType; + +namespace Solstice { + + std::unordered_map tokToNodeType = { + {TT::Math_Add, NodeType::Add}, + {TT::Math_Subtract, NodeType::Subtract}, + {TT::Math_Multiply, NodeType::Multiply}, + {TT::Math_Divide, NodeType::Divide}, + {TT::Comparison_Equal, NodeType::Equal}, + {TT::Comparison_NotEqual, NodeType::NotEqual}, + {TT::Comparison_GreaterThan, NodeType::GreaterThan}, + {TT::Comparison_LesserThan, NodeType::LesserThan}, + }; + + std::optional Parser::peek(int64_t ahead) { + if (current + ahead >= input.size() || current + ahead < 0) { + return {}; + } + return input[current + ahead]; + } + + std::optional Parser::consume() { + if (current >= input.size()) { + return {}; + } + return input[current++]; + } + + std::optional Parser::getPreviousNode(Node& parent) { + if (parent.children.empty()) { + return {}; + } + Node node = parent.children[parent.children.size() - 1]; + parent.children.pop_back(); + return node; + } + + Node Parser::parseExpr(TT type) { + auto left = getPreviousNode(output); + if (!left.has_value()) { + throw std::runtime_error("Expecting expression on left of '+'"); + } + consume(); // be rid of the + + Precedence precedence = getTokenPrecedence(Token(type)); + auto right = parseOneNode(precedence); + if (!right.has_value()) { + throw std::runtime_error("Expecting expression on right of '+'"); + } + + NodeType nodeType; + if (tokToNodeType.find(type) == tokToNodeType.end()) { + throw std::runtime_error("FIXME couldn't map token type to node type"); + } else { + nodeType = tokToNodeType[type]; + } + + return Node(nodeType, {*left, *right}); + } + + Node Parser::parseLiteral() { + auto current = peek(-1); + if (!current.has_value()) { + throw std::runtime_error("FIXME couldn't get current token"); + } + auto literal = current->getLiteral(); + if (!literal.has_value()) { + throw std::runtime_error("FIXME token with type literal does not hold a literal"); + } + + return Node(*literal); + } + + Node Parser::parseIdentifier() { + auto current = peek(-1); + if (!current.has_value()) { + throw std::runtime_error("FIXME couldn't get current token"); + } + auto id = current->getIdentifier(); + if (!id.has_value()) { + throw std::runtime_error("FIXME token with type identifier does not hold an identifier"); + } + + return Node(*id); + } + + Precedence Parser::getTokenPrecedence(const Token& token) { + static size_t braceCount = 0; + static size_t bracketCount = 0; + static size_t spikyBracketCount = 0; + switch (token.type) { + case TT::OpenCurly: { + braceCount++; + return Precedence::Other; + } + case TT::CloseCurly: { + braceCount--; + return Precedence::Other; + } + case TT::OpenSpiky: { + spikyBracketCount++; + return Precedence::Other; + } + case TT::CloseSpiky: { + spikyBracketCount--; + return Precedence::Other; + } + case TT::OpenParen: { + bracketCount++; + return Precedence::Other; + } + case TT::CloseParen: { + bracketCount--; + return Precedence::Other; + } + default: break; + } + if (braceCount > 0) { + return Precedence::Other; + } + if (bracketCount > 0) { + return Precedence::Other; + } + if (spikyBracketCount > 0) { + return Precedence::Other; + } + switch (token.type) { + case TT::None: + case TT::Kw_Type: + case TT::Hash_CImport: + case TT::Hash_Effect: + case TT::Action_Function: + return Precedence::Other; + case TT::Math_Add: + case TT::Math_Subtract: + return Precedence::Add; + case TT::Math_Multiply: + case TT::Math_Divide: + return Precedence::Multiply; + case TT::Assign_Bind: + case TT::Assign_Set: + case TT::Assign_Type: + return Precedence::Set; + case TT::NewLine: + return Precedence::NewLine; + case TT::Identifier: + return Precedence::Identifier; + case TT::Literal: + return Precedence::Identifier; + default: + return Precedence::Other; + } + } + + std::optional Parser::parseOneNode(Precedence precedence) { + auto next = peek(); + if (!next.has_value()) { + return {}; + } + if (getTokenPrecedence(*next) >= precedence) { + return {}; + } + consume(); + switch (next->type) { + case TT::None: break; + case TT::Identifier: + return parseIdentifier(); + case TT::Literal: + return parseLiteral(); + case TT::Math_Add: + case TT::Math_Subtract: + case TT::Math_Multiply: + case TT::Math_Divide: + return parseExpr(next->type); + } + throw std::runtime_error("FIXME: unimplemented parsing case"); + } + + const Node& Parser::parse() { + while (auto node = parseOneNode(Precedence::Root)) { + output.children.push_back(*node); + } + + return output; + } +} diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp new file mode 100644 index 0000000..40fc1f4 --- /dev/null +++ b/src/parser/parser.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include "../lexer/lexer.hpp" + +namespace Solstice { + + enum class NodeType { + Root, Literal, Identifier, + FunctionBind, Bind, Set, + Lambda, FunctionCall, + Add, Subtract, Multiply, Divide, + Equal, NotEqual, GreaterThan, LesserThan + }; + + enum class Precedence { + NewLine, + Identifier, + If, + While, + Compare, + Set, + FunctionCall, + Add, + Multiply, + Other, + Root + }; + + extern std::unordered_map tokToNodeType; + + class Node { + public: + NodeType type = NodeType::Root; + std::vector children; + std::variant data; + + Node() = delete; + Node(NodeType type) : type(type) {} + Node(NodeType type, const std::vector& children) : type(type), children(children) {} + Node(const Literal& literal) : type(NodeType::Literal), data(literal) {} + Node(const std::string& id) : type(NodeType::Identifier), data(id) {} + }; + + class Parser { + + std::vector input; + Node output{NodeType::Root}; + + size_t current = 0; + + std::optional peek(int64_t ahead = 0); + std::optional consume(); + + std::optional getPreviousNode(Node& parent); + + Node parseKwType(); + + Node parseHashCimport(); + Node parseHashEffect(); + + Node parseAssignBind(); + Node parseAssignSet(); + Node parseAssignType(); + + Node parseActionFunction(); + + Node parseExpr(TokenType type); + + Node parseOpenCurly(); + Node parseOpenParen(); + Node parseOpenSpiky(); + Node parseCloseCurly(); + Node parseCloseParen(); + Node parseCloseSpiky(); + Node parseNewLine(); + + Node parseLiteral(); + Node parseIdentifier(); + + static Precedence getTokenPrecedence(const Token& token); + + // Returns false when finished + std::optional parseOneNode(Precedence precedence = Precedence::Other); + + public: + Parser() = delete; + Parser(const std::vector& input) : input(input) {} + + const Node& parse(); + }; + +}