From 0057d73215d33578fdcb481b28fb188a930310eb Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Sat, 4 Oct 2025 20:37:55 +1000 Subject: [PATCH] Structs (IN BETA) --- src/data/data.cpp | 1 + src/data/data.h | 1 + src/defs/defs.cpp | 22 ++++++- src/defs/defs.h | 15 ++++- src/executor/executor.cpp | 105 +++++++++++++++++++++++++++++- src/modules/function/function.cpp | 6 +- src/modules/function/function.h | 2 +- src/modules/print/print.cpp | 40 +++++++++++- src/modules/struct/struct.cpp | 56 ++++++++++++++++ src/modules/struct/struct.h | 8 +++ src/parser/parser.cpp | 42 ++++++++++++ src/utils/evaluate/evaluate.cpp | 7 ++ tests/struct.kyn | 27 ++++++++ 13 files changed, 322 insertions(+), 10 deletions(-) create mode 100644 src/modules/struct/struct.cpp create mode 100644 src/modules/struct/struct.h create mode 100644 tests/struct.kyn diff --git a/src/data/data.cpp b/src/data/data.cpp index 7725570..52cb0ba 100644 --- a/src/data/data.cpp +++ b/src/data/data.cpp @@ -9,6 +9,7 @@ namespace data { std::vector> scopes; std::map functions; + std::map structs; void initScopes() { scopes.clear(); diff --git a/src/data/data.h b/src/data/data.h index 369186d..aad46d2 100644 --- a/src/data/data.h +++ b/src/data/data.h @@ -8,6 +8,7 @@ namespace data { extern std::vector> scopes; extern std::map functions; + extern std::map structs; void modifyValue(std::string key, Value value); Value getValue(std::string key); void pushScope(); diff --git a/src/defs/defs.cpp b/src/defs/defs.cpp index 20be709..91f5e21 100644 --- a/src/defs/defs.cpp +++ b/src/defs/defs.cpp @@ -5,7 +5,6 @@ #include #include #include "../datatypes/lists/lists.h" -#include "../utils/trim/trim.h" InstructionType strToInstructionType(std::string in) { if (in == "println") return InstructionType::Println; @@ -21,6 +20,7 @@ InstructionType strToInstructionType(std::string in) { else if (in == "concat") return InstructionType::Concat; else if (in == "split") return InstructionType::Split; else if (in == "file") return InstructionType::File; + else if (in == "struct") return InstructionType::Struct; else return InstructionType::Variable; } @@ -118,7 +118,11 @@ Value::Value(InstructionGroup instgroup) : valtype(ValueType::InstructionGroup), Value::Value(std::vector listval) : valtype(ValueType::List), list(std::move(listval)) {} -Value::Value(const Value& other) : valtype(other.valtype), string_val(other.string_val), int_val(other.int_val), double_val(other.double_val), instructionGroup(other.instructionGroup), list(other.list), varName(other.varName) { +Value::Value(std::map mapval) : valtype(ValueType::Map), map(std::move(mapval)) {} + +Value::Value(Function func) : fn_val(func) {}; + +Value::Value(const Value& other) : valtype(other.valtype), string_val(other.string_val), int_val(other.int_val), double_val(other.double_val), instructionGroup(other.instructionGroup), list(other.list), map(other.map), varName(other.varName), fn_val(other.fn_val) { if (other.processed) { processed = std::make_unique(*other.processed); } @@ -138,6 +142,8 @@ Value& Value::operator=(const Value& other) { varName = other.varName; instructionGroup = other.instructionGroup; list = other.list; + map = other.map; + fn_val = other.fn_val; if (other.processed) { processed = std::make_unique(*other.processed); } else { @@ -182,6 +188,18 @@ std::string Value::toString() const { out += "])"; break; } + case ValueType::Map: { + if (map.count("__name__")) { + out += "Struct<" + map.at("__name__").string_val + ">, members: {"; + } else { + out += "Map, items: {"; + } + for (const auto& pair : map) { + out += pair.first + ": " + pair.second.toString() + ", "; + } + out += "})"; + break; + } default: out += "FIXME)"; break; diff --git a/src/defs/defs.h b/src/defs/defs.h index 4e81cd2..51c8157 100644 --- a/src/defs/defs.h +++ b/src/defs/defs.h @@ -3,14 +3,14 @@ #include #include #include - +#include enum class InstructionType { - None, Print, Println, Math, Let, Variable, Exit, If, While, Input, Compare, Function, Return, Concat, Split, File + None, Print, Println, Math, Let, Variable, Exit, If, While, Input, Compare, Function, Struct, Return, Concat, Split, File }; enum class ValueType { - Processed, Variable, InstructionGroup, List, Map, String, Int, Double, Identifier + Processed, Variable, InstructionGroup, List, Map, String, Int, Double, Identifier, Custom }; struct Instruction; @@ -36,16 +36,20 @@ struct Value { double double_val = 0.0; InstructionGroup instructionGroup; std::vector list; + std::map map; bool is_return = false; std::string toString() const; Varname varName = Varname(); + Function fn_val; Value(std::string stringval); Value(long long intval); Value(double doubleval); Value(Instruction instval); Value(InstructionGroup instgroup); Value(std::vector listval); + Value(std::map mapval); Value(Varname var); + Value(Function func); Value(); Value(const Value& other); Value& operator=(const Value& other); @@ -53,6 +57,11 @@ struct Value { Value& operator=(Value&& other) = default; }; +struct Struct { + std::map values; + std::map functions; +}; + struct Instruction { InstructionType instruction = InstructionType::None; std::vector args; diff --git a/src/executor/executor.cpp b/src/executor/executor.cpp index 0d59e6e..1d48ae8 100644 --- a/src/executor/executor.cpp +++ b/src/executor/executor.cpp @@ -14,6 +14,7 @@ #include "../modules/compare/compare.h" #include "../modules/input/input.h" #include "../modules/function/function.h" +#include "../modules/struct/struct.h" #include "../modules/split/split.h" #include "../modules/concat/concat.h" #include "../modules/file/file.h" @@ -35,6 +36,9 @@ Value execute(Instruction inst) { if (inst.instruction == InstructionType::Function) { return modules::fndef(inst.args); } + if (inst.instruction == InstructionType::Struct) { + return modules::structdef(inst.args); + } if (inst.instruction == InstructionType::Variable) { auto& args = inst.args; @@ -68,6 +72,48 @@ Value execute(Instruction inst) { return return_val; } + // Struct instantiation + if (args[0].valtype == ValueType::Identifier && data::structs.count(args[0].string_val)) { + std::string struct_name = args[0].string_val; + const auto& struct_def = data::structs.at(struct_name); + + Value instance{std::map()}; + instance.valtype = ValueType::Map; + instance.map["__name__"] = Value(struct_name); + + for (const auto& pair : struct_def.values) { + instance.map[pair.first] = pair.second; + } + + if (struct_def.functions.count("init")) { + const auto& init_func = struct_def.functions.at("init"); + if (init_func.arg_names.size() != args.size() - 1) { + error("Struct " + struct_name + " init expects " + std::to_string(init_func.arg_names.size()) + " arguments, but got " + std::to_string(args.size() - 1)); + return Value(); + } + data::pushScope(); + data::scopes.back()["self"] = instance; + for (size_t i = 0; i < init_func.arg_names.size(); ++i) { + Value arg_val = evaluate(args[i+1]); + data::scopes.back()[init_func.arg_names[i]] = arg_val; + } + Value return_val; + for (const auto& body_inst : init_func.body) { + return_val = execute(body_inst); + if (return_val.is_return) { + break; // Should not happen in init + } + } + // After init, self might have been modified. + instance = data::scopes.back()["self"]; + data::popScope(); + } + + return instance; + } + + + // Indexed assignment: $var index = value if (args.size() == 4 && args[2].valtype == ValueType::Identifier && args[2].string_val == "=") { std::string var_name; @@ -84,8 +130,18 @@ Value execute(Instruction inst) { return handleListOp(args); } else if (subject.valtype == ValueType::String) { // strings are Real return handleStringOp(args); + } else if (subject.valtype == ValueType::Map) { + if (args[1].valtype != ValueType::Identifier) { + error("Struct member name must be an identifier."); + return Value(); + } + std::string member_name = args[1].string_val; + Value new_val = evaluate(args[3]); + subject.map[member_name] = new_val; + data::modifyValue(var_name, subject); + return Value(); } else { - error("Indexed assignment is only for lists and strings."); + error("Indexed assignment is only for lists, strings and struct members."); return Value(); } } @@ -123,7 +179,52 @@ Value execute(Instruction inst) { if (subject.valtype == ValueType::List) { return handleListGet(subject, op_args); } else if (subject.valtype == ValueType::String) { - return handleStringGet(subject, op_args); } + return handleStringGet(subject, op_args); + } else if (subject.valtype == ValueType::Map) { + if (op_args.empty()) { + return subject; + } + if (op_args[0].valtype != ValueType::Identifier) { + error("Struct member name must be an identifier."); + return Value(); + } + std::string member_name = op_args[0].string_val; + + // Check for member variable + if (subject.map.count(member_name)) { + return subject.map.at(member_name); + } + + // Check for member function + std::string struct_name = subject.map.at("__name__").string_val; + if (data::structs.count(struct_name) && data::structs.at(struct_name).functions.count(member_name)) { + const auto& func = data::structs.at(struct_name).functions.at(member_name); + std::vector func_args(op_args.begin() + 1, op_args.end()); + if (func.arg_names.size() != func_args.size()) { + error("Struct function " + member_name + " expects " + std::to_string(func.arg_names.size()) + " arguments, but got " + std::to_string(func_args.size())); + return Value(); + } + data::pushScope(); + data::scopes.back()["self"] = subject; + for (size_t i = 0; i < func.arg_names.size(); ++i) { + data::scopes.back()[func.arg_names[i]] = evaluate(func_args[i]); + } + Value return_val; + for (const auto& body_inst : func.body) { + return_val = execute(body_inst); + if (return_val.is_return) { + break; + } + } + subject = data::scopes.back()["self"]; + data::popScope(); + return_val.is_return = false; + return return_val; + } + + error("Struct " + struct_name + " does not have member or method " + member_name); + return Value(); + } error("Unknown variable or function call: " + args[0].toString()); return Value(); diff --git a/src/modules/function/function.cpp b/src/modules/function/function.cpp index 7d6576e..e30bf55 100644 --- a/src/modules/function/function.cpp +++ b/src/modules/function/function.cpp @@ -4,7 +4,7 @@ #include "../../error/error.h" #include -Value modules::fndef(std::vector args) { +Value modules::fndef(std::vector args, bool inStruct) { if (args.size() < 2) { // At least a name and a body error("Syntax error: function statement requires a name and a body"); return Value(); @@ -40,6 +40,10 @@ Value modules::fndef(std::vector args) { new_func.arg_names = arg_names; new_func.body = body_val.instructionGroup; + if (inStruct) { + return Value(new_func); + } + data::functions[func_name] = new_func; return Value(""); diff --git a/src/modules/function/function.h b/src/modules/function/function.h index 539b8c9..b872edb 100644 --- a/src/modules/function/function.h +++ b/src/modules/function/function.h @@ -4,5 +4,5 @@ #include namespace modules { - Value fndef(std::vector args); + Value fndef(std::vector args, bool inStruct = false); } diff --git a/src/modules/print/print.cpp b/src/modules/print/print.cpp index b0d0e75..2beb6f9 100644 --- a/src/modules/print/print.cpp +++ b/src/modules/print/print.cpp @@ -1,6 +1,8 @@ #include "print.h" #include "../../defs/defs.h" #include "../../error/error.h" +#include "../../data/data.h" +#include "../../executor/executor.h" #include #include #include @@ -21,7 +23,7 @@ Value modules::print(std::vector values) { case ValueType::List: { std::cout << "["; bool afterfirst = false; - for (Value val : value.list) { + for (const auto& val : value.list) { if (afterfirst) { std::cout << ", "; } @@ -33,6 +35,42 @@ Value modules::print(std::vector values) { std::cout << "]"; break; } + case ValueType::Map: { + if (value.map.count("__name__")) { + std::string struct_name = value.map.at("__name__").string_val; + if (data::structs.count(struct_name) && data::structs.at(struct_name).functions.count("toString")) { + // Call toString method + data::pushScope(); + data::scopes.back()["self"] = value; + Value result; + for (const auto& inst : data::structs.at(struct_name).functions.at("toString").body) { + result = execute(inst); + if (result.is_return) { + break; + } + } + data::popScope(); + result.is_return = false; + print({result}); + break; + } + } + // Fallback to printing members + std::cout << "{"; + bool afterfirst = false; + for (const auto& pair : value.map) { + if (pair.first == "__name__") continue; + if (afterfirst) { + std::cout << ", "; + } else { + afterfirst = true; + } + std::cout << pair.first << ": "; + print({pair.second}); + } + std::cout << "}"; + break; + } default: error("FIXME: Unhandled type in print module.\nDebug info: \n" + value.toString()); break; diff --git a/src/modules/struct/struct.cpp b/src/modules/struct/struct.cpp new file mode 100644 index 0000000..771b7cc --- /dev/null +++ b/src/modules/struct/struct.cpp @@ -0,0 +1,56 @@ +#include "struct.h" +#include "../../defs/defs.h" +#include "../../error/error.h" +#include "../../data/data.h" +#include "../function/function.h" +#include + +Value modules::structdef(std::vector args) { + if (args.size() < 2) { // At least a name and a body + error("Syntax error: struct statement requires a name and a body"); + return Value(); + } + + // First arg is the name + Value name_val = args[0]; + if (name_val.valtype != ValueType::Identifier) { + error("Syntax error: struct name must be an identifier"); + return Value(); + } + + // Second arg is the body + Value body_val = args[1]; + if (body_val.valtype != ValueType::InstructionGroup) { + error("Syntax error: struct body must be an instruction group"); + return Value(); + } + + std::string structName = name_val.string_val; + InstructionGroup insts = body_val.instructionGroup; + + Struct newStruct; + + for (Instruction inst : insts) { + if (inst.args.empty()) { + continue; + } + if (inst.instruction == InstructionType::Function) { + newStruct.functions[inst.args[0].string_val] = fndef(inst.args, true).fn_val; + } else if (inst.args[0].valtype == ValueType::Identifier) { + if (inst.args.size() < 3) { + error("Syntax error: struct definition must have identifier, assignment and value"); + return Value(); + } + if (inst.args[1].valtype == ValueType::Identifier && inst.args[1].string_val == "=") { + newStruct.values[inst.args[0].string_val] = inst.args[2]; + } + } else { + error("Only member definitions allowed in struct"); + } + } + + data::structs[structName] = newStruct; + + return Value(""); + +} diff --git a/src/modules/struct/struct.h b/src/modules/struct/struct.h new file mode 100644 index 0000000..7129953 --- /dev/null +++ b/src/modules/struct/struct.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../../defs/defs.h" +#include + +namespace modules { + Value structdef(std::vector args); +} diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 6f03a1c..2196533 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -175,6 +175,48 @@ std::vector parse(std::string program) { std::string then_content = line.substr(block_start + 1, block_end - block_start - 1); function_inst.args.push_back(Value(parse(then_content))); // Recursive call instructions.push_back(function_inst); + } else if (line.rfind("struct", 0) == 0) { + Instruction struct_inst; + struct_inst.instruction = InstructionType::Struct; + + size_t block_start = line.find('{'); + if (block_start == std::string::npos) { + error("Expected '{' for struct statement"); + continue; + } + + // 1. Struct name + std::string signature_str = line.substr(6, block_start - 6); + std::vector signature_parts = split(trim(signature_str)); + + if (signature_parts.empty()) { + error("Struct definition is missing a name."); + continue; + } + + for(const auto& part : signature_parts) { + struct_inst.args.push_back(part); + } + + // 2. Find 'then' block + size_t block_end = 0; + int brace_level = 0; + for (size_t i = block_start; i < line.length(); ++i) { + if (line[i] == '{') brace_level++; + else if (line[i] == '}') brace_level--; + if (brace_level == 0) { + block_end = i; + break; + } + } + if (block_end == 0) { + error("Mismatched braces in struct statement"); + continue; + } + + std::string then_content = line.substr(block_start + 1, block_end - block_start - 1); + struct_inst.args.push_back(Value(parse(then_content))); // Recursive call + instructions.push_back(struct_inst); } else { instructions.push_back(Instruction(split(line))); } diff --git a/src/utils/evaluate/evaluate.cpp b/src/utils/evaluate/evaluate.cpp index 6d58948..dd5f2b4 100644 --- a/src/utils/evaluate/evaluate.cpp +++ b/src/utils/evaluate/evaluate.cpp @@ -10,6 +10,13 @@ Value evaluate(Value val) { } } else if (val.valtype == ValueType::Variable) { return data::getValue(val.varName.key); + } else if (val.valtype == ValueType::Identifier) { + // Check if it's a variable that doesn't start with $ + for (auto it = data::scopes.rbegin(); it != data::scopes.rend(); ++it) { + if (it->count(val.string_val)) { + return (*it)[val.string_val]; + } + } } return val; } diff --git a/tests/struct.kyn b/tests/struct.kyn new file mode 100644 index 0000000..dd222d6 --- /dev/null +++ b/tests/struct.kyn @@ -0,0 +1,27 @@ +struct Data { + status = + moreCrap = ["dingus", 32] + + fun init status moreCrap { + #assert $status is + #assert $morecrap is + println "Initing!" + self status = $status + self moreCrap = $moreCrap + println "Done!" + } + + fun toString { + return (concat "Status: " (self status)) + } + + fun testMemberFn { + println "This is from the test member function! Yay!" + } +} + +let myData = (Data "true" ["dingus", "dongus", "mingus", "mongus"]) + +myData testMemberFn + +println $myData