Start work on static type checking

This commit is contained in:
2025-12-24 13:15:48 +11:00
parent 7dd2b10603
commit 5ec2f86b70
5 changed files with 104 additions and 4 deletions

View File

@@ -5,7 +5,7 @@ LDFLAGS = -lgroundvm
BUILD_DIR = build
SRC_DIR = src
SRCS = $(SRC_DIR)/main.cpp $(SRC_DIR)/argparser.cpp $(SRC_DIR)/lexer.cpp $(SRC_DIR)/parser.cpp
SRCS = $(SRC_DIR)/main.cpp $(SRC_DIR)/argparser.cpp $(SRC_DIR)/lexer.cpp $(SRC_DIR)/parser.cpp $(SRC_DIR)/error.cpp
OBJS = $(patsubst $(SRC_DIR)/%.cpp, $(BUILD_DIR)/%.o, $(SRCS))
TARGET = solstice

18
src/error.cpp Normal file
View File

@@ -0,0 +1,18 @@
#include "error.h"
#include <string>
#include <iostream>
namespace Solstice {
namespace Error {
[[noreturn]] void syntaxError(std::string what) {
std::cout << "\033[31mSyntax error\n";
std::cout << "What: " << what << "\n";
exit(1);
}
[[noreturn]] void typingError(std::string what) {
std::cout << "\033[31mTyping error\n";
std::cout << "What: " << what << "\n";
exit(1);
}
}
}

8
src/error.h Normal file
View File

@@ -0,0 +1,8 @@
#include <string>
namespace Solstice {
namespace Error {
[[noreturn]] void syntaxError(std::string what);
[[noreturn]] void typingError(std::string what);
}
}

View File

@@ -1,8 +1,8 @@
#include "parser.h"
#include "error.h"
#include <groundvm.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#define parseOneToken(token) Parser({token.value()}).parse().children[0]
@@ -13,6 +13,56 @@ namespace Solstice {
return id.rfind("tmp_", 0) == 0;
}
std::map<std::string, SolDataType> variables;
SolDataType checkNodeReturnType(SolNode i) {
switch (i.nodeType) {
case SolNodeType::Identifier: {
if (variables.find(i.outputId) != variables.end()) {
return variables[i.outputId];
} else {
Error::syntaxError("Unknown variable");
}
break;
}
case SolNodeType::Equal:
case SolNodeType::Inequal:
case SolNodeType::Greater:
case SolNodeType::Lesser:
case SolNodeType::EqGreater:
case SolNodeType::EqLesser:
return SolDataType::Bool;
break;
case SolNodeType::Add:
{
if (checkNodeReturnType(i.children[0]) == SolDataType::String && checkNodeReturnType(i.children[1]) == SolDataType::String) {
return SolDataType::String;
}
}
case SolNodeType::Subtract:
case SolNodeType::Multiply:
case SolNodeType::Divide:
{
if (checkNodeReturnType(i.children[0]) == SolDataType::Double) {
return SolDataType::Double;
}
if (checkNodeReturnType(i.children[1]) == SolDataType::Double) {
return SolDataType::Double;
}
return SolDataType::Int;
break;
}
case SolNodeType::Puts:
case SolNodeType::If:
case SolNodeType::While:
case SolNodeType::CodeBlock:
default:
return SolDataType::None;
break;
}
return SolDataType::None;
}
// SolData Implementation
SolData::SolData(int64_t in) : data(in), type(SolDataType::Int) {}
SolData::SolData(double in) : data(in), type(SolDataType::Double) {}
@@ -359,6 +409,7 @@ namespace Solstice {
break;
}
case SolNodeType::If: {
ensure(children[0], SolDataType::Bool);
auto conditionCode = children[0].generateCode();
code.insert(code.end(), conditionCode.begin(), conditionCode.end());
outputId = "tmp_" + std::to_string(tmpIdIterator++);
@@ -390,6 +441,7 @@ namespace Solstice {
break;
}
case SolNodeType::While: {
ensure(children[0], SolDataType::Bool);
SolGroundCodeBlock startLabelBlock;
std::string startLabelIdString = "whilestart_" + std::to_string(labelIterator++);
std::string endLabelIdString = "whileend_" + std::to_string(labelIterator);
@@ -439,6 +491,11 @@ namespace Solstice {
break;
}
case SolNodeType::Set: {
if (variables.find(children[0].outputId) != variables.end()) {
if (variables[children[0].outputId] != checkNodeReturnType(children[1])) {
Error::typingError("Cannot change type of this variable");
}
}
SolGroundCodeBlock codeBlock;
GroundInstruction setInstruction = groundCreateInstruction(SET);
groundAddReferenceToInstruction(&setInstruction, groundCreateReference(DIRREF, children[0].outputId.data()));
@@ -446,6 +503,8 @@ namespace Solstice {
codeBlock.code.push_back(setInstruction);
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;
break;
}
case SolNodeType::FunctionCall: {

View File

@@ -2,11 +2,22 @@
#define PARSER_H
#include <groundvm.h>
#include <map>
#include <vector>
#include <optional>
#include <string>
#include <variant>
#define ensure(node, datatype) \
if (checkNodeReturnType(node) != datatype) { \
Error::typingError("Expected " #datatype); \
}
#define ensure2(node, datatype1, datatype2) \
if (checkNodeReturnType(node) != datatype1 && checkNodeReturnType(node) != datatype2) { \
Error::typingError("Expected either " #datatype1 " or " #datatype2); \
}
namespace Solstice {
// External variables referenced in parser logic
@@ -15,14 +26,17 @@ namespace Solstice {
namespace Parser {
enum class SolNodeType {
Add, Subtract, 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, FunctionCall, Expression, BracketStart, BracketEnd, Puts
};
enum class SolDataType {
Int, String, Double, Bool, Char, None
};
extern std::map<std::string, SolDataType> variables;
class SolNode;
class SolGroundCodeBlock {
@@ -85,6 +99,7 @@ namespace Solstice {
};
GroundProgram assembleProgram(SolNode& rootNode);
SolDataType checkNodeReturnType(SolNode in);
} // namespace Parser
}