From 1dedb30a87396153a53e5c84508766feecebc361 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Sat, 11 Apr 2026 17:05:20 +1000 Subject: [PATCH] Constructors --- src/codegen/codegen.c | 99 +++++++++++++++++++++++++++++++++- src/lexer/SolsToken.h | 11 +++- src/lexer/SolsType.c | 3 ++ src/lexer/SolsType.h | 2 +- src/lexer/lexer.c | 2 + src/parser/SolsNode.h | 4 +- src/parser/parser.c | 120 +++++++++++++++++++++++++++++++++++++++++- tests/struct.sols | 28 +++++----- 8 files changed, 248 insertions(+), 21 deletions(-) diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 6376aa8..a634032 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -255,6 +255,18 @@ ResultType(SolsType, charptr) getNodeType(SolsNode* node, SolsScope* scope) { return Error(SolsType, charptr, estr.str); } if (type.as.success.type != STT_FUN) { + if (type.as.success.type == STT_TEMPLATE) { + SolsType* constructor = NULL; + for (size_t i = 0; i < type.as.success.children.count; i++) { + if (strcmp(type.as.success.children.at[i].name, "constructor") == 0) { + constructor = &type.as.success.children.at[i].type; + break; + } + } + if (constructor != NULL) { + return Success(SolsType, charptr, *constructor->returnType); + } + } Estr estr = CREATE_ESTR(node->as.idName); APPEND_ESTR(estr, " is not a callable function"); return Error(SolsType, charptr, estr.str); @@ -272,7 +284,9 @@ ResultType(SolsType, charptr) getNodeType(SolsNode* node, SolsScope* scope) { SolsVariable* var = findSolsVariable(scope, node->as.type.identifierType); if (var == NULL) { Estr estr = CREATE_ESTR("Unable to find variable "); - APPEND_ESTR(estr, node->as.type.identifierType); + if (node->as.type.identifierType != NULL) { + APPEND_ESTR(estr, node->as.type.identifierType); + } return Error(SolsType, charptr, estr.str); } type = var->typeinfo; @@ -293,6 +307,7 @@ ResultType(SolsType, charptr) getNodeType(SolsNode* node, SolsScope* scope) { } return findStructMemberType(&type.as.success, node->children.at[1].as.idName); } + default: break; } return Error(SolsType, charptr, "Not yet implemented"); } @@ -946,6 +961,31 @@ ResultType(GroundProgram, charptr) generateFunctionCallNode(SolsNode* node, Sols return Error(GroundProgram, charptr, type.as.error); } + char* oldId = NULL; + + if (type.as.success.type == STT_TEMPLATE) { + SolsType* constructor = NULL; + for (size_t i = 0; i < type.as.success.children.count; i++) { + if (strcmp(type.as.success.children.at[i].name, "constructor") == 0) { + constructor = &type.as.success.children.at[i].type; + break; + } + } + if (constructor == NULL) { + return Error(GroundProgram, charptr, "Template type does not have a constructor"); + } + type.as.success = *constructor; + + node->children.at[0].type = SNT_IDENTIFIER; + oldId = node->children.at[0].as.idName; + Estr idName = CREATE_ESTR(node->children.at[0].as.idName); + APPEND_ESTR(idName, "_SOLS_CONSTRUCTOR"); + node->children.at[0].as.idName = idName.str; + node->children.at[0].accessArg = groundCreateReference(FNREF, idName.str); + } + + size_t argCount = node->children.count - 1; + // Ensure the argument types match the function types if (node->children.count - 1 != type.as.success.children.count) { return Error(GroundProgram, charptr, "Incorrect amount of arguments for function"); @@ -993,6 +1033,10 @@ ResultType(GroundProgram, charptr) generateFunctionCallNode(SolsNode* node, Sols node->accessArg = groundCreateReference(VALREF, returnStr); + if (oldId != NULL) { + node->children.at[0].as.idName = oldId; + } + return Success(GroundProgram, charptr, program); } @@ -1111,6 +1155,7 @@ ResultType(GroundProgram, charptr) generateStructNode(SolsNode* node, SolsScope* } type.as.success; }); + SolsNode* constructor = NULL; GroundProgram constants = groundCreateProgram(); GroundProgram structDef = groundCreateProgram(); GroundInstruction structDefInst = groundCreateInstruction(STRUCT); @@ -1150,6 +1195,9 @@ ResultType(GroundProgram, charptr) generateStructNode(SolsNode* node, SolsScope* groundAddInstructionToProgram(&structDef, defNode.as.success.instructions[j]); } + continue; + } else if (child->type == SNT_CONSTRUCTOR) { + constructor = child; continue; } ResultType(SolsType, charptr) childType = getNodeType(&child->children.at[1], scope); @@ -1175,6 +1223,53 @@ ResultType(GroundProgram, charptr) generateStructNode(SolsNode* node, SolsScope* } groundAddInstructionToProgram(&structDef, groundCreateInstruction(ENDSTRUCT)); + // Construct constructor + if (constructor != NULL) { + GroundInstruction constructorInst = groundCreateInstruction(FUN); + Estr constructorName = CREATE_ESTR(node->as.idName); + APPEND_ESTR(constructorName, "_SOLS_CONSTRUCTOR"); + groundAddReferenceToInstruction(&constructorInst, groundCreateReference(FNREF, constructorName.str)); + groundAddReferenceToInstruction(&constructorInst, groundCreateReference(TYPEREF, node->as.idName)); + for (size_t i = 0; i < constructor->as.type.children.count; i++) { + ResultType(GroundArg, charptr) arg = createGroundArgFromSolsType(&constructor->as.type.children.at[i].type, scope); + if (arg.error) { + return Error(GroundProgram, charptr, arg.as.error); + } + groundAddReferenceToInstruction(&constructorInst, arg.as.success); + groundAddReferenceToInstruction(&constructorInst, groundCreateReference(DIRREF, constructor->as.type.children.at[i].name)); + } + groundAddInstructionToProgram(&structDef, constructorInst); + + GroundInstruction constructorSelf = groundCreateInstruction(INIT); + groundAddReferenceToInstruction(&constructorSelf, groundCreateReference(DIRREF, "self")); + groundAddReferenceToInstruction(&constructorSelf, groundCreateReference(TYPEREF, node->as.idName)); + groundAddInstructionToProgram(&structDef, constructorSelf); + + SolsScope childScope = copySolsScope(scope); + addVariableToScope(&childScope, "self", type); + + for (size_t i = 0; i < constructor->as.type.children.count; i++) { + addVariableToScope(&childScope, constructor->as.type.children.at[i].name, constructor->as.type.children.at[i].type); + } + + ResultType(GroundProgram, charptr) constructorCode = generateCode(&constructor->children.at[0], &childScope); + if (constructorCode.error) { + return Error(GroundProgram, charptr, constructorCode.as.error); + } + for (size_t i = 0; i < constructorCode.as.success.size; i++) { + groundAddInstructionToProgram(&structDef, constructorCode.as.success.instructions[i]); + } + GroundInstruction ret = groundCreateInstruction(RETURN); + groundAddReferenceToInstruction(&ret, groundCreateReference(VALREF, "self")); + groundAddInstructionToProgram(&structDef, ret); + groundAddInstructionToProgram(&structDef, groundCreateInstruction(ENDFUN)); + + *constructor->as.type.returnType = type; + constructor->as.type.returnType->type = STT_OBJECT; + + addChildToSolsType(&type, constructor->as.type, "constructor"); + } + // Add to scope addVariableToScope(scope, node->as.idName, type); @@ -1182,6 +1277,8 @@ ResultType(GroundProgram, charptr) generateStructNode(SolsNode* node, SolsScope* for (size_t i = 0; i < structDef.size; i++) { groundAddInstructionToProgram(&constants, structDef.instructions[i]); } + + return Success(GroundProgram, charptr, constants); } diff --git a/src/lexer/SolsToken.h b/src/lexer/SolsToken.h index f87a7fe..02d4821 100644 --- a/src/lexer/SolsToken.h +++ b/src/lexer/SolsToken.h @@ -10,7 +10,16 @@ #include "SolsLiteral.h" typedef enum SolsTokenType { - STT_IDENTIFIER, STT_LITERAL, STT_TYPE, STT_DOT, STT_OPEN_CURLY, STT_CLOSE_CURLY, STT_OPEN_PAREN, STT_CLOSE_PAREN, STT_OP_ADD, STT_OP_SUB, STT_OP_MUL, STT_OP_DIV, STT_OP_ADDTO, STT_OP_SUBTO, STT_OP_MULTO, STT_OP_DIVTO, STT_OP_INCREMENT, STT_OP_DECREMENT, STT_OP_SET, STT_OP_GREATER, STT_OP_LESSER, STT_OP_EQUAL, STT_OP_INEQUAL, STT_OP_EQGREATER, STT_OP_EQLESSER, STT_KW_DEF, STT_KW_LAMBDA, STT_KW_RETURN, STT_KW_USE, STT_KW_STRUCT, STT_KW_PUTS, STT_KW_IF, STT_KW_WHILE, STT_KW_NEW, STT_KW_GROUND, STT_LINE_END, STT_COMMA + STT_IDENTIFIER, STT_LITERAL, STT_TYPE, STT_DOT, + STT_OPEN_CURLY, STT_CLOSE_CURLY, STT_OPEN_PAREN, STT_CLOSE_PAREN, + STT_OP_ADD, STT_OP_SUB, STT_OP_MUL, STT_OP_DIV, + STT_OP_ADDTO, STT_OP_SUBTO, STT_OP_MULTO, STT_OP_DIVTO, + STT_OP_INCREMENT, STT_OP_DECREMENT, STT_OP_SET, + STT_OP_GREATER, STT_OP_LESSER, STT_OP_EQUAL, STT_OP_INEQUAL, STT_OP_EQGREATER, STT_OP_EQLESSER, + STT_KW_DEF, STT_KW_LAMBDA, STT_KW_RETURN, + STT_KW_USE, STT_KW_STRUCT, STT_KW_CONSTRUCTOR, STT_KW_DESTRUCTOR, + STT_KW_PUTS, STT_KW_IF, STT_KW_WHILE, + STT_KW_NEW, STT_KW_GROUND, STT_LINE_END, STT_COMMA } SolsTokenType; typedef char* charptr; diff --git a/src/lexer/SolsType.c b/src/lexer/SolsType.c index 30a9f71..c51b084 100644 --- a/src/lexer/SolsType.c +++ b/src/lexer/SolsType.c @@ -277,6 +277,9 @@ ResultType(GroundArg, charptr) createGroundArgFromSolsType(SolsType* type, struc return Error(GroundArg, charptr, "Anonymous structs are not supported yet"); } } + case STT_NONE: { + return Success(GroundArg, charptr, groundCreateReference(TYPEREF, "none")); + } } return Error(GroundArg, charptr, "How did we get here?"); } diff --git a/src/lexer/SolsType.h b/src/lexer/SolsType.h index e958704..fe144f1 100644 --- a/src/lexer/SolsType.h +++ b/src/lexer/SolsType.h @@ -8,7 +8,7 @@ #include "../include/nothing.h" typedef enum SolsTypeType { - STT_INT, STT_STRING, STT_DOUBLE, STT_BOOL, STT_CHAR, STT_FUN, STT_TEMPLATE, STT_OBJECT, STT_UNKNOWN + STT_INT, STT_STRING, STT_DOUBLE, STT_BOOL, STT_CHAR, STT_FUN, STT_TEMPLATE, STT_OBJECT, STT_UNKNOWN, STT_NONE } SolsTypeType; // Definition of charptr for Result() and ResultType() macros diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 387dfff..9ac1c39 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -15,6 +15,8 @@ struct _SolsTokenTypeMap SolsTokenTypeMap[] = { {"return", STT_KW_RETURN}, {"use", STT_KW_USE}, {"struct", STT_KW_STRUCT}, + {"constructor", STT_KW_CONSTRUCTOR}, + {"destructor", STT_KW_DESTRUCTOR}, {"ground", STT_KW_GROUND}, {"new", STT_KW_NEW}, {"{", STT_OPEN_CURLY}, diff --git a/src/parser/SolsNode.h b/src/parser/SolsNode.h index 3990321..91cd63f 100644 --- a/src/parser/SolsNode.h +++ b/src/parser/SolsNode.h @@ -19,9 +19,9 @@ typedef enum SolsNodeType { SNT_OP_SET, SNT_OP_GREATER, SNT_OP_LESSER, SNT_OP_EQUAL, SNT_OP_INEQUAL, SNT_OP_EQGREATER, SNT_OP_EQLESSER, SNT_DEF, SNT_LAMBDA, SNT_FUNCTION_CALL, SNT_RETURN, - SNT_USE, SNT_STRUCT, + SNT_USE, SNT_STRUCT, SNT_CONSTRUCTOR, SNT_DESTRUCTOR, SNT_PUTS, SNT_IF, SNT_WHILE, SNT_NEW, - SNT_GROUND, SNT_ROOT, SNT_EXPR_IN_PAREN, SNT_DOT + SNT_GROUND, SNT_ROOT, SNT_EXPR_IN_PAREN, SNT_DOT, } SolsNodeType; struct SolsNode; diff --git a/src/parser/parser.c b/src/parser/parser.c index 852d1eb..af01a0a 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -1675,6 +1675,106 @@ static inline ResultType(Nothing, charptr) parseCloseParen(SolsParser* parser) { return Error(Nothing, charptr, "Extra closing parentheses"); } +static inline ResultType(Nothing, charptr) parseConstructor(SolsParser* parser) { + ResultType(SolsNode, charptr) node = createSolsNode(SNT_CONSTRUCTOR); + ResultType(SolsType, charptr) type = createSolsType(STT_FUN); + if (type.error) { + return Error(Nothing, charptr, type.as.error); + } + type.as.success.returnType = malloc(sizeof(SolsType)); + if (type.as.success.returnType == NULL) { + return Error(Nothing, charptr, "Failed to allocate memory for return type"); + } + type.as.success.returnType->type = STT_NONE; + if (node.error) { + return Error(Nothing, charptr, node.as.error); + } + ResultType(SolsToken, Nothing) openParen = parserConsume(parser); + if (openParen.error || openParen.as.success.type != STT_OPEN_PAREN) { + return Error(Nothing, charptr, "Expecting '('"); + } + // Parse type signature + for (;;) { + ResultType(SolsToken, Nothing) next = parserPeek(parser, 1); + if (next.error) { + return Error(Nothing, charptr, "Expecting ')' at end of constructor argument list"); + } + if (next.as.success.type == STT_CLOSE_PAREN) { + parserConsume(parser); + break; + } + + // Pattern of type, name, comma + + SolsType tmpType; + + if (next.as.success.type == STT_TYPE) { + tmpType = next.as.success.as.type; + } else if (next.as.success.type == STT_IDENTIFIER) { + tmpType = createIdentifiedSolsType(next.as.success.as.idName).as.success; + } else { + return Error(Nothing, charptr, "Expecting a type or identifier of type in constructor argument list"); + } + + parserConsume(parser); + + char* argName; + next = parserPeek(parser, 1); + + if (next.as.success.type == STT_IDENTIFIER) { + argName = next.as.success.as.idName; + } else { + return Error(Nothing, charptr, "Expecting identifier after type in constructor argument list"); + } + + // Add type to constructed SolsType + addChildToSolsType(&type.as.success, tmpType, argName); + parserConsume(parser); + + next = parserPeek(parser, 1); + if (next.error) { + return Error(Nothing, charptr, "Expecting a comma or closing bracket"); + } + if (next.as.success.type == STT_CLOSE_PAREN) { + parserConsume(parser); + break; + } + if (next.as.success.type != STT_COMMA) { + return Error(Nothing, charptr, "Expecting a comma or closing bracket"); + } + parserConsume(parser); + } + + // Add type to node + node.as.success.as.type = type.as.success; + + // Skip newlines before the opening curly brace + while (parserPeek(parser, 1).as.success.type == STT_LINE_END) { + parserConsume(parser); + } + + ResultType(SolsToken, Nothing) openCurly = parserConsume(parser); + if (openCurly.error || openCurly.as.success.type != STT_OPEN_CURLY) { + return Error(Nothing, charptr, "Expecting opening curly brace ({) for constructor body"); + } + + // Add node to parent + addChildToSolsNode(parser->currentParent, node.as.success); + + // Parse code block + ResultType(Nothing, charptr) res = parseCodeBlock(parser); + if (res.error) return res; + + // Last child of parent is code block, we need to move it + SolsNode codeBlock = parser->currentParent->children.at[parser->currentParent->children.count - 1]; + parser->currentParent->children.count--; + + // We need to get the actual node from the parent to modify it + SolsNode* constructorNode = &parser->currentParent->children.at[parser->currentParent->children.count - 1]; + addChildToSolsNode(constructorNode, codeBlock); + return Success(Nothing, charptr, {}); +} + static inline ResultType(Nothing, charptr) parseStruct(SolsParser* parser) { // Get struct name SolsNode structNode = ({ @@ -1735,7 +1835,7 @@ static inline ResultType(Nothing, charptr) parseStruct(SolsParser* parser) { _result.as.success; }); if (keyTok.type == STT_KW_DEF) { - // def + // def fnName(Arg arg1, Arg arg2, ...) ReturnType {} ResultType(Nothing, charptr) defNode = parseDef(parser); if (defNode.error) { return Error(Nothing, charptr, defNode.as.error); @@ -1744,6 +1844,16 @@ static inline ResultType(Nothing, charptr) parseStruct(SolsParser* parser) { parser->currentParent->children.count--; continue; } + if (keyTok.type == STT_KW_CONSTRUCTOR) { + // constructor(Arg arg1, Arg arg2, ...) {} + ResultType(Nothing, charptr) constructorNode = parseConstructor(parser); + if (constructorNode.error) { + return Error(Nothing, charptr, constructorNode.as.error); + } + addChildToSolsNode(&structNode, parser->currentParent->children.at[parser->currentParent->children.count - 1]); + parser->currentParent->children.count--; + continue; + } if (keyTok.type != STT_IDENTIFIER) { return Error(Nothing, charptr, "Expecting struct key"); } @@ -1906,6 +2016,14 @@ ResultType(Nothing, charptr) parse(SolsParser* parser) { } case STT_CLOSE_PAREN: PARSER_HANDLE(CloseParen); case STT_DOT: PARSER_HANDLE(Dot); + case STT_KW_CONSTRUCTOR: { + createParserError(parser, "'constructor' must be used inside a struct definition"); + break; + } + case STT_KW_DESTRUCTOR: { + createParserError(parser, "'destructor' must be used inside a struct definition"); + break; + } } } if (parser->errors.count > 0) { diff --git a/tests/struct.sols b/tests/struct.sols index 5cfe973..88f280d 100644 --- a/tests/struct.sols +++ b/tests/struct.sols @@ -1,18 +1,16 @@ -use io +struct Person { + name = "" + age = 0 + def greet() string { + return "Hello, " + self.name + "!" + } -struct dingus { - x = 5 - y = "dingus" + constructor(string name, int age) { + self.name = name + self.age = age + } } -e = new dingus -puts e -puts dingus - -puts e.x -println(e.y) - -e.x = 7 -e.y = "heheheha" -puts e.x -println(e.y) +max = Person("Max", 16) +puts max +puts max.greet() \ No newline at end of file