Refactor type system, print all errors in file

This commit is contained in:
2025-12-26 13:28:47 +11:00
parent 24ea348858
commit d8812fa14e
6 changed files with 189 additions and 50 deletions

View File

@@ -11,7 +11,9 @@ namespace Solstice {
const std::string RESET = "\033[0m";
const std::string BOLD = "\033[1m";
[[noreturn]] void syntaxError(std::string what, int line, std::string lineContent) {
int amountOfErrors = 0;
void syntaxError(std::string what, int line, std::string lineContent) {
std::cout << BOLD << RED << "Syntax Error" << RESET;
if (line > 0) std::cout << " on line " << BOLD << line << RESET;
std::cout << ":\n";
@@ -22,9 +24,9 @@ namespace Solstice {
std::cout << "\n";
}
std::cout << YELLOW << "-> " << what << RESET << "\n";
exit(1);
amountOfErrors++;
}
[[noreturn]] void typingError(std::string what, int line, std::string lineContent) {
void typingError(std::string what, int line, std::string lineContent) {
std::cout << BOLD << RED << "Typing Error" << RESET;
if (line > 0) std::cout << " on line " << BOLD << line << RESET;
std::cout << ":\n";
@@ -35,7 +37,13 @@ namespace Solstice {
std::cout << "\n";
}
std::cout << YELLOW << "-> " << what << RESET << "\n";
exit(1);
amountOfErrors++;
}
void summariseErrors() {
if (amountOfErrors > 0) {
std::cout << amountOfErrors << " errors generated.\n";
exit(1);
}
}
}
}

View File

@@ -5,9 +5,11 @@
namespace Solstice {
namespace Error {
[[noreturn]] void syntaxError(std::string what, int line = 0, std::string lineContent = "");
[[noreturn]] void typingError(std::string what, int line = 0, std::string lineContent = "");
void syntaxError(std::string what, int line = 0, std::string lineContent = "");
void typingError(std::string what, int line = 0, std::string lineContent = "");
void summariseErrors();
extern int amountOfErrors;
}
}
#endif
#endif

View File

@@ -111,7 +111,8 @@ namespace Solstice {
}
case '(':
case ')':
case '}':
case '}':
case ',':
{
if (!buf.empty()) {
addToken(buf);

View File

@@ -8,6 +8,7 @@
#include "lexer.h"
#include "parser.h"
#include "argparser.h"
#include "error.h"
#define parseOneToken(token) Parser({token.value()}).parse().children[0]
@@ -28,6 +29,7 @@ int main(int argc, char** argv) {
auto lexed = Solstice::Lexer(ss.str()).lex();
auto parsed = Solstice::Parser::Parser(lexed).parse();
GroundProgram program = Solstice::Parser::assembleProgram(parsed);
Solstice::Error::summariseErrors();
if (args.output.has_value()) {
std::FILE* originalStdout = stdout;
std::FILE* tmp = std::tmpfile();

View File

@@ -1,7 +1,6 @@
#include "parser.h"
#include "error.h"
#include <groundvm.h>
#include <iostream>
#include <cstring>
#define parseOneToken(token) Parser({token}).parse().children[0]
@@ -13,9 +12,9 @@ namespace Solstice {
return id.rfind("tmp_", 0) == 0;
}
std::map<std::string, SolDataType> variables;
std::map<std::string, std::string> variables;
SolDataType checkNodeReturnType(SolNode i) {
std::string checkNodeReturnType(SolNode i) {
switch (i.nodeType) {
case SolNodeType::Identifier: {
if (variables.find(i.outputId) != variables.end()) {
@@ -26,7 +25,7 @@ namespace Solstice {
break;
}
case SolNodeType::Value: {
return i.data.type;
return i.data.getTypeString();
}
case SolNodeType::Equal:
case SolNodeType::Inequal:
@@ -34,25 +33,25 @@ namespace Solstice {
case SolNodeType::Lesser:
case SolNodeType::EqGreater:
case SolNodeType::EqLesser:
return SolDataType::Bool;
return "bool";
break;
case SolNodeType::Add:
{
if (checkNodeReturnType(i.children[0]) == SolDataType::String && checkNodeReturnType(i.children[1]) == SolDataType::String) {
return SolDataType::String;
if (checkNodeReturnType(i.children[0]) == "string" && checkNodeReturnType(i.children[1]) == "string") {
return "string";
}
}
case SolNodeType::Subtract:
case SolNodeType::Multiply:
case SolNodeType::Divide:
{
if (checkNodeReturnType(i.children[0]) == SolDataType::Double) {
return SolDataType::Double;
if (checkNodeReturnType(i.children[0]) == "double") {
return "double";
}
if (checkNodeReturnType(i.children[1]) == SolDataType::Double) {
return SolDataType::Double;
if (checkNodeReturnType(i.children[1]) == "double") {
return "double";
}
return SolDataType::Int;
return "int";
break;
}
case SolNodeType::Puts:
@@ -62,10 +61,11 @@ namespace Solstice {
case SolNodeType::Set:
case SolNodeType::Root:
case SolNodeType::None:
return SolDataType::None;
case SolNodeType::FunctionDef:
return "none";
break;
}
return SolDataType::None;
return "none";
}
// SolData Implementation
@@ -74,6 +74,7 @@ namespace Solstice {
SolData::SolData(std::string in) : data(in), type(SolDataType::String) {}
SolData::SolData(char in) : data(in), type(SolDataType::Char) {}
SolData::SolData(bool in) : data(in), type(SolDataType::Bool) {}
SolData::SolData(SolFunction in) : data(in), type(SolDataType::Function) {}
std::optional<int64_t> SolData::getInt() {
if (type == SolDataType::Int) {
@@ -110,6 +111,26 @@ namespace Solstice {
return {};
}
}
std::optional<SolFunction> SolData::getFunction() {
if (type == SolDataType::Function) {
return std::get<SolFunction>(data);
} else {
return {};
}
}
std::string SolData::getTypeString() {
switch (type) {
case SolDataType::Int: return "int";
case SolDataType::Double: return "double";
case SolDataType::String: return "string";
case SolDataType::Char: return "char";
case SolDataType::Bool: return "bool";
case SolDataType::Function: return "function";
case SolDataType::None: return "none";
}
return "none";
}
// SolNode Implementation
SolNode::SolNode(SolNodeType nodeType) : nodeType(nodeType) {}
@@ -178,8 +199,8 @@ namespace Solstice {
break;
}
case SolNodeType::Add: {
ensure3(children[0], SolDataType::Int, SolDataType::Double, SolDataType::String, "operator '+'");
ensure3(children[1], SolDataType::Int, SolDataType::Double, SolDataType::String, "operator '+'");
ensure3(children[0], "int", "double", "string", "operator '+'");
ensure3(children[1], "int", "double", "string", "operator '+'");
ensuresame(children[0], children[1], "operator '+'");
SolGroundCodeBlock codeBlock;
outputId = "tmp_" + std::to_string(tmpIdIterator++);
@@ -197,8 +218,8 @@ namespace Solstice {
break;
}
case SolNodeType::Subtract: {
ensure2(children[0], SolDataType::Int, SolDataType::Double, "operator '-'");
ensure2(children[1], SolDataType::Int, SolDataType::Double, "operator '-'");
ensure2(children[0], "int", "double", "operator '-'");
ensure2(children[1], "int", "double", "operator '-'");
SolGroundCodeBlock codeBlock;
outputId = "tmp_" + std::to_string(tmpIdIterator++);
GroundInstruction gi = groundCreateInstruction(SUBTRACT);
@@ -215,8 +236,8 @@ namespace Solstice {
break;
}
case SolNodeType::Multiply: {
ensure2(children[0], SolDataType::Int, SolDataType::Double, "operator '*'");
ensure2(children[1], SolDataType::Int, SolDataType::Double, "operator '*'");
ensure2(children[0], "int", "double", "operator '*'");
ensure2(children[1], "int", "double", "operator '*'");
SolGroundCodeBlock codeBlock;
outputId = "tmp_" + std::to_string(tmpIdIterator++);
GroundInstruction gi = groundCreateInstruction(MULTIPLY);
@@ -233,8 +254,8 @@ namespace Solstice {
break;
}
case SolNodeType::Divide: {
ensure2(children[0], SolDataType::Int, SolDataType::Double, "operator '/'");
ensure2(children[1], SolDataType::Int, SolDataType::Double, "operator '/'");
ensure2(children[0], "int", "double", "operator '/'");
ensure2(children[1], "int", "double", "operator '/'");
SolGroundCodeBlock codeBlock;
outputId = "tmp_" + std::to_string(tmpIdIterator++);
GroundInstruction gi = groundCreateInstruction(DIVIDE);
@@ -478,7 +499,7 @@ namespace Solstice {
break;
}
case SolNodeType::If: {
ensure(children[0], SolDataType::Bool, "if condition");
ensure(children[0], "bool", "if condition");
auto conditionCode = children[0].generateCode();
code.insert(code.end(), conditionCode.begin(), conditionCode.end());
outputId = "tmp_" + std::to_string(tmpIdIterator++);
@@ -510,7 +531,7 @@ namespace Solstice {
break;
}
case SolNodeType::While: {
ensure(children[0], SolDataType::Bool, "while condition");
ensure(children[0], "bool", "while condition");
SolGroundCodeBlock startLabelBlock;
std::string startLabelIdString = "whilestart_" + std::to_string(labelIterator++);
std::string endLabelIdString = "whileend_" + std::to_string(labelIterator);
@@ -574,13 +595,14 @@ namespace Solstice {
if (isTemp(children[1].outputId)) codeBlock.toBeDropped.push_back(children[1].outputId);
code.push_back(codeBlock);
// Make sure we know what the variable type is
variables[children[0].outputId] = children[1].data.type;
variables[children[0].outputId] = children[1].data.getTypeString();
break;
}
case SolNodeType::FunctionCall: {
// Take care of in built functions
// This will be removed when inline ground is added
if (children[0].outputId == "input") {
ensure(children[1], SolDataType::String, "input function argument");
ensure(children[1], "string", "input function argument");
SolGroundCodeBlock inputCodeBlock;
if (children.size() > 1) {
GroundInstruction printInstruction = groundCreateInstruction(PRINT);
@@ -595,6 +617,7 @@ namespace Solstice {
code.push_back(inputCodeBlock);
break;
}
std::string fnToCall = children[0].outputId;
break;
}
@@ -728,6 +751,9 @@ namespace Solstice {
if (in == "while") {
return SolNodeType::While;
}
if (in == "def") {
return SolNodeType::FunctionDef;
}
if (in == "{") {
return SolNodeType::CodeBlockStart;
}
@@ -1154,6 +1180,93 @@ namespace Solstice {
rootNode.addNode(node);
}
}
case SolNodeType::FunctionDef: {
SolFunction fn;
auto nameTokenOpt = consume();
if (!nameTokenOpt || getNodeType(nameTokenOpt.value().value) != SolNodeType::Identifier) {
Error::syntaxError("Expected function name", tokenObj.line, tokenObj.lineContent);
}
std::string fnName = nameTokenOpt.value().value;
auto openParen = consume();
if (!openParen || openParen.value().value != "(") {
Error::syntaxError("Expected '(' after function name", tokenObj.line, tokenObj.lineContent);
}
std::string signature = "fun(";
bool first = true;
while (auto next = peek()) {
if (next.value().value == ")") {
consume();
break;
}
if (!first) {
auto comma = consume();
if (comma.value().value != ",") {
Error::syntaxError("Expected ',' between arguments", tokenObj.line, tokenObj.lineContent);
}
}
auto typeToken = consume();
if (!typeToken) Error::syntaxError("Expected type", tokenObj.line, tokenObj.lineContent);
std::string argType = typeToken.value().value;
auto argNameToken = consume();
if (!argNameToken) Error::syntaxError("Expected argument name", tokenObj.line, tokenObj.lineContent);
std::string argName = argNameToken.value().value;
fn.parameters.push_back({argName, argType});
if (!first) signature += ", ";
signature += argType;
first = false;
}
signature += ") ";
while (peek() && peek().value().value == "\n") consume();
auto next = peek();
if (next && next.value().value != "{") {
auto retTypeToken = consume();
fn.returnType = retTypeToken.value().value;
} else {
fn.returnType = "void";
}
signature += fn.returnType;
fn.signature = signature;
while (peek() && peek().value().value == "\n") consume();
auto brace = peek();
if (!brace || brace.value().value != "{") {
Error::syntaxError("Expected '{' starting function body", tokenObj.line, tokenObj.lineContent);
}
consume();
std::vector<Token> bodyTokens;
size_t brackets = 1;
while(auto t = consume()) {
if (t.value().value == "{") brackets++;
if (t.value().value == "}") brackets--;
if (brackets == 0) break;
bodyTokens.push_back(t.value());
}
if (brackets != 0) Error::syntaxError("Unclosed function body", tokenObj.line, tokenObj.lineContent);
auto parsedBody = Parser(bodyTokens).parse();
auto bodyNode = std::make_shared<SolNode>(SolNodeType::CodeBlock);
bodyNode->children = parsedBody.children;
fn.content = bodyNode;
SolNode fnNode(SolNodeType::FunctionDef);
fnNode.data = SolData(fn);
variables[fnName] = signature;
rootNode.addNode(fnNode);
break;
}
}
}
return rootNode;
@@ -1179,4 +1292,4 @@ namespace Solstice {
} // namespace Parser
}
}

View File

@@ -7,34 +7,35 @@
#include <optional>
#include <string>
#include <variant>
#include <memory>
#include "lexer.h"
#include "error.h"
#define ensure(node, datatype, op) \
if (checkNodeReturnType(node) != datatype) { \
Error::typingError("Expected " #datatype " for " op, node.line, node.lineContent); \
Error::typingError("Expected " + std::string(datatype) + " for " + op, node.line, node.lineContent); \
}
#define ensure2(node, datatype1, datatype2, op) { \
SolDataType dataType = checkNodeReturnType(node); \
std::string dataType = checkNodeReturnType(node); \
if (!(dataType == datatype1 || dataType == datatype2)) { \
Error::typingError("Expected either " #datatype1 " or " #datatype2 " for " op, node.line, node.lineContent); \
Error::typingError("Expected either " + std::string(datatype1) + " or " + std::string(datatype2) + " for " + op, node.line, node.lineContent); \
} \
}
#define ensure3(node, datatype1, datatype2, datatype3, op) { \
SolDataType dataType = checkNodeReturnType(node); \
#define ensure3(node, datatype1, datatype2, datatype3, op) { \
std::string dataType = checkNodeReturnType(node); \
if (!(dataType == datatype1 || dataType == datatype2 || dataType == datatype3)) { \
Error::typingError("Expected either " #datatype1 ", " #datatype2 ", or " #datatype3 " for " op, node.line, node.lineContent); \
Error::typingError("Expected either " + std::string(datatype1) + ", " + std::string(datatype2) + ", or " + std::string(datatype3) + " for " + op, node.line, node.lineContent); \
} \
}
#define ensuresame(node1, node2, op) { \
SolDataType n1t = checkNodeReturnType(node1); \
SolDataType n2t = checkNodeReturnType(node2); \
std::string n1t = checkNodeReturnType(node1); \
std::string n2t = checkNodeReturnType(node2); \
if (n1t != n2t) { \
if (!(n1t == SolDataType::Int && n2t == SolDataType::Double || n1t == SolDataType::Double && n2t == SolDataType::Int)) { \
Error::typingError("Expected types to be the same for " op, node1.line, node1.lineContent); \
if (!(n1t == "int" && n2t == "double" || n1t == "double" && n2t == "int")) { \
Error::typingError("Expected types to be the same for " + std::string(op), node1.line, node1.lineContent); \
} \
} \
}
@@ -56,14 +57,14 @@ namespace Solstice {
enum class SolNodeType {
Add, Subtract, Multiply, Divide, Equal, Inequal, Greater, Lesser, EqGreater, EqLesser, Set, While, If, Value, Identifier, None, Root, CodeBlock, CodeBlockStart, CodeBlockEnd, FunctionCall, Expression, BracketStart, BracketEnd, Puts
Add, Subtract, Multiply, Divide, Equal, Inequal, Greater, Lesser, EqGreater, EqLesser, Set, While, If, Value, Identifier, None, Root, CodeBlock, CodeBlockStart, CodeBlockEnd, FunctionDef, FunctionCall, Expression, BracketStart, BracketEnd, Puts
};
enum class SolDataType {
Int, String, Double, Bool, Char, None
Int, String, Double, Bool, Char, Function, None
};
extern std::map<std::string, SolDataType> variables;
extern std::map<std::string, std::string> variables;
class SolNode;
@@ -74,8 +75,17 @@ namespace Solstice {
SolGroundCodeBlock() = default;
};
class SolFunction {
public:
std::string signature;
std::vector<std::pair<std::string, std::string>> parameters; // name, type
std::string returnType;
std::shared_ptr<SolNode> content;
SolFunction() = default;
};
class SolData {
typedef std::variant<int64_t, std::string, double, bool, char> varData;
typedef std::variant<int64_t, std::string, double, bool, char, SolFunction> varData;
varData data;
public:
SolDataType type = SolDataType::Int;
@@ -85,11 +95,14 @@ namespace Solstice {
SolData(std::string in);
SolData(char in);
SolData(bool in);
SolData(SolFunction in);
std::optional<int64_t> getInt();
std::optional<double> getDouble();
std::optional<std::string> getString();
std::optional<char> getChar();
std::optional<bool> getBool();
std::optional<SolFunction> getFunction();
std::string getTypeString();
};
class SolNode {
@@ -134,7 +147,7 @@ namespace Solstice {
};
GroundProgram assembleProgram(SolNode& rootNode);
SolDataType checkNodeReturnType(SolNode in);
std::string checkNodeReturnType(SolNode in);
} // namespace Parser
}