initial commit

This commit is contained in:
2026-07-31 15:58:09 +10:00
commit f81284fd37
10 changed files with 689 additions and 0 deletions

11
meson.build Normal file
View File

@@ -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)

View File

View File

0
src/ir/ir.cpp Normal file
View File

0
src/ir/ir.hpp Normal file
View File

258
src/lexer/lexer.cpp Normal file
View File

@@ -0,0 +1,258 @@
#include "lexer.hpp"
#include <stdexcept>
#include <string>
using TT = Solstice::TokenType;
namespace Solstice {
std::unordered_map<std::string, TT> 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<std::string> Literal::getString() {
if (std::holds_alternative<std::string>(data)) {
return std::get<std::string>(data);
}
return {};
}
std::optional<int64_t> Literal::getInt() {
if (std::holds_alternative<int64_t>(data)) {
return std::get<int64_t>(data);
}
return {};
}
std::optional<double> Literal::getDouble() {
if (std::holds_alternative<double>(data)) {
return std::get<double>(data);
}
return {};
}
std::optional<char> Literal::getChar() {
if (std::holds_alternative<char>(data)) {
return std::get<char>(data);
}
return {};
}
std::optional<bool> Literal::getBool() {
if (std::holds_alternative<bool>(data)) {
return std::get<bool>(data);
}
return {};
}
std::optional<Literal> Token::getLiteral() {
if (std::holds_alternative<Literal>(data)) {
return std::get<Literal>(data);
}
return {};
}
std::optional<std::string> Token::getIdentifier() {
if (std::holds_alternative<std::string>(data)) {
return std::get<std::string>(data);
}
return {};
}
std::optional<char> Lexer::peek(std::size_t ahead) {
if (current + ahead >= input.size()) {
return {};
}
return input[current + ahead];
}
std::optional<char> 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<Token>& 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;
}
}

125
src/lexer/lexer.hpp Normal file
View File

@@ -0,0 +1,125 @@
#pragma once
#include <cstddef>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
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<std::string, TokenType> stringToTokenType;
class Literal {
std::variant<std::string, int64_t, double, char, bool> data;
public:
LiteralType type = LiteralType::None;
std::optional<std::string> getString();
std::optional<int64_t> getInt();
std::optional<double> getDouble();
std::optional<char> getChar();
std::optional<bool> 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<Literal, std::string> data;
public:
std::optional<Literal> getLiteral();
std::optional<std::string> 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<Token> output;
std::size_t current = 0;
std::optional<char> peek(std::size_t ahead = 0);
std::optional<char> consume();
static Token processToken(const std::string& buf);
public:
Lexer() = delete;
Lexer(const std::string& input) : input(input) {}
const std::vector<Token>& lex();
};
}

10
src/main.cpp Normal file
View File

@@ -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();
}

190
src/parser/parser.cpp Normal file
View File

@@ -0,0 +1,190 @@
#include "parser.hpp"
#include <stdexcept>
#include <unordered_map>
using TT = Solstice::TokenType;
namespace Solstice {
std::unordered_map<TokenType, NodeType> 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<Token> Parser::peek(int64_t ahead) {
if (current + ahead >= input.size() || current + ahead < 0) {
return {};
}
return input[current + ahead];
}
std::optional<Token> Parser::consume() {
if (current >= input.size()) {
return {};
}
return input[current++];
}
std::optional<Node> 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<Node> 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;
}
}

95
src/parser/parser.hpp Normal file
View File

@@ -0,0 +1,95 @@
#pragma once
#include <vector>
#include <optional>
#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<TokenType, NodeType> tokToNodeType;
class Node {
public:
NodeType type = NodeType::Root;
std::vector<Node> children;
std::variant<Literal, std::string> data;
Node() = delete;
Node(NodeType type) : type(type) {}
Node(NodeType type, const std::vector<Node>& 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<Token> input;
Node output{NodeType::Root};
size_t current = 0;
std::optional<Token> peek(int64_t ahead = 0);
std::optional<Token> consume();
std::optional<Node> 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<Node> parseOneNode(Precedence precedence = Precedence::Other);
public:
Parser() = delete;
Parser(const std::vector<Token>& input) : input(input) {}
const Node& parse();
};
}