From 978f75f06fac89d90a46d99c52837fd3924b5991 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:53:24 -0600 Subject: [PATCH 01/32] test: simple class --- .../tests/success/syntax/class_simple/main.canyon | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon diff --git a/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon b/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon new file mode 100644 index 0000000..eebc5fa --- /dev/null +++ b/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon @@ -0,0 +1,7 @@ +fun main() { +} + +class Foo { + x: i32 = 0; + y: i32 = 0; +} \ No newline at end of file From af288738e4111f24147467f582ad71d97bb277c8 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:54:36 -0600 Subject: [PATCH 02/32] test: class keyword --- test/unit_tests/test_lexer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit_tests/test_lexer.cpp b/test/unit_tests/test_lexer.cpp index 6a09013..168adb6 100644 --- a/test/unit_tests/test_lexer.cpp +++ b/test/unit_tests/test_lexer.cpp @@ -534,7 +534,8 @@ INSTANTIATE_TEST_SUITE_P(testKeywords, TestLexerKeywords, testing::Values(std::pair{"return", Keyword::Type::RETURN}, std::pair{"let", Keyword::Type::LET}, std::pair{"fun", Keyword::Type::FUN}, std::pair{"if", Keyword::Type::IF}, std::pair{"else", Keyword::Type::ELSE}, - std::pair{"while", Keyword::Type::WHILE})); + std::pair{"while", Keyword::Type::WHILE}, + std::pair{"class", Keyword::Type::CLASS})); TEST_P(TestLexerSymbols, testSymbols) { const std::string symbol = GetParam(); From e2fc8b35eb9ea699bc3ea28c523371132937d0f3 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:56:41 -0600 Subject: [PATCH 03/32] feat(Lexer): class keyword --- src/lexer.cpp | 3 +++ src/tokens.cpp | 4 ++++ src/tokens.h | 1 + 3 files changed, 8 insertions(+) diff --git a/src/lexer.cpp b/src/lexer.cpp index 6bf0098..111e18d 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -234,6 +234,9 @@ std::unique_ptr Lexer::createKeyword(const Slice &s) { if (s.contents == "while") { return std::make_unique(s, Keyword::Type::WHILE); } + if (s.contents == "class") { + return std::make_unique(s, Keyword::Type::CLASS); + } return nullptr; } diff --git a/src/tokens.cpp b/src/tokens.cpp index 4f1d2dc..ca35eeb 100644 --- a/src/tokens.cpp +++ b/src/tokens.cpp @@ -59,6 +59,10 @@ void Keyword::print(std::ostream &os) const { os << "while"; break; } + case Type::CLASS: { + os << "class"; + break; + } } } diff --git a/src/tokens.h b/src/tokens.h index d9df019..8d98843 100644 --- a/src/tokens.h +++ b/src/tokens.h @@ -50,6 +50,7 @@ struct Keyword : public Token { IF, ELSE, WHILE, + CLASS, }; Type type; From 76b7348e6e9443cf69f674fdc51e7e6e29c040b6 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:02:52 -0600 Subject: [PATCH 04/32] refactor(Parser): prepare module-level parsing for classes --- src/parser.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 55684c2..62dcb4c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -14,9 +14,33 @@ Parser::Parser(std::filesystem::path source, std::vector> std::unique_ptr Parser::parse() { auto mod = std::make_unique(source); while (!isAtEnd()) { - std::pair, std::unique_ptr> func - = parseFunction(); - if (func.second == nullptr) { + auto *keyword = dynamic_cast(tokens[i].get()); + if (keyword != nullptr && keyword->type == Keyword::Type::FUN) { + std::pair, std::unique_ptr> func + = parseFunction(); + if (func.second == nullptr) { + synchronize(); + mustSynchronize = false; + auto *punc = dynamic_cast(tokens[i].get()); + while (punc != nullptr) { + if (punc->type == Punctuation::Type::Semicolon) { + i++; + synchronize(); + mustSynchronize = false; + punc = dynamic_cast(tokens[i].get()); + } else if (punc->type == Punctuation::Type::CloseBrace) { + i++; + break; + } else { + std::cerr << "Unexpected token in parse" << std::endl; + exit(EXIT_FAILURE); + } + } + continue; + } + mod->addFunction(std::move(func.first), std::move(func.second)); + } else { + errorHandler->error(*tokens[i], "Expected keyword `fun`"); synchronize(); mustSynchronize = false; auto *punc = dynamic_cast(tokens[i].get()); @@ -36,7 +60,6 @@ std::unique_ptr Parser::parse() { } continue; } - mod->addFunction(std::move(func.first), std::move(func.second)); } return mod; } From 4a0a0caa681f88bb7af939a21b7068b57982ade3 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:57:34 -0600 Subject: [PATCH 05/32] feat(Parser): classes --- src/ast.cpp | 38 +++++++ src/ast.h | 22 +++++ src/ccodeadapter.cpp | 5 + src/ccodeadapter.h | 1 + src/ccodegenerator.cpp | 6 ++ src/ccodegenerator.h | 1 + src/parser.cpp | 208 +++++++++++++++++++++++++++------------ src/parser.h | 2 + src/semanticanalyzer.cpp | 5 + src/semanticanalyzer.h | 1 + 10 files changed, 228 insertions(+), 61 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 7039c07..ccf5c00 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -408,6 +408,28 @@ void Function::accept(ASTVisitor &visitor) { visitor.visit(*this); } +Class::Class( + std::vector, std::unique_ptr>> fields, + std::vector, std::unique_ptr>> methods) + : fields(std::move(fields)), methods(std::move(methods)) { +} + +void Class::forEachField(const std::function &fieldHandler) { + for (auto &[name, type] : fields) { + fieldHandler(*name, *type); + } +} + +void Class::forEachMethod(const std::function &methodHandler) { + for (auto &[name, method] : methods) { + methodHandler(*name, *method); + } +} + +void Class::accept(ASTVisitor &visitor) { + visitor.visit(*this); +} + Type::Type(int id, int parentID, std::string_view name) : id(id), parentID(parentID), name(name) { } @@ -445,6 +467,18 @@ void Module::forEachFunction( } } +void Module::addClass(std::unique_ptr name, std::unique_ptr cls, + bool isBuiltin) { + classes[name->s.contents] = {std::move(cls), isBuiltin}; +} + +void Module::forEachClass( + const std::function &classHandler) { + for (auto &[name, cls] : classes) { + classHandler(name, *std::get<0>(cls), std::get<1>(cls)); + } +} + Type Module::getType(std::string_view typeName) { if (typeTableByName.find(typeName) == typeTableByName.end()) { return Type(-1, -1, ""); @@ -670,6 +704,9 @@ void ASTPrinter::visit(LetStatement &node) { void ASTPrinter::visit([[maybe_unused]] Function &node) { } +void ASTPrinter::visit([[maybe_unused]] Class &node) { +} + void ASTPrinter::visit(Module &node) { node.forEachFunction( [this](std::string_view name, Function &function, bool /*unused*/) { @@ -678,4 +715,5 @@ void ASTPrinter::visit(Module &node) { function.getBody().accept(*this); std::cerr << '\n'; }); + // TODO classes } diff --git a/src/ast.h b/src/ast.h index bbc4741..edaf892 100644 --- a/src/ast.h +++ b/src/ast.h @@ -274,6 +274,20 @@ class Function : public ASTComponent { ~Function() = default; }; +class Class : public ASTComponent { +private: + std::vector, std::unique_ptr>> fields; + std::vector, std::unique_ptr>> methods; +public: + Class(std::vector, std::unique_ptr>> fields, + std::vector, std::unique_ptr>> + methods); + void forEachField(const std::function &fieldHandler); + void forEachMethod(const std::function &methodHandler); + void accept(ASTVisitor &visitor); + ~Class() = default; +}; + struct Type { int id; int parentID; @@ -292,6 +306,8 @@ class Module : public ASTComponent { std::unordered_map>> binaryOperators; std::filesystem::path source; + std::unordered_map, bool>> + classes; public: std::list ownedStrings; explicit Module(std::filesystem::path source); @@ -300,6 +316,10 @@ class Module : public ASTComponent { bool isBuiltin = false); void forEachFunction( const std::function &functionHandler); + void addClass(std::unique_ptr name, std::unique_ptr cls, + bool isBuiltin = false); + void forEachClass( + const std::function &classHandler); Type getType(std::string_view typeName); Type getType(int id); void insertType(std::string_view typeName); @@ -333,6 +353,7 @@ class ASTVisitor { virtual void visit(ExpressionStatement &node) = 0; virtual void visit(LetStatement &node) = 0; virtual void visit(Function &node) = 0; + virtual void visit(Class &node) = 0; virtual void visit(Module &node) = 0; virtual ~ASTVisitor() = default; }; @@ -355,6 +376,7 @@ class ASTPrinter : public ASTVisitor { void visit(ExpressionStatement &node) override; void visit(LetStatement &node) override; void visit(Function &node) override; + void visit(Class &node) override; void visit(Module &node) override; virtual ~ASTPrinter() = default; }; diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index fb1d276..0c68e72 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -444,6 +444,10 @@ void CCodeAdapter::visit(Function &node) { std::move(enclosingScope)); } +void CCodeAdapter::visit([[maybe_unused]] Class &node) { + // TODO +} + void CCodeAdapter::visit(Module &node) { node.forEachFunction([this](std::string_view name, Function &oldFunction, bool isBuiltin) { @@ -457,6 +461,7 @@ void CCodeAdapter::visit(Module &node) { std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)), std::move(newFunction), isBuiltin); }); + // TODO classes } void CCodeAdapter::visitExpression(Expression &node) { diff --git a/src/ccodeadapter.h b/src/ccodeadapter.h index 9a24164..7bc25b5 100644 --- a/src/ccodeadapter.h +++ b/src/ccodeadapter.h @@ -40,6 +40,7 @@ class CCodeAdapter : ASTVisitor { void visit(ExpressionStatement &node) override; void visit(LetStatement &node) override; void visit(Function &node) override; + void visit(Class &node) override; void visit(Module &node) override; virtual ~CCodeAdapter() = default; private: diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index 1d5d684..90160e5 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -211,6 +211,10 @@ void CCodeGenerator::visit([[maybe_unused]] Function &node) { // Currently being handled in the Module visitor } +void CCodeGenerator::visit([[maybe_unused]] Class &node) { + // TODO +} + void CCodeGenerator::visit(Module &node) { // Forward declarations node.forEachFunction( @@ -232,6 +236,8 @@ void CCodeGenerator::visit(Module &node) { *os << '\n'; generateMain(); + // TODO classes + // Function definitions node.forEachFunction( [this](std::string_view name, Function &function, bool isBuiltin) { diff --git a/src/ccodegenerator.h b/src/ccodegenerator.h index 3485021..47b440b 100644 --- a/src/ccodegenerator.h +++ b/src/ccodegenerator.h @@ -37,6 +37,7 @@ class CCodeGenerator : public ASTVisitor { void visit(ExpressionStatement &node) override; void visit(LetStatement &node) override; void visit(Function &node) override; + void visit(Class &node) override; void visit(Module &node) override; ~CCodeGenerator() = default; private: diff --git a/src/parser.cpp b/src/parser.cpp index 62dcb4c..b55abc1 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -39,8 +39,30 @@ std::unique_ptr Parser::parse() { continue; } mod->addFunction(std::move(func.first), std::move(func.second)); + } else if (keyword != nullptr && keyword->type == Keyword::Type::CLASS) { + std::pair, std::unique_ptr> cls = parseClass(); + if (cls.second == nullptr) { + synchronize(); + mustSynchronize = false; + auto *punc = dynamic_cast(tokens[i].get()); + while (punc != nullptr) { + if (punc->type == Punctuation::Type::Semicolon) { + i++; + synchronize(); + mustSynchronize = false; + punc = dynamic_cast(tokens[i].get()); + } else if (punc->type == Punctuation::Type::CloseBrace) { + i++; + break; + } else { + std::cerr << "Unexpected token in parse" << std::endl; + exit(EXIT_FAILURE); + } + } + continue; + } } else { - errorHandler->error(*tokens[i], "Expected keyword `fun`"); + errorHandler->error(*tokens[i], "Expected keyword `fun` or `class`"); synchronize(); mustSynchronize = false; auto *punc = dynamic_cast(tokens[i].get()); @@ -152,79 +174,143 @@ std::pair, std::unique_ptr> Parser::parseFunct std::move(block))}; } +std::pair, std::unique_ptr> Parser::parseClass() { + auto *keyword = dynamic_cast(tokens[i].get()); + if (keyword == nullptr || keyword->type != Keyword::Type::CLASS) { + errorHandler->error(*tokens[i], "Expected keyword `class`"); + return {nullptr, nullptr}; + } + i++; + auto *symbol = dynamic_cast(tokens[i].get()); + if (symbol == nullptr) { + errorHandler->error(*tokens[i], "Expected symbol following `class`"); + return {nullptr, nullptr}; + } + symbol = dynamic_cast(tokens[i++].release()); + auto *punc = dynamic_cast(tokens[i].get()); + if (punc == nullptr || punc->type != Punctuation::Type::OpenBrace) { + errorHandler->error(*tokens[i], + "Expected '{' following symbol in class definition"); + mustSynchronize = true; + return {nullptr, nullptr}; + } + i++; + + std::vector, std::unique_ptr>> fields; + std::vector, std::unique_ptr>> methods; + while (true) { + auto *keyword2 = dynamic_cast(tokens[i].get()); + if (keyword2 != nullptr && keyword2->type == Keyword::Type::LET) { + std::unique_ptr let = parseLet(); + if (let == nullptr) { + return {nullptr, nullptr}; + } + fields.emplace_back(std::make_unique(let->getSymbol()), + std::make_unique(*let->getTypeAnnotation())); + } else if (keyword2 != nullptr && keyword2->type == Keyword::Type::FUN) { + std::pair, std::unique_ptr> method + = parseFunction(); + if (method.second == nullptr) { + return {nullptr, nullptr}; + } + methods.push_back(std::move(method)); + } else { + break; + } + } + + auto *p2 = dynamic_cast(tokens[i].get()); + if (p2 == nullptr || p2->type != Punctuation::Type::CloseBrace) { + errorHandler->error(*tokens[i], "Expected '}' after class definition"); + return {nullptr, nullptr}; + } + i++; + + return {std::unique_ptr(symbol), + std::make_unique(std::move(fields), std::move(methods))}; +} + std::unique_ptr Parser::parseStatement() { auto *keyword = dynamic_cast(tokens[i].get()); if (keyword != nullptr && keyword->type == Keyword::Type::LET) { + return parseLet(); + } + return nullptr; +} + +std::unique_ptr Parser::parseLet() { + auto *keyword = dynamic_cast(tokens[i].get()); + if (keyword == nullptr || keyword->type != Keyword::Type::LET) { + errorHandler->error(*tokens[i], "Expected keyword `let`"); + mustSynchronize = true; + return nullptr; + } + i++; + auto *symbol = dynamic_cast(tokens[i].get()); + if (symbol == nullptr) { + errorHandler->error(*tokens[i], "Expected symbol following `let`"); + mustSynchronize = true; + return nullptr; + } + symbol = dynamic_cast(tokens[i++].release()); + Symbol *type = nullptr; + auto *punc = dynamic_cast(tokens[i].get()); + if (punc != nullptr && punc->type == Punctuation::Type::Colon) { i++; - auto *symbol = dynamic_cast(tokens[i].get()); - if (symbol == nullptr) { - errorHandler->error(*tokens[i], "Expected symbol following `let`"); + type = dynamic_cast(tokens[i].get()); + if (type == nullptr) { + errorHandler->error(*tokens[i], + "Expected type following ':' in `let` statement"); mustSynchronize = true; return nullptr; } - symbol = dynamic_cast(tokens[i++].release()); - Symbol *type = nullptr; + type = dynamic_cast(tokens[i++].release()); + } else { + type = nullptr; + } + punc = dynamic_cast(tokens[i].get()); + if (punc != nullptr && punc->type == Punctuation::Type::Semicolon) { + i++; + return std::make_unique(*keyword, std::unique_ptr(symbol), + std::unique_ptr(type), nullptr, nullptr, punc); + } + auto *op = dynamic_cast(tokens[i].get()); + if (op == nullptr || op->type != Operator::Type::Assignment) { + errorHandler->error(*tokens[i], + "Expected assignment expression in `let` statement"); + mustSynchronize = true; + return nullptr; + } + op = dynamic_cast(tokens[i].release()); + i++; + std::unique_ptr expr = parseExpression(); + if (expr == nullptr) { + synchronize(); + mustSynchronize = false; auto *punc = dynamic_cast(tokens[i].get()); - if (punc != nullptr && punc->type == Punctuation::Type::Colon) { - i++; - type = dynamic_cast(tokens[i].get()); - if (type == nullptr) { - errorHandler->error(*tokens[i], - "Expected type following ':' in `let` statement"); - mustSynchronize = true; + if (punc != nullptr) { + if (punc->type == Punctuation::Type::Semicolon) { + i++; return nullptr; } - type = dynamic_cast(tokens[i++].release()); - } else { - type = nullptr; - } - punc = dynamic_cast(tokens[i].get()); - if (punc != nullptr && punc->type == Punctuation::Type::Semicolon) { - i++; - return std::make_unique(*keyword, - std::unique_ptr(symbol), std::unique_ptr(type), nullptr, - nullptr, punc); - } - auto *op = dynamic_cast(tokens[i].get()); - if (op == nullptr || op->type != Operator::Type::Assignment) { - errorHandler->error(*tokens[i], - "Expected assignment expression in `let` statement"); - mustSynchronize = true; - return nullptr; - } - op = dynamic_cast(tokens[i].release()); - i++; - std::unique_ptr expr = parseExpression(); - if (expr == nullptr) { - synchronize(); - mustSynchronize = false; - auto *punc = dynamic_cast(tokens[i].get()); - if (punc != nullptr) { - if (punc->type == Punctuation::Type::Semicolon) { - i++; - return nullptr; - } - if (punc->type == Punctuation::Type::CloseBrace) { - return nullptr; - } - std::cerr << "Unexpected token in parseExpression" << std::endl; - exit(EXIT_FAILURE); + if (punc->type == Punctuation::Type::CloseBrace) { + return nullptr; } + std::cerr << "Unexpected token in parseExpression" << std::endl; + exit(EXIT_FAILURE); } - punc = dynamic_cast(tokens[i].get()); - if (punc == nullptr || punc->type != Punctuation::Type::Semicolon) { - errorHandler->error(*tokens[i], - "Expected ';' following expression in `let` statement"); - mustSynchronize = true; - return nullptr; - } - i++; - return std::make_unique(*keyword, std::unique_ptr(symbol), - std::unique_ptr(type), std::unique_ptr(op), - std::move(expr), punc); } - - return nullptr; + punc = dynamic_cast(tokens[i].get()); + if (punc == nullptr || punc->type != Punctuation::Type::Semicolon) { + errorHandler->error(*tokens[i], + "Expected ';' following expression in `let` statement"); + mustSynchronize = true; + return nullptr; + } + i++; + return std::make_unique(*keyword, std::unique_ptr(symbol), + std::unique_ptr(type), std::unique_ptr(op), std::move(expr), + punc); } std::unique_ptr Parser::parseExpression() { diff --git a/src/parser.h b/src/parser.h index 3184787..f03a117 100644 --- a/src/parser.h +++ b/src/parser.h @@ -26,7 +26,9 @@ class Parser { ~Parser() = default; private: std::pair, std::unique_ptr> parseFunction(); + std::pair, std::unique_ptr> parseClass(); std::unique_ptr parseStatement(); + std::unique_ptr parseLet(); std::unique_ptr parseExpression(); std::unique_ptr parseBlock(); std::unique_ptr parseIfElse(); diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index b6544a6..fb9f556 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -353,6 +353,10 @@ void SemanticAnalyzer::visit(Function &node) { inUnreachableCode = false; } +void SemanticAnalyzer::visit([[maybe_unused]] Class &node) { + // TODO +} + void SemanticAnalyzer::visit(Module &node) { addDefaultOperators(&node); addRuntimeFunctions(&node, builtinApiJsonFile); @@ -376,6 +380,7 @@ void SemanticAnalyzer::visit(Module &node) { function.setTypeID(module->getType(type->s.contents).id); } }); + // TODO classes bool hasMain = false; node.forEachFunction( [this, &hasMain](std::string_view name, Function &function, bool isBuiltin) { diff --git a/src/semanticanalyzer.h b/src/semanticanalyzer.h index 8ac9742..2adc9ed 100644 --- a/src/semanticanalyzer.h +++ b/src/semanticanalyzer.h @@ -38,6 +38,7 @@ class SemanticAnalyzer : public ASTVisitor { void visit(ExpressionStatement &node) override; void visit(LetStatement &node) override; void visit(Function &node) override; + void visit(Class &node) override; void visit(Module &node) override; virtual ~SemanticAnalyzer() = default; }; From 27eeec324603318d65b599e9c177799f40a1c9ab Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:57:52 -0600 Subject: [PATCH 06/32] fix: test error message --- .../syntax/function_missing_fun_keyword/expected/stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/function_missing_fun_keyword/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/syntax/function_missing_fun_keyword/expected/stderr index 2ec2831..36e658c 100644 --- a/test/end_to_end_tests/tests/canyon_failure/syntax/function_missing_fun_keyword/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/syntax/function_missing_fun_keyword/expected/stderr @@ -1 +1 @@ -Error at {main}:1:1: Expected keyword `fun` +Error at {main}:1:1: Expected keyword `fun` or `class` From 02dc63ce6f0acf968d7a7fc0fa414e8f292c587c Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:00:03 -0600 Subject: [PATCH 07/32] fix: class field syntax --- .../tests/success/syntax/class_simple/main.canyon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon b/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon index eebc5fa..5d7cce4 100644 --- a/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon +++ b/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon @@ -2,6 +2,6 @@ fun main() { } class Foo { - x: i32 = 0; - y: i32 = 0; + let x: i32 = 0; + let y: i32 = 0; } \ No newline at end of file From 4ddaf1d7846598e4bc825656494d0db66793c2e7 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:41:39 -0600 Subject: [PATCH 08/32] feat: class definitions --- src/ast.cpp | 23 +++-- src/ast.h | 18 ++-- src/ccodeadapter.cpp | 41 ++++++++- src/ccodeadapter.h | 1 + src/ccodegenerator.cpp | 193 +++++++++++++++++++++++++++------------ src/ccodegenerator.h | 2 +- src/parser.cpp | 10 +- src/semanticanalyzer.cpp | 39 +++++++- 8 files changed, 240 insertions(+), 87 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index ccf5c00..6f5d246 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -408,24 +408,29 @@ void Function::accept(ASTVisitor &visitor) { visitor.visit(*this); } -Class::Class( - std::vector, std::unique_ptr>> fields, - std::vector, std::unique_ptr>> methods) - : fields(std::move(fields)), methods(std::move(methods)) { +Class::Class(std::vector> fieldDeclarations, + std::unordered_map> methods) + : fieldDeclarations(std::move(fieldDeclarations)), methods(std::move(methods)) { } -void Class::forEachField(const std::function &fieldHandler) { - for (auto &[name, type] : fields) { - fieldHandler(*name, *type); +void Class::forEachFieldDeclaration( + const std::function &fieldHandler) { + for (auto &field : fieldDeclarations) { + fieldHandler(*field); } } -void Class::forEachMethod(const std::function &methodHandler) { +void Class::forEachMethod( + const std::function &methodHandler) { for (auto &[name, method] : methods) { - methodHandler(*name, *method); + methodHandler(name, *method); } } +BlockExpression &Class::getScope() { + return *scope; +} + void Class::accept(ASTVisitor &visitor) { visitor.visit(*this); } diff --git a/src/ast.h b/src/ast.h index edaf892..b31d511 100644 --- a/src/ast.h +++ b/src/ast.h @@ -248,6 +248,8 @@ class LetStatement : public Statement { int getSymbolTypeID() const; void accept(ASTVisitor &visitor) override; virtual ~LetStatement() = default; + + friend struct Field; }; class Function : public ASTComponent { @@ -276,14 +278,16 @@ class Function : public ASTComponent { class Class : public ASTComponent { private: - std::vector, std::unique_ptr>> fields; - std::vector, std::unique_ptr>> methods; + std::vector> fieldDeclarations; + // std::unordered_map fields; + std::unordered_map> methods; + std::unique_ptr scope = std::make_unique(); public: - Class(std::vector, std::unique_ptr>> fields, - std::vector, std::unique_ptr>> - methods); - void forEachField(const std::function &fieldHandler); - void forEachMethod(const std::function &methodHandler); + Class(std::vector> fieldDeclarations, + std::unordered_map> methods); + void forEachFieldDeclaration(const std::function &fieldHandler); + void forEachMethod(const std::function &methodHandler); + BlockExpression &getScope(); void accept(ASTVisitor &visitor); ~Class() = default; }; diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index 0c68e72..169b482 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -444,24 +444,55 @@ void CCodeAdapter::visit(Function &node) { std::move(enclosingScope)); } -void CCodeAdapter::visit([[maybe_unused]] Class &node) { - // TODO +void CCodeAdapter::visit(Class &node) { + std::vector> newFieldDeclarations; + node.forEachFieldDeclaration([this, &newFieldDeclarations]( + LetStatement &declaration) { + declaration.accept(*this); + std::unique_ptr newDeclaration = std::unique_ptr( + dynamic_cast(returnValue.release())); + generatedStrings->push_back(std::string(declaration.getSymbol().s.contents)); + std::string_view newName = generatedStrings->back(); + newDeclaration->getSymbol().s.contents = newName; + newFieldDeclarations.push_back(std::move(newDeclaration)); + }); + std::unordered_map> newMethods; + node.forEachMethod([this, &newMethods](std::string_view name, Function &method) { + // + method.accept(*this); + std::unique_ptr newMethod = std::unique_ptr( + dynamic_cast(returnValue.release())); + newMethod->setTypeID(method.getTypeID()); + newMethods.emplace(name, std::move(newMethod)); + }); + returnValue = std::make_unique(std::move(newFieldDeclarations), + std::move(newMethods)); } void CCodeAdapter::visit(Module &node) { node.forEachFunction([this](std::string_view name, Function &oldFunction, bool isBuiltin) { + generatedStrings->push_back("CANYON_FUNCTION_" + std::string(name)); + std::string_view newName = generatedStrings->back(); oldFunction.accept(*this); std::unique_ptr newFunction = std::unique_ptr( dynamic_cast(returnValue.release())); - generatedStrings->push_back("CANYON_FUNCTION_" + std::string(name)); - std::string_view newName = generatedStrings->back(); newFunction->setTypeID(oldFunction.getTypeID()); outputModule->addFunction( std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)), std::move(newFunction), isBuiltin); }); - // TODO classes + node.forEachClass([this](std::string_view name, Class &oldClass, bool isBuiltin) { + generatedStrings->push_back("CANYON_CLASS_" + std::string(name)); + std::string_view newName = generatedStrings->back(); + currentClassName = &generatedStrings->back(); + oldClass.accept(*this); + std::unique_ptr newClass + = std::unique_ptr(dynamic_cast(returnValue.release())); + outputModule->addClass( + std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)), + std::move(newClass), isBuiltin); + }); } void CCodeAdapter::visitExpression(Expression &node) { diff --git a/src/ccodeadapter.h b/src/ccodeadapter.h index 7bc25b5..00a392f 100644 --- a/src/ccodeadapter.h +++ b/src/ccodeadapter.h @@ -22,6 +22,7 @@ class CCodeAdapter : ASTVisitor { std::stack blockTemporaryVariables; std::vector scopeStack; std::list *generatedStrings; + std::string *currentClassName = nullptr; public: CCodeAdapter(Module *module, std::list *generatedStrings); std::unique_ptr transform(); diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index 90160e5..3cba95d 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -212,11 +212,11 @@ void CCodeGenerator::visit([[maybe_unused]] Function &node) { } void CCodeGenerator::visit([[maybe_unused]] Class &node) { - // TODO + // Currently being handled in the Module visitor } void CCodeGenerator::visit(Module &node) { - // Forward declarations + // Function prototypes node.forEachFunction( [this](std::string_view name, Function &function, bool /*unused*/) { Type functionType = module->getType(function.getTypeID()); @@ -234,13 +234,85 @@ void CCodeGenerator::visit(Module &node) { *os << ");\n"; }); *os << '\n'; - generateMain(); - // TODO classes + // Class method prototypes + node.forEachClass([this](std::string_view className, Class &cls, bool /*unused*/) { + cls.forEachMethod([this, &className](std::string_view methodName, + Function &method) { + Type methodType = module->getType(method.getTypeID()); + const std::string &cType = cTypes[methodType.id]; + std::string newName + = std::string(className) + "_METHOD_" + std::string(methodName); + *os << cType << ' ' << newName << '('; + bool first = true; + method.forEachParameter([this, &first](Symbol ¶meter, Symbol &type) { + const std::string &cType = cTypes[module->getType(type.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType << ' ' << parameter.s; + }); + *os << ");\n"; + }); + }); + *os << '\n'; + + // Virtual tables + node.forEachClass([this](std::string_view className, Class &cls, bool /*unused*/) { + *os << "struct " << className << "_VT {\n"; + tabLevel++; + cls.forEachMethod([this](std::string_view methodName, Function &method) { + Type methodType = module->getType(method.getTypeID()); + const std::string &cType = cTypes[methodType.id]; + *os << std::string(tabLevel, '\t') << cType << " (*" << methodName << ")("; + bool first = true; + method.forEachParameter([this, &first](Symbol &/*unused*/, Symbol &type) { + const std::string &cType = cTypes[module->getType(type.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType; + }); + *os << ");\n"; + }); + tabLevel--; + *os << "} " << className << "_VT_INSTANCE = {\n"; + tabLevel++; + cls.forEachMethod( + [this, &className](std::string_view methodName, Function & /*unused*/) { + std::string newName + = std::string(className) + "_METHOD_" + std::string(methodName); + *os << std::string(tabLevel, '\t') << newName << ",\n"; + }); + tabLevel--; + *os << "};\n"; + }); + *os << '\n'; + + // Class structs + node.forEachClass([this](std::string_view name, Class &cls, bool /*unused*/) { + *os << "typedef struct {\n"; + tabLevel++; + *os << std::string(tabLevel, '\t') << "struct " << name << "_VT *vt;\n"; + cls.forEachFieldDeclaration([this](LetStatement &declaration) { + *os << std::string(tabLevel, '\t'); + const std::string &cType = cTypes[declaration.getSymbolTypeID()]; + *os << cType << ' ' << declaration.getSymbol().s << ";\n"; + }); + tabLevel--; + *os << "} *" << name << ";\n"; + }); + *os << '\n'; // Function definitions + generateBuiltinFunctions(); node.forEachFunction( [this](std::string_view name, Function &function, bool isBuiltin) { + if (isBuiltin) { + return; + } Type functionType = module->getType(function.getTypeID()); const std::string &cType = cTypes[functionType.id]; *os << cType << ' ' << name << '('; @@ -254,63 +326,70 @@ void CCodeGenerator::visit(Module &node) { *os << cType << ' ' << parameter.s; }); *os << ") "; - if (!isBuiltin) { - function.getBody().accept(*this); - *os << "\n"; - return; - } - if (name == "CANYON_FUNCTION_printI8") { - *os << "{\n" - " printf(\"%\" PRId8, CANYON_PARAMETER_value);\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printI16") { - *os << "{\n" - " printf(\"%\" PRId16, CANYON_PARAMETER_value);\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printI32") { - *os << "{\n" - " printf(\"%\" PRId32, CANYON_PARAMETER_value);\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printI64") { - *os << "{\n" - " printf(\"%\" PRId64, CANYON_PARAMETER_value);\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printU8") { - *os << "{\n" - " printf(\"%\" PRIu8, CANYON_PARAMETER_value);\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printU16") { - *os << "{\n" - " printf(\"%\" PRIu16, CANYON_PARAMETER_value);\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printU32") { - *os << "{\n" - " printf(\"%\" PRIu32, CANYON_PARAMETER_value);\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printU64") { - *os << "{\n" - " printf(\"%\" PRIu64, CANYON_PARAMETER_value);\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printBool") { - *os << "{\n" - " printf(CANYON_PARAMETER_value ? \"true\" : \"false\");\n" - "}\n"; - } else if (name == "CANYON_FUNCTION_printChar") { - *os << "{\n" - " printf(\"%c\", CANYON_PARAMETER_value);\n" - "}\n"; - } else { - std::cerr << "Unknown builtin function: " << name << '\n'; - exit(EXIT_FAILURE); - } + function.getBody().accept(*this); *os << "\n"; }); + + // Method definitions + node.forEachClass([this](std::string_view className, Class &cls, bool /*unused*/) { + cls.forEachMethod([this, &className](std::string_view methodName, + Function &method) { + Type methodType = module->getType(method.getTypeID()); + const std::string &cType = cTypes[methodType.id]; + std::string newName + = std::string(className) + "_METHOD_" + std::string(methodName); + *os << cType << ' ' << newName << '('; + bool first = true; + method.forEachParameter([this, &first](Symbol ¶meter, Symbol &type) { + const std::string &cType = cTypes[module->getType(type.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType << ' ' << parameter.s; + }); + *os << ") "; + method.getBody().accept(*this); + *os << "\n"; + return; + *os << "\n"; + }); + }); } -void CCodeGenerator::generateMain() { +void CCodeGenerator::generateBuiltinFunctions() { *os << "int main() {\n" - " CANYON_FUNCTION_main();\n" - " return 0;\n" + "\tCANYON_FUNCTION_main();\n" + "\treturn 0;\n" "}\n" - "\n"; + "void CANYON_FUNCTION_printI8(int8_t CANYON_PARAMETER_value) {\n" + "\tprintf(\"%\" PRId8, CANYON_PARAMETER_value);\n" + "}\n" + "void CANYON_FUNCTION_printI16(int16_t CANYON_PARAMETER_value) {\n" + "\tprintf(\"%\" PRId16, CANYON_PARAMETER_value);\n" + "}\n" + "void CANYON_FUNCTION_printI32(int32_t CANYON_PARAMETER_value) {\n" + "\tprintf(\"%\" PRId32, CANYON_PARAMETER_value);\n" + "}\n" + "void CANYON_FUNCTION_printI64(int64_t CANYON_PARAMETER_value) {\n" + "\tprintf(\"%\" PRId64, CANYON_PARAMETER_value);\n" + "}\n" + "void CANYON_FUNCTION_printU8(uint8_t CANYON_PARAMETER_value) {\n" + "\tprintf(\"%\" PRIu8, CANYON_PARAMETER_value);\n" + "}\n" + "void CANYON_FUNCTION_printU16(uint16_t CANYON_PARAMETER_value) {\n" + "\tprintf(\"%\" PRIu16, CANYON_PARAMETER_value);\n" + "}\n" + "void CANYON_FUNCTION_printU32(uint32_t CANYON_PARAMETER_value) {\n" + "\tprintf(\"%\" PRIu32, CANYON_PARAMETER_value);\n" + "}\n" + "void CANYON_FUNCTION_printU64(uint64_t CANYON_PARAMETER_value) {\n" + "\tprintf(\"%\" PRIu64, CANYON_PARAMETER_value);\n" + "}\n" + "void CANYON_FUNCTION_printBool(bool CANYON_PARAMETER_value) {\n" + "\tprintf(CANYON_PARAMETER_value ? \"true\" : \"false\");\n" + "}\n" + "void CANYON_FUNCTION_printChar(char CANYON_PARAMETER_value) {\n" + "\tprintf(\"%c\", CANYON_PARAMETER_value);\n" + "}\n"; } diff --git a/src/ccodegenerator.h b/src/ccodegenerator.h index 47b440b..05baf8c 100644 --- a/src/ccodegenerator.h +++ b/src/ccodegenerator.h @@ -42,7 +42,7 @@ class CCodeGenerator : public ASTVisitor { ~CCodeGenerator() = default; private: void generateIncludes(); - void generateMain(); + void generateBuiltinFunctions(); }; #endif diff --git a/src/parser.cpp b/src/parser.cpp index b55abc1..1739d54 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -61,6 +61,7 @@ std::unique_ptr Parser::parse() { } continue; } + mod->addClass(std::move(cls.first), std::move(cls.second)); } else { errorHandler->error(*tokens[i], "Expected keyword `fun` or `class`"); synchronize(); @@ -196,8 +197,8 @@ std::pair, std::unique_ptr> Parser::parseClass() } i++; - std::vector, std::unique_ptr>> fields; - std::vector, std::unique_ptr>> methods; + std::vector> fields; + std::unordered_map> methods; while (true) { auto *keyword2 = dynamic_cast(tokens[i].get()); if (keyword2 != nullptr && keyword2->type == Keyword::Type::LET) { @@ -205,15 +206,14 @@ std::pair, std::unique_ptr> Parser::parseClass() if (let == nullptr) { return {nullptr, nullptr}; } - fields.emplace_back(std::make_unique(let->getSymbol()), - std::make_unique(*let->getTypeAnnotation())); + fields.push_back(std::move(let)); } else if (keyword2 != nullptr && keyword2->type == Keyword::Type::FUN) { std::pair, std::unique_ptr> method = parseFunction(); if (method.second == nullptr) { return {nullptr, nullptr}; } - methods.push_back(std::move(method)); + methods[method.first->s.contents] = std::move(method.second); } else { break; } diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index fb9f556..cc20872 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -353,8 +353,15 @@ void SemanticAnalyzer::visit(Function &node) { inUnreachableCode = false; } -void SemanticAnalyzer::visit([[maybe_unused]] Class &node) { - // TODO +void SemanticAnalyzer::visit(Class &node) { + scopeStack.push_back(&node.getScope()); + node.forEachFieldDeclaration([this](LetStatement &declaration) { + declaration.accept(*this); + }); + node.forEachMethod([this](std::string_view /*unused*/, Function &method) { + currentFunction = &method; + method.accept(*this); + }); } void SemanticAnalyzer::visit(Module &node) { @@ -380,7 +387,27 @@ void SemanticAnalyzer::visit(Module &node) { function.setTypeID(module->getType(type->s.contents).id); } }); - // TODO classes + node.forEachClass([this](std::string_view /*unused*/, Class &cls, bool /*unused*/) { + cls.forEachMethod([this](std::string_view /*unused*/, Function &method) { + method.forEachParameter([this, &method](Symbol ¶meter, Symbol &type) { + int typeID = module->getType(type.s.contents).id; + if (typeID == -1) { + errorHandler->error(type.s, "Unknown type"); + return; + } + method.getBody().pushSymbol(parameter.s.contents, typeID, + SymbolSource::FunctionParameter); + }); + + Symbol *type = method.getReturnTypeAnnotation(); + if (type == nullptr) { + method.setTypeID(module->getType("()").id); + } else { + method.setTypeID(module->getType(type->s.contents).id); + } + }); + }); + bool hasMain = false; node.forEachFunction( [this, &hasMain](std::string_view name, Function &function, bool isBuiltin) { @@ -397,6 +424,12 @@ void SemanticAnalyzer::visit(Module &node) { } } }); + node.forEachClass([this](std::string_view /*unused*/, Class &cls, bool isBuiltin) { + if (isBuiltin) { + return; + } + cls.accept(*this); + }); if (!hasMain) { errorHandler->error(module->getSource(), "No main function"); } From f3473fcb4d97695dc039aaa7954d496d42659466 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:41:53 -0600 Subject: [PATCH 09/32] test: class definition without keyword --- .../canyon_failure/syntax/class_missing_keyword/expected/stderr | 1 + .../canyon_failure/syntax/class_missing_keyword/main.canyon | 1 + 2 files changed, 2 insertions(+) create mode 100644 test/end_to_end_tests/tests/canyon_failure/syntax/class_missing_keyword/expected/stderr create mode 100644 test/end_to_end_tests/tests/canyon_failure/syntax/class_missing_keyword/main.canyon diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/class_missing_keyword/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/syntax/class_missing_keyword/expected/stderr new file mode 100644 index 0000000..36e658c --- /dev/null +++ b/test/end_to_end_tests/tests/canyon_failure/syntax/class_missing_keyword/expected/stderr @@ -0,0 +1 @@ +Error at {main}:1:1: Expected keyword `fun` or `class` diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/class_missing_keyword/main.canyon b/test/end_to_end_tests/tests/canyon_failure/syntax/class_missing_keyword/main.canyon new file mode 100644 index 0000000..495cee6 --- /dev/null +++ b/test/end_to_end_tests/tests/canyon_failure/syntax/class_missing_keyword/main.canyon @@ -0,0 +1 @@ +Foo {} From 6542cca356fc5862448cb219352bfdd32c7a67c6 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:33:18 -0600 Subject: [PATCH 10/32] test: constructor keyword --- test/unit_tests/test_lexer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit_tests/test_lexer.cpp b/test/unit_tests/test_lexer.cpp index 168adb6..c467d6c 100644 --- a/test/unit_tests/test_lexer.cpp +++ b/test/unit_tests/test_lexer.cpp @@ -535,7 +535,8 @@ INSTANTIATE_TEST_SUITE_P(testKeywords, TestLexerKeywords, std::pair{"let", Keyword::Type::LET}, std::pair{"fun", Keyword::Type::FUN}, std::pair{"if", Keyword::Type::IF}, std::pair{"else", Keyword::Type::ELSE}, std::pair{"while", Keyword::Type::WHILE}, - std::pair{"class", Keyword::Type::CLASS})); + std::pair{"class", Keyword::Type::CLASS}, + std::pair{"constructor", Keyword::Type::CONSTRUCTOR})); TEST_P(TestLexerSymbols, testSymbols) { const std::string symbol = GetParam(); From 5ffd68aa92db3bd7ccf196a91e0da259f38bd498 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:36:48 -0600 Subject: [PATCH 11/32] feat(Lexer): constructor keyword --- src/lexer.cpp | 3 +++ src/tokens.cpp | 4 ++++ src/tokens.h | 1 + 3 files changed, 8 insertions(+) diff --git a/src/lexer.cpp b/src/lexer.cpp index 111e18d..19f5773 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -237,6 +237,9 @@ std::unique_ptr Lexer::createKeyword(const Slice &s) { if (s.contents == "class") { return std::make_unique(s, Keyword::Type::CLASS); } + if (s.contents == "constructor") { + return std::make_unique(s, Keyword::Type::CONSTRUCTOR); + } return nullptr; } diff --git a/src/tokens.cpp b/src/tokens.cpp index ca35eeb..f859a56 100644 --- a/src/tokens.cpp +++ b/src/tokens.cpp @@ -63,6 +63,10 @@ void Keyword::print(std::ostream &os) const { os << "class"; break; } + case Type::CONSTRUCTOR: { + os << "constructor"; + break; + } } } diff --git a/src/tokens.h b/src/tokens.h index 8d98843..6d5744a 100644 --- a/src/tokens.h +++ b/src/tokens.h @@ -51,6 +51,7 @@ struct Keyword : public Token { ELSE, WHILE, CLASS, + CONSTRUCTOR, }; Type type; From 565b55fe48120d50732812d403373b8d2ddd1ec5 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:39:26 -0600 Subject: [PATCH 12/32] test: impl block --- .../tests/success/syntax/impl/main.canyon | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/end_to_end_tests/tests/success/syntax/impl/main.canyon diff --git a/test/end_to_end_tests/tests/success/syntax/impl/main.canyon b/test/end_to_end_tests/tests/success/syntax/impl/main.canyon new file mode 100644 index 0000000..213daa5 --- /dev/null +++ b/test/end_to_end_tests/tests/success/syntax/impl/main.canyon @@ -0,0 +1,8 @@ +fun main() {} + +class Foo { +} + +impl Foo { + fun bar() {} +} From 323dc217ee915c69a5c1f09457ad1aabc5c4e0a2 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:42:02 -0600 Subject: [PATCH 13/32] test: impl keyword --- test/unit_tests/test_lexer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit_tests/test_lexer.cpp b/test/unit_tests/test_lexer.cpp index c467d6c..3f8087a 100644 --- a/test/unit_tests/test_lexer.cpp +++ b/test/unit_tests/test_lexer.cpp @@ -536,7 +536,8 @@ INSTANTIATE_TEST_SUITE_P(testKeywords, TestLexerKeywords, std::pair{"if", Keyword::Type::IF}, std::pair{"else", Keyword::Type::ELSE}, std::pair{"while", Keyword::Type::WHILE}, std::pair{"class", Keyword::Type::CLASS}, - std::pair{"constructor", Keyword::Type::CONSTRUCTOR})); + std::pair{"constructor", Keyword::Type::CONSTRUCTOR}, + std::pair{"impl", Keyword::Type::IMPL})); TEST_P(TestLexerSymbols, testSymbols) { const std::string symbol = GetParam(); From 60dbfd98767fc17a4c70e0822c52ebbe4f6bffb1 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:42:23 -0600 Subject: [PATCH 14/32] feat(Lexer): impl keyword --- src/lexer.cpp | 3 +++ src/tokens.cpp | 4 ++++ src/tokens.h | 1 + 3 files changed, 8 insertions(+) diff --git a/src/lexer.cpp b/src/lexer.cpp index 19f5773..b35c47e 100644 --- a/src/lexer.cpp +++ b/src/lexer.cpp @@ -240,6 +240,9 @@ std::unique_ptr Lexer::createKeyword(const Slice &s) { if (s.contents == "constructor") { return std::make_unique(s, Keyword::Type::CONSTRUCTOR); } + if (s.contents == "impl") { + return std::make_unique(s, Keyword::Type::IMPL); + } return nullptr; } diff --git a/src/tokens.cpp b/src/tokens.cpp index f859a56..8a4b09e 100644 --- a/src/tokens.cpp +++ b/src/tokens.cpp @@ -67,6 +67,10 @@ void Keyword::print(std::ostream &os) const { os << "constructor"; break; } + case Type::IMPL: { + os << "impl"; + break; + } } } diff --git a/src/tokens.h b/src/tokens.h index 6d5744a..dd17dc6 100644 --- a/src/tokens.h +++ b/src/tokens.h @@ -52,6 +52,7 @@ struct Keyword : public Token { WHILE, CLASS, CONSTRUCTOR, + IMPL, }; Type type; From 45bcdafc217a34a676c30afe35f65cd5f7447ba1 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 00:44:42 -0600 Subject: [PATCH 15/32] test: classes should not directly contain methods --- .../canyon_failure/syntax/class_only_data/expected/stdout | 1 + .../tests/canyon_failure/syntax/class_only_data/main.canyon | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stdout create mode 100644 test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/main.canyon diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stdout b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stdout new file mode 100644 index 0000000..4ec3fe7 --- /dev/null +++ b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stdout @@ -0,0 +1 @@ +Error at {main}:4:5: Unexpected keyword `fun` diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/main.canyon b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/main.canyon new file mode 100644 index 0000000..b52b5a1 --- /dev/null +++ b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/main.canyon @@ -0,0 +1,5 @@ +fun main() {} + +class Foo { + fun shouldFail() {} +} \ No newline at end of file From e9205777a99d6570d8f05cf33dc2cc54b80816c5 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:29:43 -0600 Subject: [PATCH 16/32] feat(Parser): impl blocks --- src/ast.cpp | 23 ++++- src/ast.h | 25 ++++-- src/ccodeadapter.cpp | 16 ++-- src/ccodeadapter.h | 1 + src/ccodegenerator.cpp | 86 +------------------ src/ccodegenerator.h | 1 + src/parser.cpp | 80 +++++++++++++++-- src/parser.h | 1 + src/semanticanalyzer.cpp | 28 +----- src/semanticanalyzer.h | 1 + .../syntax/class_only_data/expected/stderr | 2 + .../syntax/class_only_data/expected/stdout | 1 - .../syntax/class_only_data/main.canyon | 7 ++ 13 files changed, 136 insertions(+), 136 deletions(-) create mode 100644 test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stderr delete mode 100644 test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stdout diff --git a/src/ast.cpp b/src/ast.cpp index 6f5d246..5ec34bb 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -408,9 +408,8 @@ void Function::accept(ASTVisitor &visitor) { visitor.visit(*this); } -Class::Class(std::vector> fieldDeclarations, - std::unordered_map> methods) - : fieldDeclarations(std::move(fieldDeclarations)), methods(std::move(methods)) { +Class::Class(std::vector> fieldDeclarations) + : fieldDeclarations(std::move(fieldDeclarations)) { } void Class::forEachFieldDeclaration( @@ -420,13 +419,21 @@ void Class::forEachFieldDeclaration( } } -void Class::forEachMethod( +Impl::Impl(std::unordered_map> methods) + : methods(std::move(methods)) { +} + +void Impl::forEachMethod( const std::function &methodHandler) { for (auto &[name, method] : methods) { methodHandler(name, *method); } } +void Impl::accept(ASTVisitor &visitor) { + visitor.visit(*this); +} + BlockExpression &Class::getScope() { return *scope; } @@ -477,6 +484,11 @@ void Module::addClass(std::unique_ptr name, std::unique_ptr cls, classes[name->s.contents] = {std::move(cls), isBuiltin}; } +void Module::addImpl(std::unique_ptr className, std::unique_ptr impl, + bool isBuiltin) { + impls[className->s.contents] = {std::move(impl), isBuiltin}; +} + void Module::forEachClass( const std::function &classHandler) { for (auto &[name, cls] : classes) { @@ -712,6 +724,9 @@ void ASTPrinter::visit([[maybe_unused]] Function &node) { void ASTPrinter::visit([[maybe_unused]] Class &node) { } +void ASTPrinter::visit([[maybe_unused]] Impl &node) { +} + void ASTPrinter::visit(Module &node) { node.forEachFunction( [this](std::string_view name, Function &function, bool /*unused*/) { diff --git a/src/ast.h b/src/ast.h index b31d511..ff23835 100644 --- a/src/ast.h +++ b/src/ast.h @@ -70,7 +70,6 @@ class BinaryExpression : public Expression { Operator &getOperator(); void accept(ASTVisitor &visitor) override; virtual ~BinaryExpression() = default; - friend class ASTVisitor; }; class UnaryExpression : public Expression { @@ -248,8 +247,6 @@ class LetStatement : public Statement { int getSymbolTypeID() const; void accept(ASTVisitor &visitor) override; virtual ~LetStatement() = default; - - friend struct Field; }; class Function : public ASTComponent { @@ -279,19 +276,26 @@ class Function : public ASTComponent { class Class : public ASTComponent { private: std::vector> fieldDeclarations; - // std::unordered_map fields; - std::unordered_map> methods; std::unique_ptr scope = std::make_unique(); public: - Class(std::vector> fieldDeclarations, - std::unordered_map> methods); + Class(std::vector> fieldDeclarations); void forEachFieldDeclaration(const std::function &fieldHandler); - void forEachMethod(const std::function &methodHandler); BlockExpression &getScope(); void accept(ASTVisitor &visitor); ~Class() = default; }; +class Impl : public ASTComponent { +private: + std::unordered_map> methods; +public: + Impl(std::unordered_map> methods); + void forEachMethod( + const std::function &methodHandler); + void accept(ASTVisitor &visitor); + ~Impl() = default; +}; + struct Type { int id; int parentID; @@ -312,6 +316,7 @@ class Module : public ASTComponent { std::filesystem::path source; std::unordered_map, bool>> classes; + std::unordered_map, bool>> impls; public: std::list ownedStrings; explicit Module(std::filesystem::path source); @@ -322,6 +327,8 @@ class Module : public ASTComponent { const std::function &functionHandler); void addClass(std::unique_ptr name, std::unique_ptr cls, bool isBuiltin = false); + void addImpl(std::unique_ptr className, std::unique_ptr impl, + bool isBuiltin = false); void forEachClass( const std::function &classHandler); Type getType(std::string_view typeName); @@ -358,6 +365,7 @@ class ASTVisitor { virtual void visit(LetStatement &node) = 0; virtual void visit(Function &node) = 0; virtual void visit(Class &node) = 0; + virtual void visit(Impl &node) = 0; virtual void visit(Module &node) = 0; virtual ~ASTVisitor() = default; }; @@ -381,6 +389,7 @@ class ASTPrinter : public ASTVisitor { void visit(LetStatement &node) override; void visit(Function &node) override; void visit(Class &node) override; + void visit(Impl &node) override; void visit(Module &node) override; virtual ~ASTPrinter() = default; }; diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index 169b482..2ff4412 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -456,17 +456,11 @@ void CCodeAdapter::visit(Class &node) { newDeclaration->getSymbol().s.contents = newName; newFieldDeclarations.push_back(std::move(newDeclaration)); }); - std::unordered_map> newMethods; - node.forEachMethod([this, &newMethods](std::string_view name, Function &method) { - // - method.accept(*this); - std::unique_ptr newMethod = std::unique_ptr( - dynamic_cast(returnValue.release())); - newMethod->setTypeID(method.getTypeID()); - newMethods.emplace(name, std::move(newMethod)); - }); - returnValue = std::make_unique(std::move(newFieldDeclarations), - std::move(newMethods)); + returnValue = std::make_unique(std::move(newFieldDeclarations)); +} + +void CCodeAdapter::visit([[maybe_unused]] Impl &node) { + // TODO } void CCodeAdapter::visit(Module &node) { diff --git a/src/ccodeadapter.h b/src/ccodeadapter.h index 00a392f..83ef761 100644 --- a/src/ccodeadapter.h +++ b/src/ccodeadapter.h @@ -42,6 +42,7 @@ class CCodeAdapter : ASTVisitor { void visit(LetStatement &node) override; void visit(Function &node) override; void visit(Class &node) override; + void visit(Impl &node) override; void visit(Module &node) override; virtual ~CCodeAdapter() = default; private: diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index 3cba95d..be4f144 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -215,6 +215,10 @@ void CCodeGenerator::visit([[maybe_unused]] Class &node) { // Currently being handled in the Module visitor } +void CCodeGenerator::visit([[maybe_unused]] Impl &node) { + // TODO +} + void CCodeGenerator::visit(Module &node) { // Function prototypes node.forEachFunction( @@ -235,62 +239,6 @@ void CCodeGenerator::visit(Module &node) { }); *os << '\n'; - // Class method prototypes - node.forEachClass([this](std::string_view className, Class &cls, bool /*unused*/) { - cls.forEachMethod([this, &className](std::string_view methodName, - Function &method) { - Type methodType = module->getType(method.getTypeID()); - const std::string &cType = cTypes[methodType.id]; - std::string newName - = std::string(className) + "_METHOD_" + std::string(methodName); - *os << cType << ' ' << newName << '('; - bool first = true; - method.forEachParameter([this, &first](Symbol ¶meter, Symbol &type) { - const std::string &cType = cTypes[module->getType(type.s.contents).id]; - if (!first) { - *os << ", "; - } - first = false; - *os << cType << ' ' << parameter.s; - }); - *os << ");\n"; - }); - }); - *os << '\n'; - - // Virtual tables - node.forEachClass([this](std::string_view className, Class &cls, bool /*unused*/) { - *os << "struct " << className << "_VT {\n"; - tabLevel++; - cls.forEachMethod([this](std::string_view methodName, Function &method) { - Type methodType = module->getType(method.getTypeID()); - const std::string &cType = cTypes[methodType.id]; - *os << std::string(tabLevel, '\t') << cType << " (*" << methodName << ")("; - bool first = true; - method.forEachParameter([this, &first](Symbol &/*unused*/, Symbol &type) { - const std::string &cType = cTypes[module->getType(type.s.contents).id]; - if (!first) { - *os << ", "; - } - first = false; - *os << cType; - }); - *os << ");\n"; - }); - tabLevel--; - *os << "} " << className << "_VT_INSTANCE = {\n"; - tabLevel++; - cls.forEachMethod( - [this, &className](std::string_view methodName, Function & /*unused*/) { - std::string newName - = std::string(className) + "_METHOD_" + std::string(methodName); - *os << std::string(tabLevel, '\t') << newName << ",\n"; - }); - tabLevel--; - *os << "};\n"; - }); - *os << '\n'; - // Class structs node.forEachClass([this](std::string_view name, Class &cls, bool /*unused*/) { *os << "typedef struct {\n"; @@ -329,32 +277,6 @@ void CCodeGenerator::visit(Module &node) { function.getBody().accept(*this); *os << "\n"; }); - - // Method definitions - node.forEachClass([this](std::string_view className, Class &cls, bool /*unused*/) { - cls.forEachMethod([this, &className](std::string_view methodName, - Function &method) { - Type methodType = module->getType(method.getTypeID()); - const std::string &cType = cTypes[methodType.id]; - std::string newName - = std::string(className) + "_METHOD_" + std::string(methodName); - *os << cType << ' ' << newName << '('; - bool first = true; - method.forEachParameter([this, &first](Symbol ¶meter, Symbol &type) { - const std::string &cType = cTypes[module->getType(type.s.contents).id]; - if (!first) { - *os << ", "; - } - first = false; - *os << cType << ' ' << parameter.s; - }); - *os << ") "; - method.getBody().accept(*this); - *os << "\n"; - return; - *os << "\n"; - }); - }); } void CCodeGenerator::generateBuiltinFunctions() { diff --git a/src/ccodegenerator.h b/src/ccodegenerator.h index 05baf8c..57abaab 100644 --- a/src/ccodegenerator.h +++ b/src/ccodegenerator.h @@ -38,6 +38,7 @@ class CCodeGenerator : public ASTVisitor { void visit(LetStatement &node) override; void visit(Function &node) override; void visit(Class &node) override; + void visit(Impl &node) override; void visit(Module &node) override; ~CCodeGenerator() = default; private: diff --git a/src/parser.cpp b/src/parser.cpp index 1739d54..5f8de6b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -62,6 +62,29 @@ std::unique_ptr Parser::parse() { continue; } mod->addClass(std::move(cls.first), std::move(cls.second)); + } else if (keyword != nullptr && keyword->type == Keyword::Type::IMPL) { + std::pair, std::unique_ptr> impl = parseImpl(); + if (impl.second == nullptr) { + synchronize(); + mustSynchronize = false; + auto *punc = dynamic_cast(tokens[i].get()); + while (punc != nullptr) { + if (punc->type == Punctuation::Type::Semicolon) { + i++; + synchronize(); + mustSynchronize = false; + punc = dynamic_cast(tokens[i].get()); + } else if (punc->type == Punctuation::Type::CloseBrace) { + i++; + break; + } else { + std::cerr << "Unexpected token in parse" << std::endl; + exit(EXIT_FAILURE); + } + } + continue; + } + mod->addImpl(std::move(impl.first), std::move(impl.second)); } else { errorHandler->error(*tokens[i], "Expected keyword `fun` or `class`"); synchronize(); @@ -198,7 +221,6 @@ std::pair, std::unique_ptr> Parser::parseClass() i++; std::vector> fields; - std::unordered_map> methods; while (true) { auto *keyword2 = dynamic_cast(tokens[i].get()); if (keyword2 != nullptr && keyword2->type == Keyword::Type::LET) { @@ -207,13 +229,60 @@ std::pair, std::unique_ptr> Parser::parseClass() return {nullptr, nullptr}; } fields.push_back(std::move(let)); - } else if (keyword2 != nullptr && keyword2->type == Keyword::Type::FUN) { + } else if (keyword2 != nullptr) { + errorHandler->error(*tokens[i], "Unexpected keyword in class definition"); + mustSynchronize = true; + return {nullptr, nullptr}; + } else { + break; + } + } + + auto *p2 = dynamic_cast(tokens[i].get()); + if (p2 == nullptr || p2->type != Punctuation::Type::CloseBrace) { + errorHandler->error(*tokens[i], "Expected '}' after class definition"); + return {nullptr, nullptr}; + } + i++; + + return {std::unique_ptr(symbol), std::make_unique(std::move(fields))}; +} + +std::pair, std::unique_ptr> Parser::parseImpl() { + auto *keyword = dynamic_cast(tokens[i].get()); + if (keyword == nullptr || keyword->type != Keyword::Type::IMPL) { + errorHandler->error(*tokens[i], "Expected keyword `impl`"); + return {nullptr, nullptr}; + } + i++; + auto *symbol = dynamic_cast(tokens[i].get()); + if (symbol == nullptr) { + errorHandler->error(*tokens[i], "Expected symbol following `impl`"); + return {nullptr, nullptr}; + } + symbol = dynamic_cast(tokens[i++].release()); + auto *punc = dynamic_cast(tokens[i].get()); + if (punc == nullptr || punc->type != Punctuation::Type::OpenBrace) { + errorHandler->error(*tokens[i], "Expected '{' following symbol in impl block"); + mustSynchronize = true; + return {nullptr, nullptr}; + } + i++; + + std::unordered_map> methods; + while (true) { + auto *keyword2 = dynamic_cast(tokens[i].get()); + if (keyword2 != nullptr && keyword2->type == Keyword::Type::FUN) { std::pair, std::unique_ptr> method = parseFunction(); if (method.second == nullptr) { return {nullptr, nullptr}; } - methods[method.first->s.contents] = std::move(method.second); + methods.emplace(method.first->s.contents, std::move(method.second)); + } else if (keyword2 != nullptr) { + errorHandler->error(*tokens[i], "Unexpected keyword in impl block"); + mustSynchronize = true; + return {nullptr, nullptr}; } else { break; } @@ -221,13 +290,12 @@ std::pair, std::unique_ptr> Parser::parseClass() auto *p2 = dynamic_cast(tokens[i].get()); if (p2 == nullptr || p2->type != Punctuation::Type::CloseBrace) { - errorHandler->error(*tokens[i], "Expected '}' after class definition"); + errorHandler->error(*tokens[i], "Expected '}' after impl block"); return {nullptr, nullptr}; } i++; - return {std::unique_ptr(symbol), - std::make_unique(std::move(fields), std::move(methods))}; + return {std::unique_ptr(symbol), std::make_unique(std::move(methods))}; } std::unique_ptr Parser::parseStatement() { diff --git a/src/parser.h b/src/parser.h index f03a117..29740e1 100644 --- a/src/parser.h +++ b/src/parser.h @@ -27,6 +27,7 @@ class Parser { private: std::pair, std::unique_ptr> parseFunction(); std::pair, std::unique_ptr> parseClass(); + std::pair, std::unique_ptr> parseImpl(); std::unique_ptr parseStatement(); std::unique_ptr parseLet(); std::unique_ptr parseExpression(); diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index cc20872..0f33674 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -358,10 +358,10 @@ void SemanticAnalyzer::visit(Class &node) { node.forEachFieldDeclaration([this](LetStatement &declaration) { declaration.accept(*this); }); - node.forEachMethod([this](std::string_view /*unused*/, Function &method) { - currentFunction = &method; - method.accept(*this); - }); +} + +void SemanticAnalyzer::visit([[maybe_unused]] Impl &node) { + // TODO } void SemanticAnalyzer::visit(Module &node) { @@ -387,26 +387,6 @@ void SemanticAnalyzer::visit(Module &node) { function.setTypeID(module->getType(type->s.contents).id); } }); - node.forEachClass([this](std::string_view /*unused*/, Class &cls, bool /*unused*/) { - cls.forEachMethod([this](std::string_view /*unused*/, Function &method) { - method.forEachParameter([this, &method](Symbol ¶meter, Symbol &type) { - int typeID = module->getType(type.s.contents).id; - if (typeID == -1) { - errorHandler->error(type.s, "Unknown type"); - return; - } - method.getBody().pushSymbol(parameter.s.contents, typeID, - SymbolSource::FunctionParameter); - }); - - Symbol *type = method.getReturnTypeAnnotation(); - if (type == nullptr) { - method.setTypeID(module->getType("()").id); - } else { - method.setTypeID(module->getType(type->s.contents).id); - } - }); - }); bool hasMain = false; node.forEachFunction( diff --git a/src/semanticanalyzer.h b/src/semanticanalyzer.h index 2adc9ed..d03b322 100644 --- a/src/semanticanalyzer.h +++ b/src/semanticanalyzer.h @@ -39,6 +39,7 @@ class SemanticAnalyzer : public ASTVisitor { void visit(LetStatement &node) override; void visit(Function &node) override; void visit(Class &node) override; + void visit(Impl &node) override; void visit(Module &node) override; virtual ~SemanticAnalyzer() = default; }; diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stderr new file mode 100644 index 0000000..68219c9 --- /dev/null +++ b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stderr @@ -0,0 +1,2 @@ +Error at {main}:11:5: Unexpected keyword in class definition +Error at {main}:12:1: Expected keyword `fun` or `class` diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stdout b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stdout deleted file mode 100644 index 4ec3fe7..0000000 --- a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stdout +++ /dev/null @@ -1 +0,0 @@ -Error at {main}:4:5: Unexpected keyword `fun` diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/main.canyon b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/main.canyon index b52b5a1..1876799 100644 --- a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/main.canyon +++ b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/main.canyon @@ -1,5 +1,12 @@ fun main() {} +/* The error on this one is a little weird due to how the parser tries to get itself back + * into a good state after the bad keyword. It looks for the first closing brace, not + * realizing that to the programmer it appears part of the function, not the closing brace + * of the class. So two errors are reported, one for the bad keyword, and one for the + * extra closing brace. + */ + class Foo { fun shouldFail() {} } \ No newline at end of file From 94273b6f41b35ecbd52096f670bf83e1edb3ca37 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:45:19 -0600 Subject: [PATCH 17/32] feat(SemanticAnalyzer): impl blocks --- src/semanticanalyzer.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index 0f33674..6fd11f5 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -361,7 +361,9 @@ void SemanticAnalyzer::visit(Class &node) { } void SemanticAnalyzer::visit([[maybe_unused]] Impl &node) { - // TODO + node.forEachMethod([this](std::string_view /*unused*/, Function &method) { + method.accept(*this); + }); } void SemanticAnalyzer::visit(Module &node) { @@ -387,6 +389,26 @@ void SemanticAnalyzer::visit(Module &node) { function.setTypeID(module->getType(type->s.contents).id); } }); + node.forEachImpl([this](std::string_view /*unused*/, Impl &impl, bool /*unused*/) { + impl.forEachMethod([this](std::string_view /*unused*/, Function &method) { + method.forEachParameter([this, &method](Symbol ¶meter, Symbol &type) { + int typeID = module->getType(type.s.contents).id; + if (typeID == -1) { + errorHandler->error(type.s, "Unknown type"); + return; + } + method.getBody().pushSymbol(parameter.s.contents, typeID, + SymbolSource::FunctionParameter); + }); + + Symbol *type = method.getReturnTypeAnnotation(); + if (type == nullptr) { + method.setTypeID(module->getType("()").id); + } else { + method.setTypeID(module->getType(type->s.contents).id); + } + }); + }); bool hasMain = false; node.forEachFunction( @@ -404,6 +426,12 @@ void SemanticAnalyzer::visit(Module &node) { } } }); + node.forEachImpl([this](std::string_view /*unused*/, Impl &impl, bool isBuiltin) { + if (isBuiltin) { + return; + } + impl.accept(*this); + }); node.forEachClass([this](std::string_view /*unused*/, Class &cls, bool isBuiltin) { if (isBuiltin) { return; From c0bc6dd8e5e4d2b1963dce892e4a89f3fdd81079 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:45:40 -0600 Subject: [PATCH 18/32] feat(CCodeGenerator): impl blocks --- src/ast.cpp | 7 ++++ src/ast.h | 1 + src/ccodeadapter.cpp | 22 ++++++++++-- src/ccodegenerator.cpp | 79 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 5ec34bb..af45684 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -496,6 +496,13 @@ void Module::forEachClass( } } +void Module::forEachImpl( + const std::function &implHandler) { + for (auto &[name, impl] : impls) { + implHandler(name, *std::get<0>(impl), std::get<1>(impl)); + } +} + Type Module::getType(std::string_view typeName) { if (typeTableByName.find(typeName) == typeTableByName.end()) { return Type(-1, -1, ""); diff --git a/src/ast.h b/src/ast.h index ff23835..7e52a83 100644 --- a/src/ast.h +++ b/src/ast.h @@ -331,6 +331,7 @@ class Module : public ASTComponent { bool isBuiltin = false); void forEachClass( const std::function &classHandler); + void forEachImpl(const std::function &implHandler); Type getType(std::string_view typeName); Type getType(int id); void insertType(std::string_view typeName); diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index 2ff4412..f2e8286 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -459,8 +459,16 @@ void CCodeAdapter::visit(Class &node) { returnValue = std::make_unique(std::move(newFieldDeclarations)); } -void CCodeAdapter::visit([[maybe_unused]] Impl &node) { - // TODO +void CCodeAdapter::visit(Impl &node) { + std::unordered_map> newMethods; + node.forEachMethod([this, &newMethods](std::string_view name, Function &method) { + method.accept(*this); + std::unique_ptr newMethod = std::unique_ptr( + dynamic_cast(returnValue.release())); + newMethod->setTypeID(method.getTypeID()); + newMethods.emplace(name, std::move(newMethod)); + }); + returnValue = std::make_unique(std::move(newMethods)); } void CCodeAdapter::visit(Module &node) { @@ -487,6 +495,16 @@ void CCodeAdapter::visit(Module &node) { std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)), std::move(newClass), isBuiltin); }); + node.forEachImpl([this](std::string_view name, Impl &oldImpl, bool isBuiltin) { + generatedStrings->push_back("CANYON_IMPL_" + std::string(name)); + std::string_view newName = generatedStrings->back(); + oldImpl.accept(*this); + std::unique_ptr newImpl + = std::unique_ptr(dynamic_cast(returnValue.release())); + outputModule->addImpl( + std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)), + std::move(newImpl), isBuiltin); + }); } void CCodeAdapter::visitExpression(Expression &node) { diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index be4f144..e81b764 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -239,11 +239,63 @@ void CCodeGenerator::visit(Module &node) { }); *os << '\n'; + // Method prototypes + node.forEachImpl([this](std::string_view name, Impl &impl, bool /*unused*/) { + impl.forEachMethod([this, &name](std::string_view methodName, Function &method) { + Type functionType = module->getType(method.getTypeID()); + const std::string &cType = cTypes[functionType.id]; + *os << cType << ' ' << name << "_METHOD_" << methodName << '('; + bool first = true; + method.forEachParameter([this, &first](Symbol ¶meter, Symbol &type) { + const std::string &cType = cTypes[module->getType(type.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType << ' ' << parameter.s; + }); + *os << ");\n"; + }); + }); + *os << '\n'; + + // Virtual tables + node.forEachImpl([this](std::string_view name, Impl &impl, bool /*unused*/) { + *os << "struct " << name << "_VT {\n"; + tabLevel++; + impl.forEachMethod([this](std::string_view methodName, Function &method) { + Type methodType = module->getType(method.getTypeID()); + const std::string &cType = cTypes[methodType.id]; + *os << std::string(tabLevel, '\t') << cType << " (*" << methodName << ")("; + bool first = true; + method.forEachParameter([this, &first](Symbol & /*unused*/, Symbol &type) { + const std::string &cType = cTypes[module->getType(type.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType; + }); + *os << ");\n"; + }); + tabLevel--; + *os << "} " << name << "_VT_INSTANCE = {\n"; + tabLevel++; + impl.forEachMethod( + [this, &name](std::string_view methodName, Function & /*unused*/) { + std::string newName + = std::string(name) + "_METHOD_" + std::string(methodName); + *os << std::string(tabLevel, '\t') << newName << ",\n"; + }); + tabLevel--; + *os << "};\n"; + }); + *os << '\n'; + // Class structs node.forEachClass([this](std::string_view name, Class &cls, bool /*unused*/) { *os << "typedef struct {\n"; tabLevel++; - *os << std::string(tabLevel, '\t') << "struct " << name << "_VT *vt;\n"; cls.forEachFieldDeclaration([this](LetStatement &declaration) { *os << std::string(tabLevel, '\t'); const std::string &cType = cTypes[declaration.getSymbolTypeID()]; @@ -277,6 +329,31 @@ void CCodeGenerator::visit(Module &node) { function.getBody().accept(*this); *os << "\n"; }); + *os << '\n'; + + // Method definitions + node.forEachImpl([this](std::string_view name, Impl &impl, bool isBuiltin) { + if (isBuiltin) { + return; + } + impl.forEachMethod([this, &name](std::string_view methodName, Function &method) { + Type functionType = module->getType(method.getTypeID()); + const std::string &cType = cTypes[functionType.id]; + *os << cType << ' ' << name << "_METHOD_" << methodName << '('; + bool first = true; + method.forEachParameter([this, &first](Symbol ¶meter, Symbol &type) { + const std::string &cType = cTypes[module->getType(type.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType << ' ' << parameter.s; + }); + *os << ") "; + method.getBody().accept(*this); + *os << "\n"; + }); + }); } void CCodeGenerator::generateBuiltinFunctions() { From fdf0e0d0f002d04418a02fc9afc0cdef0f193375 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:51:20 -0600 Subject: [PATCH 19/32] test: static method --- .../tests/success/semantics/method_static/main.canyon | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/end_to_end_tests/tests/success/semantics/method_static/main.canyon diff --git a/test/end_to_end_tests/tests/success/semantics/method_static/main.canyon b/test/end_to_end_tests/tests/success/semantics/method_static/main.canyon new file mode 100644 index 0000000..d8907de --- /dev/null +++ b/test/end_to_end_tests/tests/success/semantics/method_static/main.canyon @@ -0,0 +1,11 @@ +fun main() { + let result = Foo::bar(1); +} + +struct Foo {} + +impl Foo { + fun bar(x: i32): i32 { + x + 1 + } +} \ No newline at end of file From ac252c9c49faa2c3477a96c318c6b96a8e1c78ee Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 02:49:13 -0600 Subject: [PATCH 20/32] feat: static methods --- src/ast.cpp | 41 ++++++++++++++ src/ast.h | 14 +++++ src/ccodeadapter.cpp | 44 +++++++++++---- src/ccodeadapter.h | 1 + src/ccodegenerator.cpp | 4 ++ src/ccodegenerator.h | 1 + src/parser.cpp | 39 +++++++++++++- src/parser.h | 1 + src/semanticanalyzer.cpp | 54 ++++++++++++++++--- src/semanticanalyzer.h | 1 + .../expected/stderr | 2 +- .../expected/stderr | 2 +- .../semantics/method_static/main.canyon | 6 +-- 13 files changed, 187 insertions(+), 23 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index af45684..a6bda2c 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -266,6 +266,22 @@ IfElseExpression::IfElseExpression(std::unique_ptr condition, thenBlock(std::move(thenBlock)), elseExpression(std::move(elseExpression)) { } +PathExpression::PathExpression(std::vector> path) + : Expression(Slice::merge(path[0]->getSlice(), path.back()->getSlice())), + path(std::move(path)) { +} + +void PathExpression::forEachSymbol( + const std::function &symbolHandler) { + for (auto &symbol : path) { + symbolHandler(*symbol); + } +} + +void PathExpression::accept(ASTVisitor &visitor) { + visitor.visit(*this); +} + WhileExpression::WhileExpression(const Keyword &whileKeyword, std::unique_ptr condition, std::unique_ptr body) : Expression(Slice::merge(whileKeyword.s, body->getSlice())), @@ -430,6 +446,13 @@ void Impl::forEachMethod( } } +Function *Impl::getMethod(std::string_view name) { + if (methods.find(name) == methods.end()) { + return nullptr; + } + return methods[name].get(); +} + void Impl::accept(ASTVisitor &visitor) { visitor.visit(*this); } @@ -597,6 +620,13 @@ Function *Module::getFunction(std::string_view name) { return std::get<0>(functions[name]).get(); } +Impl *Module::getImpl(std::string_view name) { + if (impls.find(name) == impls.end()) { + return nullptr; + } + return std::get<0>(impls[name]).get(); +} + std::filesystem::path Module::getSource() { return source; } @@ -677,6 +707,17 @@ void ASTPrinter::visit(ParenthesizedExpression &node) { node.getExpression().accept(*this); } +void ASTPrinter::visit(PathExpression &node) { + bool first = true; + node.forEachSymbol([this, &first](SymbolExpression &symbol) { + symbol.accept(*this); + if (!first) { + std::cerr << "::"; + } + first = false; + }); +} + void ASTPrinter::visit(IfElseExpression &node) { std::cerr << "if "; node.getCondition().accept(*this); diff --git a/src/ast.h b/src/ast.h index 7e52a83..87b7da4 100644 --- a/src/ast.h +++ b/src/ast.h @@ -179,6 +179,16 @@ class ParenthesizedExpression : public Expression { virtual ~ParenthesizedExpression() = default; }; +class PathExpression : public Expression { +private: + std::vector> path; +public: + PathExpression(std::vector> path); + void forEachSymbol(const std::function &symbolHandler); + void accept(ASTVisitor &visitor) override; + virtual ~PathExpression() = default; +}; + class IfElseExpression : public Expression { private: std::unique_ptr condition; @@ -292,6 +302,7 @@ class Impl : public ASTComponent { Impl(std::unordered_map> methods); void forEachMethod( const std::function &methodHandler); + Function *getMethod(std::string_view name); void accept(ASTVisitor &visitor); ~Impl() = default; }; @@ -343,6 +354,7 @@ class Module : public ASTComponent { int resultType); int getBinaryOperator(Operator::Type op, int leftType, int rightType); Function *getFunction(std::string_view name); + Impl *getImpl(std::string_view name); std::filesystem::path getSource(); void accept(ASTVisitor &visitor); ~Module() = default; @@ -360,6 +372,7 @@ class ASTVisitor { virtual void visit(BlockExpression &node) = 0; virtual void visit(ReturnExpression &node) = 0; virtual void visit(ParenthesizedExpression &node) = 0; + virtual void visit(PathExpression &node) = 0; virtual void visit(IfElseExpression &node) = 0; virtual void visit(WhileExpression &node) = 0; virtual void visit(ExpressionStatement &node) = 0; @@ -384,6 +397,7 @@ class ASTPrinter : public ASTVisitor { void visit(BlockExpression &node) override; void visit(ReturnExpression &node) override; void visit(ParenthesizedExpression &node) override; + void visit(PathExpression &node) override; void visit(IfElseExpression &node) override; void visit(WhileExpression &node) override; void visit(ExpressionStatement &node) override; diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index f2e8286..558a5bf 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -21,17 +21,35 @@ std::unique_ptr CCodeAdapter::transform() { void CCodeAdapter::visit(FunctionCallExpression &node) { Expression &oldFunction = node.getFunction(); auto *oldSymbol = dynamic_cast(&oldFunction); - if (oldSymbol == nullptr) { - std::cerr << "Function call target is not a symbol" << std::endl; + auto *oldPath = dynamic_cast(&oldFunction); + if (oldSymbol == nullptr && oldPath == nullptr) { + std::cerr << "Function call target is not a symbol or path" << std::endl; exit(EXIT_FAILURE); } - generatedStrings->push_back( - "CANYON_FUNCTION_" + std::string(oldSymbol->getSymbol().s.contents)); - std::string_view newName = generatedStrings->back(); - std::unique_ptr newSymbol - = std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)); - std::unique_ptr newSymbolExpression - = std::make_unique(std::move(newSymbol)); + std::unique_ptr newSymbolExpression = nullptr; + if (oldSymbol != nullptr) { + generatedStrings->push_back( + "CANYON_FUNCTION_" + std::string(oldSymbol->getSymbol().s.contents)); + std::string_view newName = generatedStrings->back(); + std::unique_ptr newSymbol + = std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)); + newSymbolExpression = std::make_unique(std::move(newSymbol)); + } else { + std::string functionName; + bool first = true; + oldPath->forEachSymbol([&functionName, &first](SymbolExpression &symbol) { + functionName += symbol.getSymbol().s.contents; + if (first) { + functionName += "_METHOD_"; + } + first = false; + }); + generatedStrings->push_back("CANYON_IMPL_" + functionName); + std::string_view newName = generatedStrings->back(); + std::unique_ptr newSymbol + = std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)); + newSymbolExpression = std::make_unique(std::move(newSymbol)); + } std::vector> newArguments; node.forEachArgument([this, &newArguments](Expression &argument) { @@ -221,6 +239,10 @@ void CCodeAdapter::visit(ParenthesizedExpression &node) { returnValue = std::move(newParenthesizedExpression); } +void CCodeAdapter::visit([[maybe_unused]] PathExpression &node) { + // TODO +} + void CCodeAdapter::visit(IfElseExpression &node) { if (node.getTypeID() != inputModule->getType("()").id && node.getTypeID() != inputModule->getType("!").id) { @@ -462,11 +484,13 @@ void CCodeAdapter::visit(Class &node) { void CCodeAdapter::visit(Impl &node) { std::unordered_map> newMethods; node.forEachMethod([this, &newMethods](std::string_view name, Function &method) { + generatedStrings->push_back(std::string(name)); + std::string_view newName = generatedStrings->back(); method.accept(*this); std::unique_ptr newMethod = std::unique_ptr( dynamic_cast(returnValue.release())); newMethod->setTypeID(method.getTypeID()); - newMethods.emplace(name, std::move(newMethod)); + newMethods.emplace(newName, std::move(newMethod)); }); returnValue = std::make_unique(std::move(newMethods)); } diff --git a/src/ccodeadapter.h b/src/ccodeadapter.h index 83ef761..b015ea8 100644 --- a/src/ccodeadapter.h +++ b/src/ccodeadapter.h @@ -36,6 +36,7 @@ class CCodeAdapter : ASTVisitor { void visit(BlockExpression &node) override; void visit(ReturnExpression &node) override; void visit(ParenthesizedExpression &node) override; + void visit(PathExpression &node) override; void visit(IfElseExpression &node) override; void visit(WhileExpression &node) override; void visit(ExpressionStatement &node) override; diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index e81b764..78e5e11 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -168,6 +168,10 @@ void CCodeGenerator::visit(ParenthesizedExpression &node) { node.getExpression().accept(*this); } +void CCodeGenerator::visit([[maybe_unused]] PathExpression &node) { + // TODO +} + void CCodeGenerator::visit(IfElseExpression &node) { *os << "if ("; node.getCondition().accept(*this); diff --git a/src/ccodegenerator.h b/src/ccodegenerator.h index 57abaab..05e72c1 100644 --- a/src/ccodegenerator.h +++ b/src/ccodegenerator.h @@ -32,6 +32,7 @@ class CCodeGenerator : public ASTVisitor { void visit(BlockExpression &node) override; void visit(ReturnExpression &node) override; void visit(ParenthesizedExpression &node) override; + void visit(PathExpression &node) override; void visit(IfElseExpression &node) override; void visit(WhileExpression &node) override; void visit(ExpressionStatement &node) override; diff --git a/src/parser.cpp b/src/parser.cpp index 5f8de6b..746ce24 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -809,7 +809,7 @@ std::unique_ptr Parser::parseUnaryExpression() { } std::unique_ptr Parser::parseFunctionCallExpression() { - std::unique_ptr expr = parsePrimaryExpression(); + std::unique_ptr expr = parsePathExpression(); if (expr == nullptr) { return nullptr; } @@ -845,6 +845,43 @@ std::unique_ptr Parser::parseFunctionCallExpression() { return expr; } +std::unique_ptr Parser::parsePathExpression() { + std::unique_ptr expr = parsePrimaryExpression(); + if (expr == nullptr) { + return nullptr; + } + if (!dynamic_cast(expr.get())) { + return expr; + } + std::unique_ptr symExpr( + dynamic_cast(expr.release())); + std::vector> symbols; + symbols.push_back(std::move(symExpr)); + while (true) { + auto *op = dynamic_cast(tokens[i].get()); + if (op != nullptr && op->type == Operator::Type::Scope) { + op = dynamic_cast(tokens[i++].release()); + std::unique_ptr expr2 = parsePrimaryExpression(); + if (expr2 == nullptr) { + return nullptr; + } + if (!dynamic_cast(expr2.get())) { + errorHandler->error(*tokens[i], "Expected symbol"); + return nullptr; + } + symExpr = std::unique_ptr( + dynamic_cast(expr2.release())); + symbols.push_back(std::move(symExpr)); + } else { + if (symbols.size() == 1) { + return std::move(symbols[0]); + } else { + return std::make_unique(std::move(symbols)); + } + } + } +} + std::unique_ptr Parser::parsePrimaryExpression() { auto *integerLiteral = dynamic_cast(tokens[i].get()); if (integerLiteral != nullptr) { diff --git a/src/parser.h b/src/parser.h index 29740e1..5084f0a 100644 --- a/src/parser.h +++ b/src/parser.h @@ -47,6 +47,7 @@ class Parser { std::unique_ptr parseMultiplicativeExpression(); std::unique_ptr parseUnaryExpression(); std::unique_ptr parseFunctionCallExpression(); + std::unique_ptr parsePathExpression(); std::unique_ptr parsePrimaryExpression(); void synchronize(); bool isAtEnd() const; diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index 6fd11f5..ff8595a 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -37,16 +37,52 @@ void SemanticAnalyzer::analyze() { void SemanticAnalyzer::visit(FunctionCallExpression &node) { Expression &functionCall = node.getFunction(); auto *symbol = dynamic_cast(&functionCall); - if (symbol == nullptr) { + auto *path = dynamic_cast(&functionCall); + if (symbol == nullptr && path == nullptr) { errorHandler->error(functionCall.getSlice(), - "Function call target is not a symbol"); + "Function call target is not a symbol or path"); return; } - Function *function = module->getFunction(symbol->getSymbol().s.contents); - if (function == nullptr) { - errorHandler->error(symbol->getSymbol().s, - "Function " + std::string(symbol->getSymbol().s.contents) + " not found"); - return; + Function *function = nullptr; + if (symbol != nullptr) { + function = module->getFunction(symbol->getSymbol().s.contents); + if (function == nullptr) { + errorHandler->error(symbol->getSymbol().s, + "Function " + std::string(symbol->getSymbol().s.contents) + + " not found"); + return; + } + } else { + Impl *impl = nullptr; + bool pathLookupGood = true; + size_t i = 0; + path->forEachSymbol([this, &impl, &function, &pathLookupGood, &i](SymbolExpression &symbol) { + if (impl == nullptr && pathLookupGood) { + impl = module->getImpl(symbol.getSymbol().s.contents); + if (impl == nullptr) { + errorHandler->error(symbol.getSymbol().s, + "Impl " + std::string(symbol.getSymbol().s.contents) + " not found"); + pathLookupGood = false; + return; + } + i++; + } else if (pathLookupGood) { + function = impl->getMethod(symbol.getSymbol().s.contents); + if (function == nullptr) { + errorHandler->error(symbol.getSymbol().s, + "Method " + std::string(symbol.getSymbol().s.contents) + " not found"); + pathLookupGood = false; + return; + } + i++; + } else if (i >= 2) { + errorHandler->error(symbol.getSymbol().s, "Path too long"); + pathLookupGood = false; + } + }); + if (!pathLookupGood) { + return; + } } std::vector> parameters; function->forEachParameter( @@ -225,6 +261,10 @@ void SemanticAnalyzer::visit(ParenthesizedExpression &node) { node.setTypeID(expr.getTypeID()); } +void SemanticAnalyzer::visit([[maybe_unused]] PathExpression &node) { + // TODO +} + void SemanticAnalyzer::visit(IfElseExpression &node) { Expression &condition = node.getCondition(); condition.accept(*this); diff --git a/src/semanticanalyzer.h b/src/semanticanalyzer.h index d03b322..d9f3b43 100644 --- a/src/semanticanalyzer.h +++ b/src/semanticanalyzer.h @@ -33,6 +33,7 @@ class SemanticAnalyzer : public ASTVisitor { void visit(BlockExpression &node) override; void visit(ReturnExpression &node) override; void visit(ParenthesizedExpression &node) override; + void visit(PathExpression &node) override; void visit(IfElseExpression &node) override; void visit(WhileExpression &node) override; void visit(ExpressionStatement &node) override; diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_expression_function_call/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_expression_function_call/expected/stderr index a38245a..5b4cd4f 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_expression_function_call/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_expression_function_call/expected/stderr @@ -1 +1 @@ -Error at {main}:2:5: Function call target is not a symbol +Error at {main}:2:5: Function call target is not a symbol or path diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_integer_function_call/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_integer_function_call/expected/stderr index a38245a..5b4cd4f 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_integer_function_call/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_integer_function_call/expected/stderr @@ -1 +1 @@ -Error at {main}:2:5: Function call target is not a symbol +Error at {main}:2:5: Function call target is not a symbol or path diff --git a/test/end_to_end_tests/tests/success/semantics/method_static/main.canyon b/test/end_to_end_tests/tests/success/semantics/method_static/main.canyon index d8907de..6b89a41 100644 --- a/test/end_to_end_tests/tests/success/semantics/method_static/main.canyon +++ b/test/end_to_end_tests/tests/success/semantics/method_static/main.canyon @@ -1,11 +1,11 @@ fun main() { - let result = Foo::bar(1); + let result: i32 = Foo::bar(1); } -struct Foo {} +class Foo {} impl Foo { fun bar(x: i32): i32 { - x + 1 + x } } \ No newline at end of file From fc035c49c9888bc52501819efff320f7cfe9d0e0 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:00:34 -0600 Subject: [PATCH 21/32] test: constructor --- .../tests/success/semantics/constructor/main.canyon | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/end_to_end_tests/tests/success/semantics/constructor/main.canyon diff --git a/test/end_to_end_tests/tests/success/semantics/constructor/main.canyon b/test/end_to_end_tests/tests/success/semantics/constructor/main.canyon new file mode 100644 index 0000000..44b3bf4 --- /dev/null +++ b/test/end_to_end_tests/tests/success/semantics/constructor/main.canyon @@ -0,0 +1,9 @@ +fun main() { + let foo: Foo = Foo::new(); +} + +class Foo {} + +impl Foo { + constructor new(self: Foo) {} +} \ No newline at end of file From fa435c96940b680533c0c4f54534b92caa5229b2 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 04:07:36 -0600 Subject: [PATCH 22/32] feat: constructors --- src/ast.cpp | 26 ++++++++++-- src/ast.h | 13 ++++-- src/ccodeadapter.cpp | 57 +++++++++++++++++++------- src/ccodegenerator.cpp | 28 ++++++++++--- src/parser.cpp | 31 +++++++++++--- src/parser.h | 3 +- src/semanticanalyzer.cpp | 87 +++++++++++++++++++++++++--------------- 7 files changed, 179 insertions(+), 66 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index a6bda2c..8d9725a 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -45,6 +45,10 @@ Expression &FunctionCallExpression::getFunction() { return *function; } +void FunctionCallExpression::addFirstArgument(std::unique_ptr argument) { + arguments.insert(arguments.begin(), std::move(argument)); +} + void FunctionCallExpression::forEachArgument( const std::function &argumentHandler) { for (auto &argument : arguments) { @@ -52,6 +56,14 @@ void FunctionCallExpression::forEachArgument( } } +void FunctionCallExpression::setIsConstructor(bool isConstructor) { + this->isConstructor = isConstructor; +} + +bool FunctionCallExpression::getIsConstructor() const { + return isConstructor; +} + void FunctionCallExpression::accept(ASTVisitor &visitor) { visitor.visit(*this); } @@ -385,16 +397,18 @@ void LetStatement::accept(ASTVisitor &visitor) { Function::Function( std::vector, std::unique_ptr>> parameters, - std::unique_ptr returnTypeAnnotation, std::unique_ptr body) + std::unique_ptr returnTypeAnnotation, std::unique_ptr body, + bool isConstructor) : parameters(std::move(parameters)), - returnTypeAnnotation(std::move(returnTypeAnnotation)), body(std::move(body)) { + returnTypeAnnotation(std::move(returnTypeAnnotation)), body(std::move(body)), + isConstructor(isConstructor) { } Function::Function( std::vector, std::unique_ptr>> parameters, - std::unique_ptr body) + std::unique_ptr body, bool isConstructor) : parameters(std::move(parameters)), returnTypeAnnotation(nullptr), - body(std::move(body)) { + body(std::move(body)), isConstructor(isConstructor) { } void Function::forEachParameter( @@ -420,6 +434,10 @@ void Function::setTypeID(int typeID) { this->typeID = typeID; } +bool Function::getIsConstructor() const { + return isConstructor; +} + void Function::accept(ASTVisitor &visitor) { visitor.visit(*this); } diff --git a/src/ast.h b/src/ast.h index 87b7da4..72b965e 100644 --- a/src/ast.h +++ b/src/ast.h @@ -46,13 +46,17 @@ class Statement : public ASTComponent { class FunctionCallExpression : public Expression { std::unique_ptr function; std::vector> arguments; + bool isConstructor = false; public: FunctionCallExpression(std::unique_ptr function, const Punctuation &open, std::vector> arguments, const Punctuation &close); FunctionCallExpression(std::unique_ptr function, std::vector> arguments); Expression &getFunction(); + void addFirstArgument(std::unique_ptr argument); void forEachArgument(const std::function &argumentHandler); + void setIsConstructor(bool isConstructor); + bool getIsConstructor() const; void accept(ASTVisitor &visitor) override; virtual ~FunctionCallExpression() = default; }; @@ -264,21 +268,23 @@ class Function : public ASTComponent { std::vector, std::unique_ptr>> parameters; std::unique_ptr returnTypeAnnotation; std::unique_ptr body; + bool isConstructor; int typeID = -1; public: Function(std::vector, std::unique_ptr>> parameters, std::unique_ptr returnTypeAnnotation, - std::unique_ptr body); + std::unique_ptr body, bool isConstructor); Function(std::vector, std::unique_ptr>> parameters, - std::unique_ptr body); + std::unique_ptr body, bool isConstructor); void forEachParameter( const std::function ¶meterHandler); Symbol *getReturnTypeAnnotation(); BlockExpression &getBody(); int getTypeID() const; void setTypeID(int typeID); + bool getIsConstructor() const; void accept(ASTVisitor &visitor); ~Function() = default; }; @@ -342,7 +348,8 @@ class Module : public ASTComponent { bool isBuiltin = false); void forEachClass( const std::function &classHandler); - void forEachImpl(const std::function &implHandler); + void forEachImpl( + const std::function &implHandler); Type getType(std::string_view typeName); Type getType(int id); void insertType(std::string_view typeName); diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index 558a5bf..1cc65ec 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -72,7 +72,8 @@ void CCodeAdapter::visit(FunctionCallExpression &node) { std::unique_ptr newFunctionCall = std::make_unique(std::move(newSymbolExpression), std::move(newArguments)); - newFunctionCall->setTypeID(oldFunction.getTypeID()); + newFunctionCall->setTypeID(node.getTypeID()); + newFunctionCall->setIsConstructor(node.getIsConstructor()); returnValue = std::move(newFunctionCall); } @@ -442,15 +443,22 @@ void CCodeAdapter::visit(LetStatement &node) { void CCodeAdapter::visit(Function &node) { std::vector, std::unique_ptr>> newParameters; - node.forEachParameter([this, &newParameters](Symbol ¶meter, Symbol &type) { - generatedStrings->push_back( - "CANYON_PARAMETER_" + std::string(parameter.s.contents)); - std::string_view newParameterName = generatedStrings->back(); - std::unique_ptr newParameter = std::make_unique( - Slice(newParameterName, inputModule->getSource(), 0, 0)); - std::unique_ptr newType = std::make_unique(type); - newParameters.emplace_back(std::move(newParameter), std::move(newType)); - }); + size_t i = 0; + int typeId = -1; + node.forEachParameter( + [this, &newParameters, &i, &node, &typeId](Symbol ¶meter, Symbol &type) { + generatedStrings->push_back( + "CANYON_PARAMETER_" + std::string(parameter.s.contents)); + std::string_view newParameterName = generatedStrings->back(); + std::unique_ptr newParameter = std::make_unique( + Slice(newParameterName, inputModule->getSource(), 0, 0)); + std::unique_ptr newType = std::make_unique(type); + newParameters.emplace_back(std::move(newParameter), std::move(newType)); + if (node.getIsConstructor() && i == 0) { + typeId = node.getBody().getSymbolType(parameter.s.contents); + } + i++; + }); Expression &oldBody = node.getBody(); std::unique_ptr enclosingScope = std::make_unique(); @@ -462,8 +470,25 @@ void CCodeAdapter::visit(Function &node) { std::unique_ptr bodyStatement = std::make_unique(std::move(newBody)); enclosingScope->pushStatement(std::move(bodyStatement)); - returnValue = std::make_unique(std::move(newParameters), - std::move(enclosingScope)); + + if (node.getIsConstructor()) { + std::unique_ptr selfParameter = std::make_unique( + Slice("CANYON_PARAMETER_self", inputModule->getSource(), 0, 0)); + std::unique_ptr selfSymbolExpression + = std::make_unique(std::move(selfParameter)); + std::unique_ptr returnExpression + = std::make_unique(std::move(selfSymbolExpression)); + std::unique_ptr returnStatement + = std::make_unique(std::move(returnExpression)); + enclosingScope->pushStatement(std::move(returnStatement)); + } + + std::unique_ptr newFunction = std::make_unique( + std::move(newParameters), std::move(enclosingScope), node.getIsConstructor()); + if (node.getIsConstructor()) { + newFunction->setTypeID(typeId); + } + returnValue = std::move(newFunction); } void CCodeAdapter::visit(Class &node) { @@ -489,7 +514,9 @@ void CCodeAdapter::visit(Impl &node) { method.accept(*this); std::unique_ptr newMethod = std::unique_ptr( dynamic_cast(returnValue.release())); - newMethod->setTypeID(method.getTypeID()); + if (!method.getIsConstructor()) { + newMethod->setTypeID(method.getTypeID()); + } newMethods.emplace(newName, std::move(newMethod)); }); returnValue = std::make_unique(std::move(newMethods)); @@ -503,7 +530,9 @@ void CCodeAdapter::visit(Module &node) { oldFunction.accept(*this); std::unique_ptr newFunction = std::unique_ptr( dynamic_cast(returnValue.release())); - newFunction->setTypeID(oldFunction.getTypeID()); + if (!oldFunction.getIsConstructor()) { + newFunction->setTypeID(oldFunction.getTypeID()); + } outputModule->addFunction( std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)), std::move(newFunction), isBuiltin); diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index 78e5e11..77730c8 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -23,6 +23,14 @@ CCodeGenerator::CCodeGenerator(Module *module, std::ostream *os) cTypes[this->module->getType("u64").id] = "uint64_t"; cTypes[this->module->getType("bool").id] = "bool"; cTypes[this->module->getType("char").id] = "char"; + + module->forEachClass( + [this, &module](std::string_view name, Class & /*unused*/, bool isBuiltin) { + if (!isBuiltin) { + int typeID = module->getType(std::string(name)).id; + cTypes[typeID] = "struct CANYON_CLASS_" + std::string(name) + " *"; + } + }); } void CCodeGenerator::generate() { @@ -37,6 +45,7 @@ void CCodeGenerator::generateIncludes() { "#include \n" "#include \n" "#include \n" + "#include \n" "\n"; } @@ -44,7 +53,14 @@ void CCodeGenerator::visit(FunctionCallExpression &node) { node.getFunction().accept(*this); *os << '('; bool first = true; - node.forEachArgument([this, &first](Expression &argument) { + if (node.getIsConstructor()) { + std::string_view type = cTypes[node.getTypeID()]; + // need to convert the pointer type in the map to the struct itself + type = type.substr(0, type.size() - 2); + *os << "malloc(sizeof(" << type << "))"; + first = false; + } + node.forEachArgument([this, &first, &node](Expression &argument) { if (!first) { *os << ", "; } @@ -153,9 +169,9 @@ void CCodeGenerator::visit(BlockExpression &node) { } void CCodeGenerator::visit(ReturnExpression &node) { - // Invariant: the return expression will only ever be in an ExpressionStatement (not - // nested inside another expression) Otherwise it would trigger unreachable code error - // before reaching the codegen phase + // Invariant: the return expression will only ever be in an ExpressionStatement + // (not nested inside another expression) Otherwise it would trigger unreachable + // code error before reaching the codegen phase *os << "return"; Expression *expression = node.getExpression(); if (expression != nullptr) { @@ -298,7 +314,7 @@ void CCodeGenerator::visit(Module &node) { // Class structs node.forEachClass([this](std::string_view name, Class &cls, bool /*unused*/) { - *os << "typedef struct {\n"; + *os << "struct " << name << " {\n"; tabLevel++; cls.forEachFieldDeclaration([this](LetStatement &declaration) { *os << std::string(tabLevel, '\t'); @@ -306,7 +322,7 @@ void CCodeGenerator::visit(Module &node) { *os << cType << ' ' << declaration.getSymbol().s << ";\n"; }); tabLevel--; - *os << "} *" << name << ";\n"; + *os << "};\n"; }); *os << '\n'; diff --git a/src/parser.cpp b/src/parser.cpp index 746ce24..14371be 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -110,16 +110,28 @@ std::unique_ptr Parser::parse() { return mod; } -std::pair, std::unique_ptr> Parser::parseFunction() { +std::pair, std::unique_ptr> Parser::parseFunction( + bool isConstructor) { auto *keyword = dynamic_cast(tokens[i].get()); - if (keyword == nullptr || keyword->type != Keyword::Type::FUN) { - errorHandler->error(*tokens[i], "Expected keyword `fun`"); - return {nullptr, nullptr}; + if (!isConstructor) { + if (keyword == nullptr || keyword->type != Keyword::Type::FUN) { + errorHandler->error(*tokens[i], "Expected keyword `fun`"); + return {nullptr, nullptr}; + } + } else { + if (keyword == nullptr || keyword->type != Keyword::Type::CONSTRUCTOR) { + errorHandler->error(*tokens[i], "Expected keyword `constructor`"); + return {nullptr, nullptr}; + } } i++; auto *symbol = dynamic_cast(tokens[i].get()); if (symbol == nullptr) { - errorHandler->error(*tokens[i], "Expected symbol following `fun`"); + if (!isConstructor) { + errorHandler->error(*tokens[i], "Expected symbol following `fun`"); + } else { + errorHandler->error(*tokens[i], "Expected symbol following `constructor`"); + } return {nullptr, nullptr}; } symbol = dynamic_cast(tokens[i++].release()); @@ -195,7 +207,7 @@ std::pair, std::unique_ptr> Parser::parseFunct return {std::unique_ptr(symbol), std::make_unique(std::move(parameters), std::unique_ptr(type), - std::move(block))}; + std::move(block), isConstructor)}; } std::pair, std::unique_ptr> Parser::parseClass() { @@ -279,6 +291,13 @@ std::pair, std::unique_ptr> Parser::parseImpl() { return {nullptr, nullptr}; } methods.emplace(method.first->s.contents, std::move(method.second)); + } else if (keyword2 != nullptr && keyword2->type == Keyword::Type::CONSTRUCTOR) { + std::pair, std::unique_ptr> method + = parseFunction(true); + if (method.second == nullptr) { + return {nullptr, nullptr}; + } + methods.emplace(method.first->s.contents, std::move(method.second)); } else if (keyword2 != nullptr) { errorHandler->error(*tokens[i], "Unexpected keyword in impl block"); mustSynchronize = true; diff --git a/src/parser.h b/src/parser.h index 5084f0a..7197e2d 100644 --- a/src/parser.h +++ b/src/parser.h @@ -25,7 +25,8 @@ class Parser { std::unique_ptr parse(); ~Parser() = default; private: - std::pair, std::unique_ptr> parseFunction(); + std::pair, std::unique_ptr> parseFunction( + bool isConstructor = false); std::pair, std::unique_ptr> parseClass(); std::pair, std::unique_ptr> parseImpl(); std::unique_ptr parseStatement(); diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index ff8595a..b990625 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -56,30 +56,33 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { Impl *impl = nullptr; bool pathLookupGood = true; size_t i = 0; - path->forEachSymbol([this, &impl, &function, &pathLookupGood, &i](SymbolExpression &symbol) { - if (impl == nullptr && pathLookupGood) { - impl = module->getImpl(symbol.getSymbol().s.contents); - if (impl == nullptr) { - errorHandler->error(symbol.getSymbol().s, - "Impl " + std::string(symbol.getSymbol().s.contents) + " not found"); - pathLookupGood = false; - return; - } - i++; - } else if (pathLookupGood) { - function = impl->getMethod(symbol.getSymbol().s.contents); - if (function == nullptr) { - errorHandler->error(symbol.getSymbol().s, - "Method " + std::string(symbol.getSymbol().s.contents) + " not found"); - pathLookupGood = false; - return; - } - i++; - } else if (i >= 2) { - errorHandler->error(symbol.getSymbol().s, "Path too long"); - pathLookupGood = false; - } - }); + path->forEachSymbol( + [this, &impl, &function, &pathLookupGood, &i](SymbolExpression &symbol) { + if (impl == nullptr && pathLookupGood) { + impl = module->getImpl(symbol.getSymbol().s.contents); + if (impl == nullptr) { + errorHandler->error(symbol.getSymbol().s, + "Impl " + std::string(symbol.getSymbol().s.contents) + + " not found"); + pathLookupGood = false; + return; + } + i++; + } else if (pathLookupGood) { + function = impl->getMethod(symbol.getSymbol().s.contents); + if (function == nullptr) { + errorHandler->error(symbol.getSymbol().s, + "Method " + std::string(symbol.getSymbol().s.contents) + + " not found"); + pathLookupGood = false; + return; + } + i++; + } else if (i >= 2) { + errorHandler->error(symbol.getSymbol().s, "Path too long"); + pathLookupGood = false; + } + }); if (!pathLookupGood) { return; } @@ -95,6 +98,10 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { size_t i = 0; bool unreachableArgument = false; bool unreachableHandled = false; + if (function->getIsConstructor()) { + // Implicit self argument + i++; + } node.forEachArgument([this, ¶meters, &i, &unreachableArgument, &unreachableHandled](Expression &argument) { if (i == parameters.size()) { @@ -136,7 +143,12 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { return; } - node.setTypeID(function->getTypeID()); + node.setIsConstructor(function->getIsConstructor()); + if (function->getIsConstructor()) { + node.setTypeID(parameters[0].second); + } else { + node.setTypeID(function->getTypeID()); + } } void SemanticAnalyzer::visit(BinaryExpression &node) { @@ -400,7 +412,7 @@ void SemanticAnalyzer::visit(Class &node) { }); } -void SemanticAnalyzer::visit([[maybe_unused]] Impl &node) { +void SemanticAnalyzer::visit(Impl &node) { node.forEachMethod([this](std::string_view /*unused*/, Function &method) { method.accept(*this); }); @@ -409,6 +421,9 @@ void SemanticAnalyzer::visit([[maybe_unused]] Impl &node) { void SemanticAnalyzer::visit(Module &node) { addDefaultOperators(&node); addRuntimeFunctions(&node, builtinApiJsonFile); + node.forEachClass([this](std::string_view name, Class & /*unused*/, bool /*unused*/) { + module->insertType(name); + }); node.forEachFunction([this]([[maybe_unused]] std::string_view name, Function &function, bool /*unused*/) { @@ -429,14 +444,22 @@ void SemanticAnalyzer::visit(Module &node) { function.setTypeID(module->getType(type->s.contents).id); } }); - node.forEachImpl([this](std::string_view /*unused*/, Impl &impl, bool /*unused*/) { - impl.forEachMethod([this](std::string_view /*unused*/, Function &method) { - method.forEachParameter([this, &method](Symbol ¶meter, Symbol &type) { + node.forEachImpl([this](std::string_view implName, Impl &impl, bool /*unused*/) { + impl.forEachMethod([this, &implName](std::string_view /*unused*/, Function &method) { + bool first = true; + method.forEachParameter([this, &method, &first, &implName](Symbol ¶meter, Symbol &type) { int typeID = module->getType(type.s.contents).id; if (typeID == -1) { errorHandler->error(type.s, "Unknown type"); return; } + if (first && method.getIsConstructor() && parameter.s.contents != "self") { + errorHandler->error(parameter.s, "First parameter of constructor must be self"); + } + if (first && method.getIsConstructor() && type.s.contents != implName) { + errorHandler->error(type.s, "Constructor self parameter type must be the same as impl name"); + } + first = false; method.getBody().pushSymbol(parameter.s.contents, typeID, SymbolSource::FunctionParameter); }); @@ -601,9 +624,9 @@ static void addRuntimeFunctions(Module *module, std::istream &builtinApiJsonFile std::string_view returnType = module->ownedStrings.back(); std::unique_ptr returnTypeAnnotation = std::make_unique(Slice(returnType, "", 0, 0)); - std::unique_ptr builtin - = std::make_unique(std::move(parameters), - std::move(returnTypeAnnotation), std::make_unique()); + std::unique_ptr builtin = std::make_unique( + std::move(parameters), std::move(returnTypeAnnotation), + std::make_unique(), false); module->addFunction(std::make_unique(Slice(functionName, "", 0, 0)), std::move(builtin), true); } From db215eb005dbc875d971a4ed6a5aadb1d87a1048 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 04:12:00 -0600 Subject: [PATCH 23/32] test: object field access --- .../{constructor => class_constructor}/main.canyon | 0 .../semantics/class_field_access/main.canyon | 14 ++++++++++++++ 2 files changed, 14 insertions(+) rename test/end_to_end_tests/tests/success/semantics/{constructor => class_constructor}/main.canyon (100%) create mode 100644 test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon diff --git a/test/end_to_end_tests/tests/success/semantics/constructor/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_constructor/main.canyon similarity index 100% rename from test/end_to_end_tests/tests/success/semantics/constructor/main.canyon rename to test/end_to_end_tests/tests/success/semantics/class_constructor/main.canyon diff --git a/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon new file mode 100644 index 0000000..927f8d3 --- /dev/null +++ b/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon @@ -0,0 +1,14 @@ +fun main() { + let foo: Foo = Foo::new(); + let result: i32 = foo.x; +} + +class Foo { + let x: i32 = 0; +} + +impl Foo { + constructor new(self: Foo) { + self.x = 1; + } +} \ No newline at end of file From a15c6cee59ef9808b85bca6cdbb97291bb126bfd Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 04:29:28 -0600 Subject: [PATCH 24/32] feat: object field access --- src/ast.cpp | 24 ++++++++++++++ src/ast.h | 15 +++++++++ src/ccodeadapter.cpp | 17 ++++++++++ src/ccodeadapter.h | 1 + src/ccodegenerator.cpp | 6 ++++ src/ccodegenerator.h | 1 + src/parser.cpp | 31 ++++++++++++++++++- src/parser.h | 1 + src/semanticanalyzer.cpp | 24 +++++++++----- src/semanticanalyzer.h | 1 + .../expected/stderr | 2 +- .../expected/stderr | 2 +- .../assignment_to_integer/expected/stderr | 2 +- .../expected/stderr | 2 +- .../expected/stderr | 2 +- 15 files changed, 118 insertions(+), 13 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 8d9725a..2b149b6 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -294,6 +294,24 @@ void PathExpression::accept(ASTVisitor &visitor) { visitor.visit(*this); } +FieldAccessExpression::FieldAccessExpression(std::unique_ptr object, + std::unique_ptr field) + : Expression(Slice::merge(object->getSlice(), field->getSlice())), + object(std::move(object)), field(std::move(field)) { +} + +Expression &FieldAccessExpression::getObject() { + return *object; +} + +SymbolExpression &FieldAccessExpression::getField() { + return *field; +} + +void FieldAccessExpression::accept(ASTVisitor &visitor) { + visitor.visit(*this); +} + WhileExpression::WhileExpression(const Keyword &whileKeyword, std::unique_ptr condition, std::unique_ptr body) : Expression(Slice::merge(whileKeyword.s, body->getSlice())), @@ -736,6 +754,12 @@ void ASTPrinter::visit(PathExpression &node) { }); } +void ASTPrinter::visit(FieldAccessExpression &node) { + node.getObject().accept(*this); + std::cerr << '.'; + node.getField().accept(*this); +} + void ASTPrinter::visit(IfElseExpression &node) { std::cerr << "if "; node.getCondition().accept(*this); diff --git a/src/ast.h b/src/ast.h index 72b965e..4a9869c 100644 --- a/src/ast.h +++ b/src/ast.h @@ -193,6 +193,19 @@ class PathExpression : public Expression { virtual ~PathExpression() = default; }; +class FieldAccessExpression : public Expression { +private: + std::unique_ptr object; + std::unique_ptr field; +public: + FieldAccessExpression(std::unique_ptr object, + std::unique_ptr field); + Expression &getObject(); + SymbolExpression &getField(); + void accept(ASTVisitor &visitor) override; + virtual ~FieldAccessExpression() = default; +}; + class IfElseExpression : public Expression { private: std::unique_ptr condition; @@ -380,6 +393,7 @@ class ASTVisitor { virtual void visit(ReturnExpression &node) = 0; virtual void visit(ParenthesizedExpression &node) = 0; virtual void visit(PathExpression &node) = 0; + virtual void visit(FieldAccessExpression &node) = 0; virtual void visit(IfElseExpression &node) = 0; virtual void visit(WhileExpression &node) = 0; virtual void visit(ExpressionStatement &node) = 0; @@ -405,6 +419,7 @@ class ASTPrinter : public ASTVisitor { void visit(ReturnExpression &node) override; void visit(ParenthesizedExpression &node) override; void visit(PathExpression &node) override; + void visit(FieldAccessExpression &node) override; void visit(IfElseExpression &node) override; void visit(WhileExpression &node) override; void visit(ExpressionStatement &node) override; diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index 1cc65ec..9072a92 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -244,6 +244,23 @@ void CCodeAdapter::visit([[maybe_unused]] PathExpression &node) { // TODO } +void CCodeAdapter::visit(FieldAccessExpression &node) { + node.getObject().accept(*this); + std::unique_ptr newObject = std::unique_ptr( + dynamic_cast(returnValue.release())); + // don't want to visit the field because otherwise it will get mangled; instead just + // make a new one with the same name + generatedStrings->push_back(std::string(node.getField().getSymbol().s.contents)); + std::unique_ptr newField + = std::make_unique(std::make_unique( + Slice(generatedStrings->back(), inputModule->getSource(), 0, 0))); + std::unique_ptr newFieldAccessExpression + = std::make_unique(std::move(newObject), + std::move(newField)); + newFieldAccessExpression->setTypeID(node.getTypeID()); + returnValue = std::move(newFieldAccessExpression); +} + void CCodeAdapter::visit(IfElseExpression &node) { if (node.getTypeID() != inputModule->getType("()").id && node.getTypeID() != inputModule->getType("!").id) { diff --git a/src/ccodeadapter.h b/src/ccodeadapter.h index b015ea8..bae7068 100644 --- a/src/ccodeadapter.h +++ b/src/ccodeadapter.h @@ -37,6 +37,7 @@ class CCodeAdapter : ASTVisitor { void visit(ReturnExpression &node) override; void visit(ParenthesizedExpression &node) override; void visit(PathExpression &node) override; + void visit(FieldAccessExpression &node) override; void visit(IfElseExpression &node) override; void visit(WhileExpression &node) override; void visit(ExpressionStatement &node) override; diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index 77730c8..7a746d5 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -188,6 +188,12 @@ void CCodeGenerator::visit([[maybe_unused]] PathExpression &node) { // TODO } +void CCodeGenerator::visit(FieldAccessExpression &node) { + node.getObject().accept(*this); + *os << "->"; + node.getField().accept(*this); +} + void CCodeGenerator::visit(IfElseExpression &node) { *os << "if ("; node.getCondition().accept(*this); diff --git a/src/ccodegenerator.h b/src/ccodegenerator.h index 05e72c1..d085ac2 100644 --- a/src/ccodegenerator.h +++ b/src/ccodegenerator.h @@ -33,6 +33,7 @@ class CCodeGenerator : public ASTVisitor { void visit(ReturnExpression &node) override; void visit(ParenthesizedExpression &node) override; void visit(PathExpression &node) override; + void visit(FieldAccessExpression &node) override; void visit(IfElseExpression &node) override; void visit(WhileExpression &node) override; void visit(ExpressionStatement &node) override; diff --git a/src/parser.cpp b/src/parser.cpp index 14371be..120b8a8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -828,7 +828,7 @@ std::unique_ptr Parser::parseUnaryExpression() { } std::unique_ptr Parser::parseFunctionCallExpression() { - std::unique_ptr expr = parsePathExpression(); + std::unique_ptr expr = parseFieldAccessExpression(); if (expr == nullptr) { return nullptr; } @@ -864,6 +864,35 @@ std::unique_ptr Parser::parseFunctionCallExpression() { return expr; } +std::unique_ptr Parser::parseFieldAccessExpression() { + std::unique_ptr expr = parsePathExpression(); + if (expr == nullptr) { + return nullptr; + } + if (!dynamic_cast(expr.get())) { + return expr; + } + while (true) { + auto *punc = dynamic_cast(tokens[i].get()); + if (punc != nullptr && punc->type == Punctuation::Type::Period) { + i++; + std::unique_ptr expr2 = parsePathExpression(); + if (expr2 == nullptr) { + return nullptr; + } + if (!dynamic_cast(expr2.get())) { + errorHandler->error(*tokens[i], "Expected symbol"); + return nullptr; + } + expr = std::make_unique(std::move(expr), + std::unique_ptr( + dynamic_cast(expr2.release()))); + } else { + return expr; + } + } +} + std::unique_ptr Parser::parsePathExpression() { std::unique_ptr expr = parsePrimaryExpression(); if (expr == nullptr) { diff --git a/src/parser.h b/src/parser.h index 7197e2d..b89afc6 100644 --- a/src/parser.h +++ b/src/parser.h @@ -48,6 +48,7 @@ class Parser { std::unique_ptr parseMultiplicativeExpression(); std::unique_ptr parseUnaryExpression(); std::unique_ptr parseFunctionCallExpression(); + std::unique_ptr parseFieldAccessExpression(); std::unique_ptr parsePathExpression(); std::unique_ptr parsePrimaryExpression(); void synchronize(); diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index b990625..4542fb8 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -165,9 +165,10 @@ void SemanticAnalyzer::visit(BinaryExpression &node) { return; } if (node.getOperator().type == Operator::Type::Assignment - && (dynamic_cast(&left) == nullptr)) { + && (dynamic_cast(&left) == nullptr) + && (dynamic_cast(&left) == nullptr)) { errorHandler->error(left.getSlice(), - "Left side of assignment must be a variable"); + "Left side of assignment must be a variable or field access"); return; } if (left.getTypeID() == -1 || right.getTypeID() == -1) { @@ -277,6 +278,10 @@ void SemanticAnalyzer::visit([[maybe_unused]] PathExpression &node) { // TODO } +void SemanticAnalyzer::visit([[maybe_unused]] FieldAccessExpression &node) { + // TODO +} + void SemanticAnalyzer::visit(IfElseExpression &node) { Expression &condition = node.getCondition(); condition.accept(*this); @@ -445,19 +450,24 @@ void SemanticAnalyzer::visit(Module &node) { } }); node.forEachImpl([this](std::string_view implName, Impl &impl, bool /*unused*/) { - impl.forEachMethod([this, &implName](std::string_view /*unused*/, Function &method) { + impl.forEachMethod([this, &implName](std::string_view /*unused*/, + Function &method) { bool first = true; - method.forEachParameter([this, &method, &first, &implName](Symbol ¶meter, Symbol &type) { + method.forEachParameter([this, &method, &first, &implName](Symbol ¶meter, + Symbol &type) { int typeID = module->getType(type.s.contents).id; if (typeID == -1) { errorHandler->error(type.s, "Unknown type"); return; } - if (first && method.getIsConstructor() && parameter.s.contents != "self") { - errorHandler->error(parameter.s, "First parameter of constructor must be self"); + if (first && method.getIsConstructor() + && parameter.s.contents != "self") { + errorHandler->error(parameter.s, + "First parameter of constructor must be self"); } if (first && method.getIsConstructor() && type.s.contents != implName) { - errorHandler->error(type.s, "Constructor self parameter type must be the same as impl name"); + errorHandler->error(type.s, "Constructor self parameter type must be " + "the same as impl name"); } first = false; method.getBody().pushSymbol(parameter.s.contents, typeID, diff --git a/src/semanticanalyzer.h b/src/semanticanalyzer.h index d9f3b43..810f61f 100644 --- a/src/semanticanalyzer.h +++ b/src/semanticanalyzer.h @@ -34,6 +34,7 @@ class SemanticAnalyzer : public ASTVisitor { void visit(ReturnExpression &node) override; void visit(ParenthesizedExpression &node) override; void visit(PathExpression &node) override; + void visit(FieldAccessExpression &node) override; void visit(IfElseExpression &node) override; void visit(WhileExpression &node) override; void visit(ExpressionStatement &node) override; diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_binary_expression/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_binary_expression/expected/stderr index 2ddbb26..8100567 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_binary_expression/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_binary_expression/expected/stderr @@ -1 +1 @@ -Error at {main}:4:5: Left side of assignment must be a variable +Error at {main}:4:5: Left side of assignment must be a variable or field access diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_function_call/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_function_call/expected/stderr index 7cc7bb1..e9e704c 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_function_call/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_function_call/expected/stderr @@ -1 +1 @@ -Error at {main}:2:5: Left side of assignment must be a variable +Error at {main}:2:5: Left side of assignment must be a variable or field access diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_integer/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_integer/expected/stderr index 7cc7bb1..e9e704c 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_integer/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_integer/expected/stderr @@ -1 +1 @@ -Error at {main}:2:5: Left side of assignment must be a variable +Error at {main}:2:5: Left side of assignment must be a variable or field access diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_parenthesized_expression/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_parenthesized_expression/expected/stderr index 17b653b..be8fc24 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_parenthesized_expression/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_parenthesized_expression/expected/stderr @@ -1 +1 @@ -Error at {main}:3:5: Left side of assignment must be a variable +Error at {main}:3:5: Left side of assignment must be a variable or field access diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_unary_expression/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_unary_expression/expected/stderr index 17b653b..be8fc24 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_unary_expression/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/assignment_to_unary_expression/expected/stderr @@ -1 +1 @@ -Error at {main}:3:5: Left side of assignment must be a variable +Error at {main}:3:5: Left side of assignment must be a variable or field access From 625d2636d6a015d32f91c0c47835e1d067409639 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 06:40:41 -0600 Subject: [PATCH 25/32] test: instance method --- .../class_instance_method/main.canyon | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon diff --git a/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon new file mode 100644 index 0000000..49fe21c --- /dev/null +++ b/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon @@ -0,0 +1,18 @@ +fun main() { + let foo: Foo = Foo::new(); + let result: i32 = foo.bar(); +} + +class Foo { + let x: i32 = 0; +} + +impl Foo { + constructor new(self: Foo) { + self.x = 1; + } + + fun bar(self: Foo): i32 { + self.x + } +} \ No newline at end of file From 58aa7f3f06196471cbf1705e4a1d5c187035930e Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 06:43:31 -0600 Subject: [PATCH 26/32] feat: instance methods --- src/ast.cpp | 91 +++++---- src/ast.h | 28 ++- src/ccodeadapter.cpp | 37 ++-- src/ccodegenerator.cpp | 176 +++++++++++++----- src/ccodegenerator.h | 10 + src/parser.cpp | 3 +- src/semanticanalyzer.cpp | 94 ++++++++-- .../expected/stderr | 2 +- .../expected/stderr | 2 +- 9 files changed, 318 insertions(+), 125 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 2b149b6..28bc4fd 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -56,12 +56,12 @@ void FunctionCallExpression::forEachArgument( } } -void FunctionCallExpression::setIsConstructor(bool isConstructor) { - this->isConstructor = isConstructor; +void FunctionCallExpression::setVariant(FunctionVariant variant) { + this->variant = variant; } -bool FunctionCallExpression::getIsConstructor() const { - return isConstructor; +FunctionVariant FunctionCallExpression::getVariant() const { + return variant; } void FunctionCallExpression::accept(ASTVisitor &visitor) { @@ -295,9 +295,9 @@ void PathExpression::accept(ASTVisitor &visitor) { } FieldAccessExpression::FieldAccessExpression(std::unique_ptr object, - std::unique_ptr field) - : Expression(Slice::merge(object->getSlice(), field->getSlice())), - object(std::move(object)), field(std::move(field)) { + std::unique_ptr field) + : Expression(Slice::merge(object->getSlice(), field->getSlice())), + object(std::move(object)), field(std::move(field)) { } Expression &FieldAccessExpression::getObject() { @@ -416,17 +416,17 @@ void LetStatement::accept(ASTVisitor &visitor) { Function::Function( std::vector, std::unique_ptr>> parameters, std::unique_ptr returnTypeAnnotation, std::unique_ptr body, - bool isConstructor) + FunctionVariant variant) : parameters(std::move(parameters)), returnTypeAnnotation(std::move(returnTypeAnnotation)), body(std::move(body)), - isConstructor(isConstructor) { + variant(variant) { } Function::Function( std::vector, std::unique_ptr>> parameters, - std::unique_ptr body, bool isConstructor) + std::unique_ptr body, FunctionVariant variant) : parameters(std::move(parameters)), returnTypeAnnotation(nullptr), - body(std::move(body)), isConstructor(isConstructor) { + body(std::move(body)), variant(variant) { } void Function::forEachParameter( @@ -452,8 +452,12 @@ void Function::setTypeID(int typeID) { this->typeID = typeID; } -bool Function::getIsConstructor() const { - return isConstructor; +void Function::setVariant(FunctionVariant variant) { + this->variant = variant; +} + +FunctionVariant Function::getVariant() const { + return variant; } void Function::accept(ASTVisitor &visitor) { @@ -501,23 +505,23 @@ void Class::accept(ASTVisitor &visitor) { visitor.visit(*this); } -Type::Type(int id, int parentID, std::string_view name) - : id(id), parentID(parentID), name(name) { +Type::Type(int id, int parentID, std::string_view name, bool isClass) + : id(id), parentID(parentID), isClass(isClass), name(name) { } Module::Module(std::filesystem::path source) : source(std::move(source)) { - insertType("()"); - insertType("!"); - insertType("i8"); - insertType("i16"); - insertType("i32"); - insertType("i64"); - insertType("u8"); - insertType("u16"); - insertType("u32"); - insertType("u64"); - insertType("bool"); - insertType("char"); + insertType("()", false); + insertType("!", false); + insertType("i8", false); + insertType("i16", false); + insertType("i32", false); + insertType("i64", false); + insertType("u8", false); + insertType("u16", false); + insertType("u32", false); + insertType("u64", false); + insertType("bool", false); + insertType("char", false); } Module::Module(const Module &module) @@ -564,23 +568,24 @@ void Module::forEachImpl( Type Module::getType(std::string_view typeName) { if (typeTableByName.find(typeName) == typeTableByName.end()) { - return Type(-1, -1, ""); + return Type(-1, -1, "", false); } return typeTableByName.at(typeName); } Type Module::getType(int id) { if (typeTableByID.find(id) == typeTableByID.end()) { - return Type(-1, -1, ""); + return Type(-1, -1, "", false); } return typeTableByID.at(id); } -void Module::insertType(std::string_view typeName) { +void Module::insertType(std::string_view typeName, bool isClass) { if (typeTableByName.find(typeName) == typeTableByName.end()) { - typeTableByName.insert({typeName, Type(typeTableByName.size(), -1, typeName)}); + typeTableByName.insert( + {typeName, Type(typeTableByName.size(), -1, typeName, isClass)}); typeTableByID.insert( - {typeTableByID.size(), Type(typeTableByID.size(), -1, typeName)}); + {typeTableByID.size(), Type(typeTableByID.size(), -1, typeName, isClass)}); } else { std::cerr << "Type already exists"; exit(EXIT_FAILURE); @@ -607,7 +612,7 @@ Type Module::getCommonTypeAncestor(int type1, int type2) { if (type2 == getType("!").id) { return getType(type1); } - return Type(-1, -1, ""); + return Type(-1, -1, "", false); } void Module::addUnaryOperator(Operator::Type op, int operandType, int resultType) { @@ -663,6 +668,26 @@ Impl *Module::getImpl(std::string_view name) { return std::get<0>(impls[name]).get(); } +std::pair Module::getImpl(int typeID) { + for (auto &[name, impl] : impls) { + int implTypeID = getType(name).id; + if (implTypeID == typeID) { + return {name, std::get<0>(impl).get()}; + } + } + return {{}, nullptr}; +} + +std::pair Module::getClass(int typeID) { + for (auto &[name, cls] : classes) { + int implTypeID = getType(name).id; + if (implTypeID == typeID) { + return {name, std::get<0>(cls).get()}; + } + } + return {{}, nullptr}; +} + std::filesystem::path Module::getSource() { return source; } diff --git a/src/ast.h b/src/ast.h index 4a9869c..b13c5f7 100644 --- a/src/ast.h +++ b/src/ast.h @@ -43,10 +43,16 @@ class Statement : public ASTComponent { virtual ~Statement() = default; }; +enum class FunctionVariant { + FUNCTION, + METHOD, + CONSTRUCTOR, + }; + class FunctionCallExpression : public Expression { std::unique_ptr function; std::vector> arguments; - bool isConstructor = false; + FunctionVariant variant; public: FunctionCallExpression(std::unique_ptr function, const Punctuation &open, std::vector> arguments, const Punctuation &close); @@ -55,8 +61,8 @@ class FunctionCallExpression : public Expression { Expression &getFunction(); void addFirstArgument(std::unique_ptr argument); void forEachArgument(const std::function &argumentHandler); - void setIsConstructor(bool isConstructor); - bool getIsConstructor() const; + void setVariant(FunctionVariant variant); + FunctionVariant getVariant() const; void accept(ASTVisitor &visitor) override; virtual ~FunctionCallExpression() = default; }; @@ -281,23 +287,24 @@ class Function : public ASTComponent { std::vector, std::unique_ptr>> parameters; std::unique_ptr returnTypeAnnotation; std::unique_ptr body; - bool isConstructor; + FunctionVariant variant; int typeID = -1; public: Function(std::vector, std::unique_ptr>> parameters, std::unique_ptr returnTypeAnnotation, - std::unique_ptr body, bool isConstructor); + std::unique_ptr body, FunctionVariant variant); Function(std::vector, std::unique_ptr>> parameters, - std::unique_ptr body, bool isConstructor); + std::unique_ptr body, FunctionVariant variant); void forEachParameter( const std::function ¶meterHandler); Symbol *getReturnTypeAnnotation(); BlockExpression &getBody(); int getTypeID() const; void setTypeID(int typeID); - bool getIsConstructor() const; + void setVariant(FunctionVariant variant); + FunctionVariant getVariant() const; void accept(ASTVisitor &visitor); ~Function() = default; }; @@ -329,8 +336,9 @@ class Impl : public ASTComponent { struct Type { int id; int parentID; + bool isClass; std::string_view name; - Type(int id, int parentID, std::string_view name); + Type(int id, int parentID, std::string_view name, bool isClass); Type(const Type &) = default; }; @@ -365,7 +373,7 @@ class Module : public ASTComponent { const std::function &implHandler); Type getType(std::string_view typeName); Type getType(int id); - void insertType(std::string_view typeName); + void insertType(std::string_view typeName, bool isClass); bool isTypeConvertible(int from, int to); Type getCommonTypeAncestor(int type1, int type2); void addUnaryOperator(Operator::Type op, int operandType, int resultType); @@ -375,6 +383,8 @@ class Module : public ASTComponent { int getBinaryOperator(Operator::Type op, int leftType, int rightType); Function *getFunction(std::string_view name); Impl *getImpl(std::string_view name); + std::pair getImpl(int typeID); + std::pair getClass(int typeID); std::filesystem::path getSource(); void accept(ASTVisitor &visitor); ~Module() = default; diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index 9072a92..6c5bd34 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -22,19 +22,20 @@ void CCodeAdapter::visit(FunctionCallExpression &node) { Expression &oldFunction = node.getFunction(); auto *oldSymbol = dynamic_cast(&oldFunction); auto *oldPath = dynamic_cast(&oldFunction); - if (oldSymbol == nullptr && oldPath == nullptr) { - std::cerr << "Function call target is not a symbol or path" << std::endl; + auto *oldFieldAccess = dynamic_cast(&oldFunction); + if (oldSymbol == nullptr && oldPath == nullptr && oldFieldAccess == nullptr) { + std::cerr << "Function call target is not a symbol, path, or method" << std::endl; exit(EXIT_FAILURE); } - std::unique_ptr newSymbolExpression = nullptr; + std::unique_ptr newWhichFunction = nullptr; if (oldSymbol != nullptr) { generatedStrings->push_back( "CANYON_FUNCTION_" + std::string(oldSymbol->getSymbol().s.contents)); std::string_view newName = generatedStrings->back(); std::unique_ptr newSymbol = std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)); - newSymbolExpression = std::make_unique(std::move(newSymbol)); - } else { + newWhichFunction = std::make_unique(std::move(newSymbol)); + } else if (oldPath != nullptr) { std::string functionName; bool first = true; oldPath->forEachSymbol([&functionName, &first](SymbolExpression &symbol) { @@ -48,7 +49,11 @@ void CCodeAdapter::visit(FunctionCallExpression &node) { std::string_view newName = generatedStrings->back(); std::unique_ptr newSymbol = std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)); - newSymbolExpression = std::make_unique(std::move(newSymbol)); + newWhichFunction = std::make_unique(std::move(newSymbol)); + } else { + oldFieldAccess->accept(*this); + newWhichFunction = std::unique_ptr( + dynamic_cast(returnValue.release())); } std::vector> newArguments; @@ -70,10 +75,10 @@ void CCodeAdapter::visit(FunctionCallExpression &node) { }); std::unique_ptr newFunctionCall - = std::make_unique(std::move(newSymbolExpression), + = std::make_unique(std::move(newWhichFunction), std::move(newArguments)); newFunctionCall->setTypeID(node.getTypeID()); - newFunctionCall->setIsConstructor(node.getIsConstructor()); + newFunctionCall->setVariant(node.getVariant()); returnValue = std::move(newFunctionCall); } @@ -471,7 +476,7 @@ void CCodeAdapter::visit(Function &node) { Slice(newParameterName, inputModule->getSource(), 0, 0)); std::unique_ptr newType = std::make_unique(type); newParameters.emplace_back(std::move(newParameter), std::move(newType)); - if (node.getIsConstructor() && i == 0) { + if (node.getVariant() == FunctionVariant::CONSTRUCTOR && i == 0) { typeId = node.getBody().getSymbolType(parameter.s.contents); } i++; @@ -488,7 +493,7 @@ void CCodeAdapter::visit(Function &node) { = std::make_unique(std::move(newBody)); enclosingScope->pushStatement(std::move(bodyStatement)); - if (node.getIsConstructor()) { + if (node.getVariant() == FunctionVariant::CONSTRUCTOR) { std::unique_ptr selfParameter = std::make_unique( Slice("CANYON_PARAMETER_self", inputModule->getSource(), 0, 0)); std::unique_ptr selfSymbolExpression @@ -501,8 +506,8 @@ void CCodeAdapter::visit(Function &node) { } std::unique_ptr newFunction = std::make_unique( - std::move(newParameters), std::move(enclosingScope), node.getIsConstructor()); - if (node.getIsConstructor()) { + std::move(newParameters), std::move(enclosingScope), node.getVariant()); + if (node.getVariant() == FunctionVariant::CONSTRUCTOR) { newFunction->setTypeID(typeId); } returnValue = std::move(newFunction); @@ -531,7 +536,7 @@ void CCodeAdapter::visit(Impl &node) { method.accept(*this); std::unique_ptr newMethod = std::unique_ptr( dynamic_cast(returnValue.release())); - if (!method.getIsConstructor()) { + if (method.getVariant() != FunctionVariant::CONSTRUCTOR) { newMethod->setTypeID(method.getTypeID()); } newMethods.emplace(newName, std::move(newMethod)); @@ -547,7 +552,7 @@ void CCodeAdapter::visit(Module &node) { oldFunction.accept(*this); std::unique_ptr newFunction = std::unique_ptr( dynamic_cast(returnValue.release())); - if (!oldFunction.getIsConstructor()) { + if (oldFunction.getVariant() != FunctionVariant::CONSTRUCTOR) { newFunction->setTypeID(oldFunction.getTypeID()); } outputModule->addFunction( @@ -555,7 +560,7 @@ void CCodeAdapter::visit(Module &node) { std::move(newFunction), isBuiltin); }); node.forEachClass([this](std::string_view name, Class &oldClass, bool isBuiltin) { - generatedStrings->push_back("CANYON_CLASS_" + std::string(name)); + generatedStrings->push_back(std::string(name)); std::string_view newName = generatedStrings->back(); currentClassName = &generatedStrings->back(); oldClass.accept(*this); @@ -566,7 +571,7 @@ void CCodeAdapter::visit(Module &node) { std::move(newClass), isBuiltin); }); node.forEachImpl([this](std::string_view name, Impl &oldImpl, bool isBuiltin) { - generatedStrings->push_back("CANYON_IMPL_" + std::string(name)); + generatedStrings->push_back(std::string(name)); std::string_view newName = generatedStrings->back(); oldImpl.accept(*this); std::unique_ptr newImpl diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index 7a746d5..656e9eb 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -28,7 +28,8 @@ CCodeGenerator::CCodeGenerator(Module *module, std::ostream *os) [this, &module](std::string_view name, Class & /*unused*/, bool isBuiltin) { if (!isBuiltin) { int typeID = module->getType(std::string(name)).id; - cTypes[typeID] = "struct CANYON_CLASS_" + std::string(name) + " *"; + cTypes[typeID] = "struct CANYON_INSTANCE_" + std::string(name); + classNames[typeID] = std::string(name); } }); } @@ -50,16 +51,25 @@ void CCodeGenerator::generateIncludes() { } void CCodeGenerator::visit(FunctionCallExpression &node) { + Status old = status; + status = Status::IN_CALL; node.getFunction().accept(*this); + status = old; *os << '('; bool first = true; - if (node.getIsConstructor()) { - std::string_view type = cTypes[node.getTypeID()]; - // need to convert the pointer type in the map to the struct itself - type = type.substr(0, type.size() - 2); - *os << "malloc(sizeof(" << type << "))"; + if (node.getVariant() == FunctionVariant::CONSTRUCTOR) { + std::string_view name = classNames[node.getTypeID()]; + *os << "malloc(sizeof(struct CANYON_CLASS_" << name << "))"; first = false; } + if (node.getVariant() == FunctionVariant::METHOD) { + FieldAccessExpression &fieldAccess + = dynamic_cast(node.getFunction()); + *os << "(struct CANYON_CLASS_" << classNames[fieldAccess.getObject().getTypeID()] + << " *)"; + fieldAccess.getObject().accept(*this); + *os << ".object"; + } node.forEachArgument([this, &first, &node](Expression &argument) { if (!first) { *os << ", "; @@ -189,8 +199,20 @@ void CCodeGenerator::visit([[maybe_unused]] PathExpression &node) { } void CCodeGenerator::visit(FieldAccessExpression &node) { + if (status == Status::IN_CALL) { + node.getObject().accept(*this); + *os << ".vt->"; + node.getField().accept(*this); + return; + } + if (node.getObject().getSlice().contents == "CANYON_PARAMETER_self") { + *os << "CANYON_PARAMETER_self->"; + node.getField().accept(*this); + return; + } + *os << "((struct CANYON_CLASS_" << classNames[node.getObject().getTypeID()] << " *)"; node.getObject().accept(*this); - *os << "->"; + *os << ".object)->"; node.getField().accept(*this); } @@ -225,9 +247,19 @@ void CCodeGenerator::visit(LetStatement &node) { const std::string &cType = cTypes[node.getSymbolTypeID()]; Expression *expression = node.getExpression(); if (expression != nullptr) { - *os << cType << ' ' << node.getSymbol().s << " = "; - node.getExpression()->accept(*this); - *os << ";\n"; + FunctionCallExpression *functionCall + = dynamic_cast(expression); + if (module->getType(node.getSymbolTypeID()).isClass && functionCall != nullptr + && functionCall->getVariant() == FunctionVariant::CONSTRUCTOR) { + *os << cType << ' ' << node.getSymbol().s << " = {"; + expression->accept(*this); + std::string_view className = classNames[node.getSymbolTypeID()]; + *os << ", &" << className << "_VT_INSTANCE};\n"; + } else { + *os << cType << ' ' << node.getSymbol().s << " = "; + node.getExpression()->accept(*this); + *os << ";\n"; + } } else { *os << cType << ' ' << node.getSymbol().s << ";\n"; } @@ -269,17 +301,32 @@ void CCodeGenerator::visit(Module &node) { node.forEachImpl([this](std::string_view name, Impl &impl, bool /*unused*/) { impl.forEachMethod([this, &name](std::string_view methodName, Function &method) { Type functionType = module->getType(method.getTypeID()); - const std::string &cType = cTypes[functionType.id]; - *os << cType << ' ' << name << "_METHOD_" << methodName << '('; + if (method.getVariant() == FunctionVariant::CONSTRUCTOR) { + *os << "struct CANYON_CLASS_" << classNames[functionType.id] << " *"; + } else { + const std::string &cType = cTypes[functionType.id]; + *os << cType; + } + *os << " CANYON_IMPL_" << name << "_METHOD_" << methodName << '('; bool first = true; - method.forEachParameter([this, &first](Symbol ¶meter, Symbol &type) { - const std::string &cType = cTypes[module->getType(type.s.contents).id]; - if (!first) { - *os << ", "; - } - first = false; - *os << cType << ' ' << parameter.s; - }); + method.forEachParameter( + [this, &first](Symbol ¶meter, Symbol &typeSymbol) { + Type type = module->getType(typeSymbol.s.contents); + if (first && type.isClass + && parameter.s.contents == "CANYON_PARAMETER_self") { + *os << "struct CANYON_CLASS_" << classNames[type.id] + << " *CANYON_PARAMETER_self"; + first = false; + return; + } + const std::string &cType + = cTypes[module->getType(typeSymbol.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType << ' ' << parameter.s; + }); *os << ");\n"; }); }); @@ -287,40 +334,62 @@ void CCodeGenerator::visit(Module &node) { // Virtual tables node.forEachImpl([this](std::string_view name, Impl &impl, bool /*unused*/) { - *os << "struct " << name << "_VT {\n"; + *os << "struct CANYON_IMPL_" << name << "_VT {\n"; tabLevel++; impl.forEachMethod([this](std::string_view methodName, Function &method) { + if (method.getVariant() != FunctionVariant::METHOD) { + return; + } Type methodType = module->getType(method.getTypeID()); const std::string &cType = cTypes[methodType.id]; *os << std::string(tabLevel, '\t') << cType << " (*" << methodName << ")("; bool first = true; - method.forEachParameter([this, &first](Symbol & /*unused*/, Symbol &type) { - const std::string &cType = cTypes[module->getType(type.s.contents).id]; - if (!first) { - *os << ", "; - } - first = false; - *os << cType; - }); + method.forEachParameter( + [this, &first](Symbol ¶meter, Symbol &typeSymbol) { + Type type = module->getType(typeSymbol.s.contents); + if (first && type.isClass + && parameter.s.contents == "CANYON_PARAMETER_self") { + *os << "struct CANYON_CLASS_" << classNames[type.id] << '*'; + first = false; + return; + } + const std::string &cType + = cTypes[module->getType(typeSymbol.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType; + }); *os << ");\n"; }); tabLevel--; *os << "} " << name << "_VT_INSTANCE = {\n"; tabLevel++; - impl.forEachMethod( - [this, &name](std::string_view methodName, Function & /*unused*/) { - std::string newName - = std::string(name) + "_METHOD_" + std::string(methodName); - *os << std::string(tabLevel, '\t') << newName << ",\n"; - }); + impl.forEachMethod([this, &name](std::string_view methodName, Function &method) { + if (method.getVariant() != FunctionVariant::METHOD) { + return; + } + *os << std::string(tabLevel, '\t') << "CANYON_IMPL_" << name << "_METHOD_" + << methodName << ",\n"; + }); tabLevel--; *os << "};\n"; }); *os << '\n'; + // Instance structs + node.forEachImpl([this](std::string_view name, Impl & /*unused*/, bool /*unused*/) { + *os << "struct CANYON_INSTANCE_" << name << " {\n" + << "\tvoid *object;\n" + << "\tstruct CANYON_IMPL_" << name << "_VT *vt;\n" + << "};\n"; + }); + *os << '\n'; + // Class structs node.forEachClass([this](std::string_view name, Class &cls, bool /*unused*/) { - *os << "struct " << name << " {\n"; + *os << "struct CANYON_CLASS_" << name << " {\n"; tabLevel++; cls.forEachFieldDeclaration([this](LetStatement &declaration) { *os << std::string(tabLevel, '\t'); @@ -364,17 +433,32 @@ void CCodeGenerator::visit(Module &node) { } impl.forEachMethod([this, &name](std::string_view methodName, Function &method) { Type functionType = module->getType(method.getTypeID()); - const std::string &cType = cTypes[functionType.id]; - *os << cType << ' ' << name << "_METHOD_" << methodName << '('; + if (method.getVariant() == FunctionVariant::CONSTRUCTOR) { + *os << "struct CANYON_CLASS_" << classNames[functionType.id] << " *"; + } else { + const std::string &cType = cTypes[functionType.id]; + *os << cType; + } + *os << " CANYON_IMPL_" << name << "_METHOD_" << methodName << '('; bool first = true; - method.forEachParameter([this, &first](Symbol ¶meter, Symbol &type) { - const std::string &cType = cTypes[module->getType(type.s.contents).id]; - if (!first) { - *os << ", "; - } - first = false; - *os << cType << ' ' << parameter.s; - }); + method.forEachParameter( + [this, &first](Symbol ¶meter, Symbol &typeSymbol) { + Type type = module->getType(typeSymbol.s.contents); + if (first && type.isClass + && parameter.s.contents == "CANYON_PARAMETER_self") { + *os << "struct CANYON_CLASS_" << classNames[type.id] + << " *CANYON_PARAMETER_self"; + first = false; + return; + } + const std::string &cType + = cTypes[module->getType(typeSymbol.s.contents).id]; + if (!first) { + *os << ", "; + } + first = false; + *os << cType << ' ' << parameter.s; + }); *os << ") "; method.getBody().accept(*this); *os << "\n"; diff --git a/src/ccodegenerator.h b/src/ccodegenerator.h index d085ac2..76cffeb 100644 --- a/src/ccodegenerator.h +++ b/src/ccodegenerator.h @@ -17,8 +17,18 @@ class CCodeGenerator : public ASTVisitor { Module *module; std::ostream *os; std::unordered_map cTypes; + std::unordered_map classNames; int tabLevel = 0; std::list generatedStrings; + enum class Status { + NORMAL, + IN_FUNCTION, + IN_METHOD, + IN_CLASS, + IN_IMPL, + IN_CALL, + }; + Status status = Status::NORMAL; public: CCodeGenerator(Module *module, std::ostream *os); void generate(); diff --git a/src/parser.cpp b/src/parser.cpp index 120b8a8..d25d64d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -207,7 +207,8 @@ std::pair, std::unique_ptr> Parser::parseFunct return {std::unique_ptr(symbol), std::make_unique(std::move(parameters), std::unique_ptr(type), - std::move(block), isConstructor)}; + std::move(block), isConstructor ? FunctionVariant::CONSTRUCTOR + : FunctionVariant::FUNCTION)}; } std::pair, std::unique_ptr> Parser::parseClass() { diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index 4542fb8..c62d619 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -38,9 +38,10 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { Expression &functionCall = node.getFunction(); auto *symbol = dynamic_cast(&functionCall); auto *path = dynamic_cast(&functionCall); - if (symbol == nullptr && path == nullptr) { + auto *fieldAccess = dynamic_cast(&functionCall); + if (symbol == nullptr && path == nullptr && fieldAccess == nullptr) { errorHandler->error(functionCall.getSlice(), - "Function call target is not a symbol or path"); + "Function call target is not a symbol, path, or method"); return; } Function *function = nullptr; @@ -52,7 +53,7 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { + " not found"); return; } - } else { + } else if (path != nullptr) { Impl *impl = nullptr; bool pathLookupGood = true; size_t i = 0; @@ -86,6 +87,32 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { if (!pathLookupGood) { return; } + } else { + FieldAccessExpression &fieldAccessExpr = *fieldAccess; + fieldAccessExpr.getObject().accept(*this); + if (inUnreachableCode) { + errorHandler->error(fieldAccessExpr.getSlice(), "Unreachable code"); + return; + } + int objectTypeId = fieldAccessExpr.getObject().getTypeID(); + if (objectTypeId == -1) { + return; + } + std::pair impl = module->getImpl(objectTypeId); + if (impl.second == nullptr) { + errorHandler->error(fieldAccessExpr.getSlice(), + "Field access on non-class type"); + return; + } + function + = impl.second->getMethod(fieldAccessExpr.getField().getSymbol().s.contents); + if (function == nullptr) { + errorHandler->error(fieldAccessExpr.getField().getSymbol().s, + "Method " + + std::string(fieldAccessExpr.getField().getSymbol().s.contents) + + " not found in impl for " + std::string(impl.first)); + return; + } } std::vector> parameters; function->forEachParameter( @@ -98,7 +125,8 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { size_t i = 0; bool unreachableArgument = false; bool unreachableHandled = false; - if (function->getIsConstructor()) { + FunctionVariant variant = function->getVariant(); + if (variant == FunctionVariant::CONSTRUCTOR || variant == FunctionVariant::METHOD) { // Implicit self argument i++; } @@ -143,8 +171,8 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { return; } - node.setIsConstructor(function->getIsConstructor()); - if (function->getIsConstructor()) { + node.setVariant(function->getVariant()); + if (function->getVariant() == FunctionVariant::CONSTRUCTOR) { node.setTypeID(parameters[0].second); } else { node.setTypeID(function->getTypeID()); @@ -278,8 +306,28 @@ void SemanticAnalyzer::visit([[maybe_unused]] PathExpression &node) { // TODO } -void SemanticAnalyzer::visit([[maybe_unused]] FieldAccessExpression &node) { - // TODO +void SemanticAnalyzer::visit(FieldAccessExpression &node) { + node.getObject().accept(*this); + if (inUnreachableCode) { + errorHandler->error(node.getSlice(), "Unreachable code"); + return; + } + int objectTypeID = node.getObject().getTypeID(); + if (objectTypeID == -1) { + return; + } + std::pair cls = module->getClass(objectTypeID); + if (cls.second == nullptr) { + errorHandler->error(node.getSlice(), "Field access on non-class type"); + return; + } + cls.second->forEachFieldDeclaration( + [this, &node, &cls](LetStatement &fieldDeclaration) { + if (fieldDeclaration.getSymbol().s.contents + == node.getField().getSymbol().s.contents) { + node.setTypeID(fieldDeclaration.getSymbolTypeID()); + } + }); } void SemanticAnalyzer::visit(IfElseExpression &node) { @@ -427,7 +475,13 @@ void SemanticAnalyzer::visit(Module &node) { addDefaultOperators(&node); addRuntimeFunctions(&node, builtinApiJsonFile); node.forEachClass([this](std::string_view name, Class & /*unused*/, bool /*unused*/) { - module->insertType(name); + module->insertType(name, true); + }); + node.forEachClass([this](std::string_view /*unused*/, Class &cls, bool isBuiltin) { + if (isBuiltin) { + return; + } + cls.accept(*this); }); node.forEachFunction([this]([[maybe_unused]] std::string_view name, @@ -460,15 +514,25 @@ void SemanticAnalyzer::visit(Module &node) { errorHandler->error(type.s, "Unknown type"); return; } - if (first && method.getIsConstructor() + if (first && method.getVariant() == FunctionVariant::CONSTRUCTOR && parameter.s.contents != "self") { errorHandler->error(parameter.s, "First parameter of constructor must be self"); } - if (first && method.getIsConstructor() && type.s.contents != implName) { + if (first && method.getVariant() == FunctionVariant::CONSTRUCTOR + && type.s.contents != implName) { errorHandler->error(type.s, "Constructor self parameter type must be " "the same as impl name"); } + if (first && method.getVariant() == FunctionVariant::FUNCTION) { + if (parameter.s.contents == "self") { + method.setVariant(FunctionVariant::METHOD); + if (type.s.contents != implName) { + errorHandler->error(type.s, "Method self parameter type must " + "be the same as impl name"); + } + } + } first = false; method.getBody().pushSymbol(parameter.s.contents, typeID, SymbolSource::FunctionParameter); @@ -505,12 +569,6 @@ void SemanticAnalyzer::visit(Module &node) { } impl.accept(*this); }); - node.forEachClass([this](std::string_view /*unused*/, Class &cls, bool isBuiltin) { - if (isBuiltin) { - return; - } - cls.accept(*this); - }); if (!hasMain) { errorHandler->error(module->getSource(), "No main function"); } @@ -636,7 +694,7 @@ static void addRuntimeFunctions(Module *module, std::istream &builtinApiJsonFile = std::make_unique(Slice(returnType, "", 0, 0)); std::unique_ptr builtin = std::make_unique( std::move(parameters), std::move(returnTypeAnnotation), - std::make_unique(), false); + std::make_unique(), FunctionVariant::FUNCTION); module->addFunction(std::make_unique(Slice(functionName, "", 0, 0)), std::move(builtin), true); } diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_expression_function_call/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_expression_function_call/expected/stderr index 5b4cd4f..cf9ff8a 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_expression_function_call/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_expression_function_call/expected/stderr @@ -1 +1 @@ -Error at {main}:2:5: Function call target is not a symbol or path +Error at {main}:2:5: Function call target is not a symbol, path, or method diff --git a/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_integer_function_call/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_integer_function_call/expected/stderr index 5b4cd4f..cf9ff8a 100644 --- a/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_integer_function_call/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/semantics/non_symbol_integer_function_call/expected/stderr @@ -1 +1 @@ -Error at {main}:2:5: Function call target is not a symbol or path +Error at {main}:2:5: Function call target is not a symbol, path, or method From c1748feb55e0ee05a869af870c8d70b60dd53c57 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:18:57 -0600 Subject: [PATCH 27/32] feat: change class fields to not use let statement syntax --- src/ast.cpp | 37 +++++++++++--- src/ast.h | 14 +++-- src/ccodeadapter.cpp | 22 ++++---- src/ccodegenerator.cpp | 11 ++-- src/parser.cpp | 51 ++++++++++++++----- src/semanticanalyzer.cpp | 48 +++++++++-------- .../syntax/class_only_data/expected/stderr | 2 +- .../semantics/class_field_access/main.canyon | 2 +- .../class_instance_method/main.canyon | 2 +- .../success/syntax/class_simple/main.canyon | 4 +- 10 files changed, 124 insertions(+), 69 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 28bc4fd..03ba7fe 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -186,6 +186,14 @@ int BlockExpression::getSymbolType(std::string_view symbol) { return std::get<0>(symbols[symbol]); } +void BlockExpression::setSymbolType(std::string_view symbol, int typeID) { + if (symbols.find(symbol) == symbols.end()) { + std::cerr << "Symbol not found"; + exit(EXIT_FAILURE); + } + std::get<0>(symbols[symbol]) = typeID; +} + SymbolSource BlockExpression::getSymbolSource(std::string_view symbol) { if (symbols.find(symbol) == symbols.end()) { return SymbolSource::Unknown; @@ -379,6 +387,12 @@ LetStatement::LetStatement(const Keyword &let, std::unique_ptr symbol, equalSign(std::move(equalSign)), expression(std::move(expression)) { } +LetStatement::LetStatement(std::unique_ptr symbol, + std::unique_ptr typeAnnotation, Punctuation *semicolon) + : Statement(Slice::merge(symbol->s, semicolon->s)), symbol(std::move(symbol)), + typeAnnotation(std::move(typeAnnotation)), isFieldDeclaration(true) { +} + LetStatement::LetStatement(std::unique_ptr symbol, std::unique_ptr expression) : Statement(Slice("", "", 0, 0)), symbol(std::move(symbol)), typeAnnotation(nullptr), @@ -409,6 +423,10 @@ int LetStatement::getSymbolTypeID() const { return symbolTypeID; } +bool LetStatement::getIsFieldDeclaration() const { + return isFieldDeclaration; +} + void LetStatement::accept(ASTVisitor &visitor) { visitor.visit(*this); } @@ -468,6 +486,9 @@ Class::Class(std::vector> fieldDeclarations) : fieldDeclarations(std::move(fieldDeclarations)) { } +Class::Class(std::unique_ptr scope) : scope(std::move(scope)) { +} + void Class::forEachFieldDeclaration( const std::function &fieldHandler) { for (auto &field : fieldDeclarations) { @@ -475,6 +496,14 @@ void Class::forEachFieldDeclaration( } } +BlockExpression &Class::getScope() { + return *scope; +} + +void Class::accept(ASTVisitor &visitor) { + visitor.visit(*this); +} + Impl::Impl(std::unordered_map> methods) : methods(std::move(methods)) { } @@ -497,14 +526,6 @@ void Impl::accept(ASTVisitor &visitor) { visitor.visit(*this); } -BlockExpression &Class::getScope() { - return *scope; -} - -void Class::accept(ASTVisitor &visitor) { - visitor.visit(*this); -} - Type::Type(int id, int parentID, std::string_view name, bool isClass) : id(id), parentID(parentID), isClass(isClass), name(name) { } diff --git a/src/ast.h b/src/ast.h index b13c5f7..3885661 100644 --- a/src/ast.h +++ b/src/ast.h @@ -44,10 +44,10 @@ class Statement : public ASTComponent { }; enum class FunctionVariant { - FUNCTION, - METHOD, - CONSTRUCTOR, - }; + FUNCTION, + METHOD, + CONSTRUCTOR, +}; class FunctionCallExpression : public Expression { std::unique_ptr function; @@ -138,6 +138,7 @@ enum class SymbolSource { Unknown, LetStatement, FunctionParameter, + FieldDeclaration, GENERATED_Block, GENERATED_IfElse, GENERATED_While, @@ -157,6 +158,7 @@ class BlockExpression : public Expression { void forEachStatement(const std::function &statementHandler); Expression *getFinalExpression(); int getSymbolType(std::string_view symbol); + void setSymbolType(std::string_view symbol, int typeID); SymbolSource getSymbolSource(std::string_view symbol); void pushSymbol(std::string_view symbol, int typeID, SymbolSource source); void forEachSymbol( @@ -266,11 +268,13 @@ class LetStatement : public Statement { std::unique_ptr typeAnnotation; std::unique_ptr equalSign; std::unique_ptr expression; + bool isFieldDeclaration = false; int symbolTypeID = -1; public: LetStatement(const Keyword &let, std::unique_ptr symbol, std::unique_ptr typeAnnotation, std::unique_ptr equalSign, std::unique_ptr expression, Punctuation *semicolon); + LetStatement(std::unique_ptr symbol, std::unique_ptr typeAnnotation, Punctuation *semicolon); LetStatement(std::unique_ptr symbol, std::unique_ptr expression); Symbol &getSymbol(); Expression *getExpression(); @@ -278,6 +282,7 @@ class LetStatement : public Statement { Operator &getEqualSign(); void setSymbolTypeID(int typeID); int getSymbolTypeID() const; + bool getIsFieldDeclaration() const; void accept(ASTVisitor &visitor) override; virtual ~LetStatement() = default; }; @@ -315,6 +320,7 @@ class Class : public ASTComponent { std::unique_ptr scope = std::make_unique(); public: Class(std::vector> fieldDeclarations); + Class(std::unique_ptr scope); void forEachFieldDeclaration(const std::function &fieldHandler); BlockExpression &getScope(); void accept(ASTVisitor &visitor); diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index 6c5bd34..360a8ca 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -514,18 +514,16 @@ void CCodeAdapter::visit(Function &node) { } void CCodeAdapter::visit(Class &node) { - std::vector> newFieldDeclarations; - node.forEachFieldDeclaration([this, &newFieldDeclarations]( - LetStatement &declaration) { - declaration.accept(*this); - std::unique_ptr newDeclaration = std::unique_ptr( - dynamic_cast(returnValue.release())); - generatedStrings->push_back(std::string(declaration.getSymbol().s.contents)); - std::string_view newName = generatedStrings->back(); - newDeclaration->getSymbol().s.contents = newName; - newFieldDeclarations.push_back(std::move(newDeclaration)); - }); - returnValue = std::make_unique(std::move(newFieldDeclarations)); + std::unique_ptr newScope = std::make_unique(); + node.getScope().forEachSymbol( + [this, &newScope](std::string_view name, int typeID, SymbolSource source) { + generatedStrings->push_back(std::string(name)); + std::string_view newName = generatedStrings->back(); + std::unique_ptr newSymbol = std::make_unique( + Slice(newName, inputModule->getSource(), 0, 0)); + newScope->pushSymbol(newName, typeID, source); + }); + returnValue = std::make_unique(std::move(newScope)); } void CCodeAdapter::visit(Impl &node) { diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index 656e9eb..ac3bc2a 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -391,11 +391,12 @@ void CCodeGenerator::visit(Module &node) { node.forEachClass([this](std::string_view name, Class &cls, bool /*unused*/) { *os << "struct CANYON_CLASS_" << name << " {\n"; tabLevel++; - cls.forEachFieldDeclaration([this](LetStatement &declaration) { - *os << std::string(tabLevel, '\t'); - const std::string &cType = cTypes[declaration.getSymbolTypeID()]; - *os << cType << ' ' << declaration.getSymbol().s << ";\n"; - }); + cls.getScope().forEachSymbol( + [this](std::string_view fieldName, int typeId, SymbolSource /*unused*/) { + *os << std::string(tabLevel, '\t'); + const std::string &cType = cTypes[typeId]; + *os << cType << ' ' << fieldName << ";\n"; + }); tabLevel--; *os << "};\n"; }); diff --git a/src/parser.cpp b/src/parser.cpp index d25d64d..1362c52 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -207,8 +207,9 @@ std::pair, std::unique_ptr> Parser::parseFunct return {std::unique_ptr(symbol), std::make_unique(std::move(parameters), std::unique_ptr(type), - std::move(block), isConstructor ? FunctionVariant::CONSTRUCTOR - : FunctionVariant::FUNCTION)}; + std::move(block), + isConstructor ? FunctionVariant::CONSTRUCTOR + : FunctionVariant::FUNCTION)}; } std::pair, std::unique_ptr> Parser::parseClass() { @@ -235,20 +236,44 @@ std::pair, std::unique_ptr> Parser::parseClass() std::vector> fields; while (true) { - auto *keyword2 = dynamic_cast(tokens[i].get()); - if (keyword2 != nullptr && keyword2->type == Keyword::Type::LET) { - std::unique_ptr let = parseLet(); - if (let == nullptr) { - return {nullptr, nullptr}; - } - fields.push_back(std::move(let)); - } else if (keyword2 != nullptr) { - errorHandler->error(*tokens[i], "Unexpected keyword in class definition"); + auto *p1 = dynamic_cast(tokens[i].get()); + if (p1 != nullptr && p1->type == Punctuation::Type::CloseBrace) { + break; + } + auto *field = dynamic_cast(tokens[i].get()); + if (field == nullptr) { + errorHandler->error(*tokens[i], "Expected symbol for class field declaration"); mustSynchronize = true; return {nullptr, nullptr}; - } else { - break; } + field = dynamic_cast(tokens[i++].release()); + auto *p2 = dynamic_cast(tokens[i].get()); + if (p2 == nullptr || p2->type != Punctuation::Type::Colon) { + errorHandler->error(*tokens[i], + "Expected ':' following symbol in class field declaration"); + mustSynchronize = true; + return {nullptr, nullptr}; + } + i++; + Symbol *type = dynamic_cast(tokens[i].get()); + if (type == nullptr) { + errorHandler->error(*tokens[i], + "Expected type following ':' in `let` statement"); + mustSynchronize = true; + return {nullptr, nullptr}; + } + type = dynamic_cast(tokens[i++].release()); + auto *p3 = dynamic_cast(tokens[i].get()); + if (p3 == nullptr || p3->type != Punctuation::Type::Semicolon) { + errorHandler->error(*tokens[i], + "Expected ';' following type in class field declaration"); + mustSynchronize = true; + return {nullptr, nullptr}; + } + i++; + + fields.emplace_back(std::make_unique(std::unique_ptr(field), + std::unique_ptr(type), punc)); } auto *p2 = dynamic_cast(tokens[i].get()); diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index c62d619..eed309b 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -321,13 +321,12 @@ void SemanticAnalyzer::visit(FieldAccessExpression &node) { errorHandler->error(node.getSlice(), "Field access on non-class type"); return; } - cls.second->forEachFieldDeclaration( - [this, &node, &cls](LetStatement &fieldDeclaration) { - if (fieldDeclaration.getSymbol().s.contents - == node.getField().getSymbol().s.contents) { - node.setTypeID(fieldDeclaration.getSymbolTypeID()); - } - }); + cls.second->getScope().forEachSymbol([this, &node, &cls](std::string_view fieldName, + int /*unused*/, SymbolSource /*unused*/) { + if (fieldName == node.getField().getSymbol().s.contents) { + node.setTypeID(cls.second->getScope().getSymbolType(fieldName)); + } + }); } void SemanticAnalyzer::visit(IfElseExpression &node) { @@ -406,21 +405,26 @@ void SemanticAnalyzer::visit(LetStatement &node) { return; } int typeID = module->getType(typeAnnotation->s.contents).id; - Expression *value = node.getExpression(); - if (!value) { - errorHandler->error(node.getSlice(), "Expression required for let statement"); - return; - } - value->accept(*this); - if (inUnreachableCode) { - errorHandler->error(node.getEqualSign().s, "Unreachable code"); - return; - } - scopeStack.back()->pushSymbol(node.getSymbol().s.contents, typeID, - SymbolSource::LetStatement); - if (typeID != value->getTypeID() && value->getTypeID() != -1) { - errorHandler->error(node.getExpression()->getSlice(), - "Type mismatch in let statement"); + if (!node.getIsFieldDeclaration()) { + Expression *value = node.getExpression(); + if (!value) { + errorHandler->error(node.getSlice(), "Expression required for let statement"); + return; + } + value->accept(*this); + if (inUnreachableCode) { + errorHandler->error(node.getEqualSign().s, "Unreachable code"); + return; + } + scopeStack.back()->pushSymbol(node.getSymbol().s.contents, typeID, + SymbolSource::LetStatement); + if (typeID != value->getTypeID() && value->getTypeID() != -1) { + errorHandler->error(node.getExpression()->getSlice(), + "Type mismatch in let statement"); + } + } else { + scopeStack.back()->pushSymbol(node.getSymbol().s.contents, typeID, + SymbolSource::FieldDeclaration); } node.setSymbolTypeID(typeID); } diff --git a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stderr b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stderr index 68219c9..3dd0858 100644 --- a/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stderr +++ b/test/end_to_end_tests/tests/canyon_failure/syntax/class_only_data/expected/stderr @@ -1,2 +1,2 @@ -Error at {main}:11:5: Unexpected keyword in class definition +Error at {main}:11:5: Expected symbol for class field declaration Error at {main}:12:1: Expected keyword `fun` or `class` diff --git a/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon index 927f8d3..3f852c4 100644 --- a/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon +++ b/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon @@ -4,7 +4,7 @@ fun main() { } class Foo { - let x: i32 = 0; + x: i32; } impl Foo { diff --git a/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon index 49fe21c..f0d10f8 100644 --- a/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon +++ b/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon @@ -4,7 +4,7 @@ fun main() { } class Foo { - let x: i32 = 0; + x: i32; } impl Foo { diff --git a/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon b/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon index 5d7cce4..85cd13f 100644 --- a/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon +++ b/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon @@ -2,6 +2,6 @@ fun main() { } class Foo { - let x: i32 = 0; - let y: i32 = 0; + x: i32; + y: i32; } \ No newline at end of file From c66bdba8810da589ad4bd0a90f54cd891ea40c47 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:25:39 -0600 Subject: [PATCH 28/32] test: nested object field access --- .../class_nested_field_access/main.canyon | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/end_to_end_tests/tests/success/semantics/class_nested_field_access/main.canyon diff --git a/test/end_to_end_tests/tests/success/semantics/class_nested_field_access/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_nested_field_access/main.canyon new file mode 100644 index 0000000..402efa6 --- /dev/null +++ b/test/end_to_end_tests/tests/success/semantics/class_nested_field_access/main.canyon @@ -0,0 +1,29 @@ +fun main() { + let foo: Foo = Foo::new(5); + let bar: Bar = Bar::new(foo); + let result: i32 = bar.f.x; +} + +class Foo { + x: i32; +} + +class Bar { + f: Foo; +} + +impl Foo { + constructor new(self: Foo, x: i32) { + self.x = x; + } + + fun bar(self: Foo): i32 { + self.x + } +} + +impl Bar { + constructor new(self: Bar, f: Foo) { + self.f = f; + } +} From 6b915f62839ac0ee57df98fa0c563a8fe947597b Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:26:04 -0600 Subject: [PATCH 29/32] fix: add assignment operator for class types --- src/semanticanalyzer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index eed309b..3ba62b4 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -478,8 +478,12 @@ void SemanticAnalyzer::visit(Impl &node) { void SemanticAnalyzer::visit(Module &node) { addDefaultOperators(&node); addRuntimeFunctions(&node, builtinApiJsonFile); - node.forEachClass([this](std::string_view name, Class & /*unused*/, bool /*unused*/) { + int unitTypeID = node.getType("()").id; + node.forEachClass([this, unitTypeID](std::string_view name, Class & /*unused*/, + bool /*unused*/) { module->insertType(name, true); + int typeID = module->getType(name).id; + module->addBinaryOperator(Operator::Type::Assignment, typeID, typeID, unitTypeID); }); node.forEachClass([this](std::string_view /*unused*/, Class &cls, bool isBuiltin) { if (isBuiltin) { From 6387d02790a0fff7164a61d8ce504ac44ec8fab0 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:27:21 -0600 Subject: [PATCH 30/32] test: object method access without intermediate from constructor --- .../class_method_after_constructor/main.canyon | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 test/end_to_end_tests/tests/success/semantics/class_method_after_constructor/main.canyon diff --git a/test/end_to_end_tests/tests/success/semantics/class_method_after_constructor/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_method_after_constructor/main.canyon new file mode 100644 index 0000000..f50e225 --- /dev/null +++ b/test/end_to_end_tests/tests/success/semantics/class_method_after_constructor/main.canyon @@ -0,0 +1,17 @@ +fun main() { + let result: i32 = Foo::new().bar(); +} + +class Foo { + x: i32; +} + +impl Foo { + constructor new(self: Foo) { + self.x = 1; + } + + fun bar(self: Foo): i32 { + self.x + } +} \ No newline at end of file From 3dd5f47c07f869de469516f373de12bfa956d0f6 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 20 Jan 2025 20:52:18 -0600 Subject: [PATCH 31/32] fix: nested object field and method references --- src/ast.cpp | 4 +- src/ast.h | 7 ++- src/ccodeadapter.cpp | 118 +++++++++++++++++++++++++++++++++++---- src/ccodegenerator.cpp | 71 +++++++++++++---------- src/parser.cpp | 62 ++++++++++---------- src/parser.h | 2 +- src/semanticanalyzer.cpp | 80 ++++++++++++++++---------- 7 files changed, 235 insertions(+), 109 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 03ba7fe..3c5faec 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -303,7 +303,7 @@ void PathExpression::accept(ASTVisitor &visitor) { } FieldAccessExpression::FieldAccessExpression(std::unique_ptr object, - std::unique_ptr field) + std::unique_ptr field) : Expression(Slice::merge(object->getSlice(), field->getSlice())), object(std::move(object)), field(std::move(field)) { } @@ -312,7 +312,7 @@ Expression &FieldAccessExpression::getObject() { return *object; } -SymbolExpression &FieldAccessExpression::getField() { +Expression &FieldAccessExpression::getField() { return *field; } diff --git a/src/ast.h b/src/ast.h index 3885661..f27a70e 100644 --- a/src/ast.h +++ b/src/ast.h @@ -143,6 +143,7 @@ enum class SymbolSource { GENERATED_IfElse, GENERATED_While, GENERATED_Argument, + GENERATED_Temporary, }; class BlockExpression : public Expression { @@ -204,12 +205,12 @@ class PathExpression : public Expression { class FieldAccessExpression : public Expression { private: std::unique_ptr object; - std::unique_ptr field; + std::unique_ptr field; public: FieldAccessExpression(std::unique_ptr object, - std::unique_ptr field); + std::unique_ptr field); Expression &getObject(); - SymbolExpression &getField(); + Expression &getField(); void accept(ASTVisitor &visitor) override; virtual ~FieldAccessExpression() = default; }; diff --git a/src/ccodeadapter.cpp b/src/ccodeadapter.cpp index 360a8ca..56b3943 100644 --- a/src/ccodeadapter.cpp +++ b/src/ccodeadapter.cpp @@ -28,6 +28,7 @@ void CCodeAdapter::visit(FunctionCallExpression &node) { exit(EXIT_FAILURE); } std::unique_ptr newWhichFunction = nullptr; + std::string_view constructedObjectName = ""; if (oldSymbol != nullptr) { generatedStrings->push_back( "CANYON_FUNCTION_" + std::string(oldSymbol->getSymbol().s.contents)); @@ -36,6 +37,50 @@ void CCodeAdapter::visit(FunctionCallExpression &node) { = std::make_unique(Slice(newName, inputModule->getSource(), 0, 0)); newWhichFunction = std::make_unique(std::move(newSymbol)); } else if (oldPath != nullptr) { + if (node.getVariant() == FunctionVariant::CONSTRUCTOR) { + std::unique_ptr mallocSymbol + = std::make_unique(std::make_unique( + Slice("malloc", inputModule->getSource(), 0, 0))); + std::vector> mallocArguments; + + std::string_view className = ""; + inputModule->forEachClass([this, &node, &className](std::string_view name, + Class & /*unused*/, bool /*unused*/) { + int classTypeID = inputModule->getType(std::string(name)).id; + if (classTypeID == node.getTypeID()) { + className = name; + } + }); + generatedStrings->push_back( + "sizeof(struct CANYON_CLASS_" + std::string(className) + ")"); + std::unique_ptr mallocArgumentSymbol = std::make_unique( + Slice(generatedStrings->back(), inputModule->getSource(), 0, 0)); + mallocArguments.push_back( + std::make_unique(std::move(mallocArgumentSymbol))); + std::unique_ptr mallocFunctionCall + = std::make_unique(std::move(mallocSymbol), + std::move(mallocArguments)); + + generatedStrings->push_back("CANYON_TEMP_" + std::to_string(blockCount++)); + blockTemporaryVariables.push(generatedStrings->back()); + constructedObjectName = generatedStrings->back(); + std::unique_ptr instanceSymbol = std::make_unique( + Slice(generatedStrings->back(), inputModule->getSource(), 0, 0)); + + std::unique_ptr mallocLetStatement + = std::make_unique(std::move(instanceSymbol), + std::move(mallocFunctionCall)); + scopeStack.back()->pushSymbol(generatedStrings->back(), node.getTypeID(), + SymbolSource::GENERATED_Temporary); + mallocLetStatement->setSymbolTypeID(node.getTypeID()); + scopeStack.back()->pushStatement(std::move(mallocLetStatement)); + + std::unique_ptr arg + = std::make_unique(std::make_unique( + Slice(constructedObjectName, inputModule->getSource(), 0, 0))); + arg->setTypeID(node.getTypeID()); + node.addFirstArgument(std::move(arg)); + } std::string functionName; bool first = true; oldPath->forEachSymbol([&functionName, &first](SymbolExpression &symbol) { @@ -79,7 +124,20 @@ void CCodeAdapter::visit(FunctionCallExpression &node) { std::move(newArguments)); newFunctionCall->setTypeID(node.getTypeID()); newFunctionCall->setVariant(node.getVariant()); - returnValue = std::move(newFunctionCall); + + if (node.getVariant() == FunctionVariant::CONSTRUCTOR) { + std::unique_ptr constructorStatement + = std::make_unique(std::move(newFunctionCall)); + scopeStack.back()->pushStatement(std::move(constructorStatement)); + std::unique_ptr instanceSymbol = std::make_unique( + Slice(constructedObjectName, inputModule->getSource(), 0, 0)); + std::unique_ptr instance + = std::make_unique(std::move(instanceSymbol)); + instance->setTypeID(node.getTypeID()); + returnValue = std::move(instance); + } else { + returnValue = std::move(newFunctionCall); + } } void CCodeAdapter::visit(BinaryExpression &node) { @@ -156,8 +214,10 @@ void CCodeAdapter::visit(SymbolExpression &node) { if (source == SymbolSource::FunctionParameter) { generatedStrings->push_back( "CANYON_PARAMETER_" + std::string(oldSymbol.s.contents)); - } else { + } else if (source != SymbolSource::GENERATED_Temporary) { generatedStrings->push_back("CANYON_LOCAL_" + std::string(oldSymbol.s.contents)); + } else { + generatedStrings->emplace_back(oldSymbol.s.contents); } std::string_view newName = generatedStrings->back(); std::unique_ptr newSymbol @@ -255,15 +315,51 @@ void CCodeAdapter::visit(FieldAccessExpression &node) { dynamic_cast(returnValue.release())); // don't want to visit the field because otherwise it will get mangled; instead just // make a new one with the same name - generatedStrings->push_back(std::string(node.getField().getSymbol().s.contents)); - std::unique_ptr newField - = std::make_unique(std::make_unique( - Slice(generatedStrings->back(), inputModule->getSource(), 0, 0))); - std::unique_ptr newFieldAccessExpression - = std::make_unique(std::move(newObject), - std::move(newField)); - newFieldAccessExpression->setTypeID(node.getTypeID()); - returnValue = std::move(newFieldAccessExpression); + SymbolExpression *field = dynamic_cast(&node.getField()); + FunctionCallExpression *method + = dynamic_cast(&node.getField()); + if (field == nullptr && method == nullptr) { + std::cerr << "Field access target is not a symbol or method call" << std::endl; + exit(EXIT_FAILURE); + } + if (field != nullptr) { + generatedStrings->push_back(std::string(field->getSlice().contents)); + std::unique_ptr newField + = std::make_unique(std::make_unique( + Slice(generatedStrings->back(), inputModule->getSource(), 0, 0))); + std::unique_ptr newFieldAccessExpression + = std::make_unique(std::move(newObject), + std::move(newField)); + newFieldAccessExpression->setTypeID(node.getTypeID()); + returnValue = std::move(newFieldAccessExpression); + } else { + std::vector> newArguments; + generatedStrings->push_back( + std::string(newObject->getSlice().contents) + ".object"); + std::unique_ptr self + = std::make_unique(std::make_unique( + Slice(generatedStrings->back(), inputModule->getSource(), 0, 0))); + newArguments.push_back(std::move(self)); + method->forEachArgument([this, &newArguments](Expression &argument) { + argument.accept(*this); + newArguments.push_back(std::unique_ptr( + dynamic_cast(returnValue.release()))); + }); + generatedStrings->push_back( + std::string(method->getFunction().getSlice().contents)); + std::unique_ptr newMethod + = std::make_unique(std::make_unique( + Slice(generatedStrings->back(), inputModule->getSource(), 0, 0))); + newMethod->setTypeID(node.getTypeID()); + std::unique_ptr newMethodCall + = std::make_unique(std::move(newMethod), + std::move(newArguments)); + std::unique_ptr newFieldAccessExpression + = std::make_unique(std::move(newObject), + std::move(newMethodCall)); + newFieldAccessExpression->setTypeID(node.getTypeID()); + returnValue = std::move(newFieldAccessExpression); + } } void CCodeAdapter::visit(IfElseExpression &node) { diff --git a/src/ccodegenerator.cpp b/src/ccodegenerator.cpp index ac3bc2a..930cd65 100644 --- a/src/ccodegenerator.cpp +++ b/src/ccodegenerator.cpp @@ -51,17 +51,9 @@ void CCodeGenerator::generateIncludes() { } void CCodeGenerator::visit(FunctionCallExpression &node) { - Status old = status; - status = Status::IN_CALL; node.getFunction().accept(*this); - status = old; *os << '('; - bool first = true; - if (node.getVariant() == FunctionVariant::CONSTRUCTOR) { - std::string_view name = classNames[node.getTypeID()]; - *os << "malloc(sizeof(struct CANYON_CLASS_" << name << "))"; - first = false; - } + if (node.getVariant() == FunctionVariant::METHOD) { FieldAccessExpression &fieldAccess = dynamic_cast(node.getFunction()); @@ -70,12 +62,18 @@ void CCodeGenerator::visit(FunctionCallExpression &node) { fieldAccess.getObject().accept(*this); *os << ".object"; } + bool first = true; node.forEachArgument([this, &first, &node](Expression &argument) { if (!first) { *os << ", "; } + if (first && node.getVariant() == FunctionVariant::CONSTRUCTOR) { + argument.accept(*this); + *os << ".object"; + } else { + argument.accept(*this); + } first = false; - argument.accept(*this); }); *os << ')'; } @@ -199,21 +197,32 @@ void CCodeGenerator::visit([[maybe_unused]] PathExpression &node) { } void CCodeGenerator::visit(FieldAccessExpression &node) { - if (status == Status::IN_CALL) { - node.getObject().accept(*this); - *os << ".vt->"; + SymbolExpression *field = dynamic_cast(&node.getField()); + FunctionCallExpression *method + = dynamic_cast(&node.getField()); + if (field != nullptr) { + if (node.getObject().getSlice().contents == "CANYON_PARAMETER_self") { + *os << "CANYON_PARAMETER_self->"; + } else { + *os << "((struct CANYON_CLASS_" << classNames[node.getObject().getTypeID()] + << " *)"; + node.getObject().accept(*this); + *os << ".object)->"; + } node.getField().accept(*this); return; } - if (node.getObject().getSlice().contents == "CANYON_PARAMETER_self") { - *os << "CANYON_PARAMETER_self->"; + if (method != nullptr) { + node.getObject().accept(*this); + *os << ".vt->"; + Status old = status; + status = Status::IN_METHOD; node.getField().accept(*this); + status = old; return; } - *os << "((struct CANYON_CLASS_" << classNames[node.getObject().getTypeID()] << " *)"; - node.getObject().accept(*this); - *os << ".object)->"; - node.getField().accept(*this); + std::cerr << "Field access target is not a symbol or method call" << std::endl; + exit(EXIT_FAILURE); } void CCodeGenerator::visit(IfElseExpression &node) { @@ -250,11 +259,12 @@ void CCodeGenerator::visit(LetStatement &node) { FunctionCallExpression *functionCall = dynamic_cast(expression); if (module->getType(node.getSymbolTypeID()).isClass && functionCall != nullptr - && functionCall->getVariant() == FunctionVariant::CONSTRUCTOR) { + && functionCall->getFunction().getSlice().contents == "malloc") { *os << cType << ' ' << node.getSymbol().s << " = {"; expression->accept(*this); std::string_view className = classNames[node.getSymbolTypeID()]; - *os << ", &" << className << "_VT_INSTANCE};\n"; + *os << ", &" << className << "_VT_INSTANCE"; + *os << "};\n"; } else { *os << cType << ' ' << node.getSymbol().s << " = "; node.getExpression()->accept(*this); @@ -278,6 +288,16 @@ void CCodeGenerator::visit([[maybe_unused]] Impl &node) { } void CCodeGenerator::visit(Module &node) { + // Instance structs + node.forEachImpl([this](std::string_view name, Impl & /*unused*/, bool /*unused*/) { + *os << "struct CANYON_IMPL_" << name << "_VT;\n"; + *os << "struct CANYON_INSTANCE_" << name << " {\n" + << "\tvoid *object;\n" + << "\tstruct CANYON_IMPL_" << name << "_VT *vt;\n" + << "};\n"; + }); + *os << '\n'; + // Function prototypes node.forEachFunction( [this](std::string_view name, Function &function, bool /*unused*/) { @@ -378,15 +398,6 @@ void CCodeGenerator::visit(Module &node) { }); *os << '\n'; - // Instance structs - node.forEachImpl([this](std::string_view name, Impl & /*unused*/, bool /*unused*/) { - *os << "struct CANYON_INSTANCE_" << name << " {\n" - << "\tvoid *object;\n" - << "\tstruct CANYON_IMPL_" << name << "_VT *vt;\n" - << "};\n"; - }); - *os << '\n'; - // Class structs node.forEachClass([this](std::string_view name, Class &cls, bool /*unused*/) { *os << "struct CANYON_CLASS_" << name << " {\n"; diff --git a/src/parser.cpp b/src/parser.cpp index 1362c52..a464d3f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -242,7 +242,8 @@ std::pair, std::unique_ptr> Parser::parseClass() } auto *field = dynamic_cast(tokens[i].get()); if (field == nullptr) { - errorHandler->error(*tokens[i], "Expected symbol for class field declaration"); + errorHandler->error(*tokens[i], + "Expected symbol for class field declaration"); mustSynchronize = true; return {nullptr, nullptr}; } @@ -850,11 +851,37 @@ std::unique_ptr Parser::parseUnaryExpression() { return std::make_unique(std::unique_ptr(op), std::move(expr)); } - return parseFunctionCallExpression(); + return parseFieldAccessExpression(); +} + +std::unique_ptr Parser::parseFieldAccessExpression() { + std::unique_ptr expr = parseFunctionCallExpression(); + if (expr == nullptr) { + return nullptr; + } + while (true) { + auto *punc = dynamic_cast(tokens[i].get()); + if (punc != nullptr && punc->type == Punctuation::Type::Period) { + i++; + std::unique_ptr expr2 = parseFunctionCallExpression(); + if (expr2 == nullptr) { + return nullptr; + } + if (!dynamic_cast(expr2.get()) + && !dynamic_cast(expr2.get())) { + errorHandler->error(*tokens[i], "Expected symbol or method call"); + return nullptr; + } + expr = std::make_unique(std::move(expr), + std::move(expr2)); + } else { + return expr; + } + } } std::unique_ptr Parser::parseFunctionCallExpression() { - std::unique_ptr expr = parseFieldAccessExpression(); + std::unique_ptr expr = parsePathExpression(); if (expr == nullptr) { return nullptr; } @@ -890,35 +917,6 @@ std::unique_ptr Parser::parseFunctionCallExpression() { return expr; } -std::unique_ptr Parser::parseFieldAccessExpression() { - std::unique_ptr expr = parsePathExpression(); - if (expr == nullptr) { - return nullptr; - } - if (!dynamic_cast(expr.get())) { - return expr; - } - while (true) { - auto *punc = dynamic_cast(tokens[i].get()); - if (punc != nullptr && punc->type == Punctuation::Type::Period) { - i++; - std::unique_ptr expr2 = parsePathExpression(); - if (expr2 == nullptr) { - return nullptr; - } - if (!dynamic_cast(expr2.get())) { - errorHandler->error(*tokens[i], "Expected symbol"); - return nullptr; - } - expr = std::make_unique(std::move(expr), - std::unique_ptr( - dynamic_cast(expr2.release()))); - } else { - return expr; - } - } -} - std::unique_ptr Parser::parsePathExpression() { std::unique_ptr expr = parsePrimaryExpression(); if (expr == nullptr) { diff --git a/src/parser.h b/src/parser.h index b89afc6..484d2d1 100644 --- a/src/parser.h +++ b/src/parser.h @@ -47,8 +47,8 @@ class Parser { std::unique_ptr parseAdditiveExpression(); std::unique_ptr parseMultiplicativeExpression(); std::unique_ptr parseUnaryExpression(); - std::unique_ptr parseFunctionCallExpression(); std::unique_ptr parseFieldAccessExpression(); + std::unique_ptr parseFunctionCallExpression(); std::unique_ptr parsePathExpression(); std::unique_ptr parsePrimaryExpression(); void synchronize(); diff --git a/src/semanticanalyzer.cpp b/src/semanticanalyzer.cpp index 3ba62b4..5f7ff4f 100644 --- a/src/semanticanalyzer.cpp +++ b/src/semanticanalyzer.cpp @@ -38,8 +38,7 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { Expression &functionCall = node.getFunction(); auto *symbol = dynamic_cast(&functionCall); auto *path = dynamic_cast(&functionCall); - auto *fieldAccess = dynamic_cast(&functionCall); - if (symbol == nullptr && path == nullptr && fieldAccess == nullptr) { + if (symbol == nullptr && path == nullptr) { errorHandler->error(functionCall.getSlice(), "Function call target is not a symbol, path, or method"); return; @@ -87,32 +86,6 @@ void SemanticAnalyzer::visit(FunctionCallExpression &node) { if (!pathLookupGood) { return; } - } else { - FieldAccessExpression &fieldAccessExpr = *fieldAccess; - fieldAccessExpr.getObject().accept(*this); - if (inUnreachableCode) { - errorHandler->error(fieldAccessExpr.getSlice(), "Unreachable code"); - return; - } - int objectTypeId = fieldAccessExpr.getObject().getTypeID(); - if (objectTypeId == -1) { - return; - } - std::pair impl = module->getImpl(objectTypeId); - if (impl.second == nullptr) { - errorHandler->error(fieldAccessExpr.getSlice(), - "Field access on non-class type"); - return; - } - function - = impl.second->getMethod(fieldAccessExpr.getField().getSymbol().s.contents); - if (function == nullptr) { - errorHandler->error(fieldAccessExpr.getField().getSymbol().s, - "Method " - + std::string(fieldAccessExpr.getField().getSymbol().s.contents) - + " not found in impl for " + std::string(impl.first)); - return; - } } std::vector> parameters; function->forEachParameter( @@ -323,8 +296,55 @@ void SemanticAnalyzer::visit(FieldAccessExpression &node) { } cls.second->getScope().forEachSymbol([this, &node, &cls](std::string_view fieldName, int /*unused*/, SymbolSource /*unused*/) { - if (fieldName == node.getField().getSymbol().s.contents) { - node.setTypeID(cls.second->getScope().getSymbolType(fieldName)); + SymbolExpression *field = dynamic_cast(&node.getField()); + FunctionCallExpression *method + = dynamic_cast(&node.getField()); + if (field == nullptr && method == nullptr) { + errorHandler->error(node.getField().getSlice(), + "Field access is not a symbol or method"); + return; + } + if (field != nullptr) { + if (fieldName == field->getSlice().contents) { + node.setTypeID(cls.second->getScope().getSymbolType(fieldName)); + } + } else { + node.getObject().accept(*this); + if (inUnreachableCode) { + errorHandler->error(node.getSlice(), "Unreachable code"); + return; + } + int objectTypeId = node.getObject().getTypeID(); + if (objectTypeId == -1) { + return; + } + std::pair impl = module->getImpl(objectTypeId); + if (impl.second == nullptr) { + errorHandler->error(node.getSlice(), "Field access on non-class type"); + return; + } + FunctionCallExpression *methodCall + = dynamic_cast(&node.getField()); + if (methodCall == nullptr) { + errorHandler->error(node.getField().getSlice(), + "Field access is not a method"); + return; + } + SymbolExpression *functionName + = dynamic_cast(&methodCall->getFunction()); + if (functionName == nullptr) { + errorHandler->error(methodCall->getFunction().getSlice(), + "Method name is not a symbol"); + return; + } + Function *function + = impl.second->getMethod(functionName->getSymbol().s.contents); + if (function == nullptr) { + errorHandler->error(functionName->getSymbol(), + "Method " + std::string(functionName->getSymbol().s.contents) + + " not found in impl for " + std::string(impl.first)); + return; + } } }); } From fba9f9f1304081347c48bf3fddaa5df08537b458 Mon Sep 17 00:00:00 2001 From: Caleb Eden <58373773+calebeden@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:24:52 -0500 Subject: [PATCH 32/32] refactor(tests): clean up tests for classes --- .../{class_constructor => class_constructor_simple}/main.canyon | 2 +- .../tests/success/semantics/class_field_access/main.canyon | 2 +- .../tests/success/semantics/class_instance_method/main.canyon | 2 +- .../main.canyon | 2 +- .../tests/success/syntax/class_simple/main.canyon | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename test/end_to_end_tests/tests/success/semantics/{class_constructor => class_constructor_simple}/main.canyon (98%) rename test/end_to_end_tests/tests/success/semantics/{class_method_after_constructor => class_method_called_after_constructor}/main.canyon (99%) diff --git a/test/end_to_end_tests/tests/success/semantics/class_constructor/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_constructor_simple/main.canyon similarity index 98% rename from test/end_to_end_tests/tests/success/semantics/class_constructor/main.canyon rename to test/end_to_end_tests/tests/success/semantics/class_constructor_simple/main.canyon index 44b3bf4..9e0e457 100644 --- a/test/end_to_end_tests/tests/success/semantics/class_constructor/main.canyon +++ b/test/end_to_end_tests/tests/success/semantics/class_constructor_simple/main.canyon @@ -6,4 +6,4 @@ class Foo {} impl Foo { constructor new(self: Foo) {} -} \ No newline at end of file +} diff --git a/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon index 3f852c4..5399916 100644 --- a/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon +++ b/test/end_to_end_tests/tests/success/semantics/class_field_access/main.canyon @@ -11,4 +11,4 @@ impl Foo { constructor new(self: Foo) { self.x = 1; } -} \ No newline at end of file +} diff --git a/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon index f0d10f8..7085927 100644 --- a/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon +++ b/test/end_to_end_tests/tests/success/semantics/class_instance_method/main.canyon @@ -15,4 +15,4 @@ impl Foo { fun bar(self: Foo): i32 { self.x } -} \ No newline at end of file +} diff --git a/test/end_to_end_tests/tests/success/semantics/class_method_after_constructor/main.canyon b/test/end_to_end_tests/tests/success/semantics/class_method_called_after_constructor/main.canyon similarity index 99% rename from test/end_to_end_tests/tests/success/semantics/class_method_after_constructor/main.canyon rename to test/end_to_end_tests/tests/success/semantics/class_method_called_after_constructor/main.canyon index f50e225..63d755d 100644 --- a/test/end_to_end_tests/tests/success/semantics/class_method_after_constructor/main.canyon +++ b/test/end_to_end_tests/tests/success/semantics/class_method_called_after_constructor/main.canyon @@ -14,4 +14,4 @@ impl Foo { fun bar(self: Foo): i32 { self.x } -} \ No newline at end of file +} diff --git a/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon b/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon index 85cd13f..b3df0eb 100644 --- a/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon +++ b/test/end_to_end_tests/tests/success/syntax/class_simple/main.canyon @@ -4,4 +4,4 @@ fun main() { class Foo { x: i32; y: i32; -} \ No newline at end of file +}