From 033df976a8bdca6ed66b2fd21c1fa29bb2a899f5 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Thu, 7 May 2026 07:39:34 +0000 Subject: [PATCH 1/5] example 2 --- docs/examples/funccall.wtx | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 docs/examples/funccall.wtx diff --git a/docs/examples/funccall.wtx b/docs/examples/funccall.wtx new file mode 100644 index 0000000..2c1b782 --- /dev/null +++ b/docs/examples/funccall.wtx @@ -0,0 +1,4 @@ +let main = func(argc: i32) i32 { + print("hello world"); + return 0; +} From 6e16c8ab71bddd91f6bd6c5519cd2373785bafe3 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Thu, 7 May 2026 11:54:29 +0000 Subject: [PATCH 2/5] Parse a function call --- src/frontend/ast.h | 39 ++++++++++++++++++-- src/frontend/lexer.cpp | 5 ++- src/frontend/lexer.h | 1 + src/frontend/parser.cpp | 79 +++++++++++++++++++++++++++++++++++++++-- src/frontend/parser.h | 4 +++ tests/lexer_test.h | 4 +++ tests/parser_test.h | 16 +++++++++ tests/test.cpp | 5 +++ 8 files changed, 148 insertions(+), 5 deletions(-) diff --git a/src/frontend/ast.h b/src/frontend/ast.h index f81512f..a6d24d3 100644 --- a/src/frontend/ast.h +++ b/src/frontend/ast.h @@ -12,7 +12,9 @@ namespace Winter { enum class NodeType : std::uint8_t { + argNode, bodyNode, + callNode, exprNode, funcNode, letNode, @@ -23,9 +25,11 @@ namespace Winter { error }; + struct argNode; struct bodyNode; struct exprNode; struct funcNode; + struct funcCallNode; struct letNode; struct paramNode; struct numlitNode; @@ -50,8 +54,30 @@ namespace Winter { [[nodiscard]] static _Node tombstone() { return _Node(NodeType::error, TOMBSTONE()); } }; - using Node = - _Node; + using Node = _Node< + argNode, + bodyNode, + exprNode, + letNode, + funcNode, + funcCallNode, + paramNode, + numlitNode, + returnNode, + TOMBSTONE>; + + struct argNode { + std::optional str; + std::optional num; + std::optional ch; + + [[nodiscard]] std::string display() const { + return std::format( + "ArgNode[ str:{} num:{} char:{} ]", str.has_value() ? str.value() : "_", + num.has_value() ? std::format("{}", num.value()) : "_", + ch.has_value() ? std::format("{}", ch.value()) : "_"); + } + }; struct bodyNode { int childCount; @@ -85,6 +111,15 @@ namespace Winter { } }; + struct funcCallNode { + std::string name; + std::vector args; + + [[nodiscard]] std::string display() const { + return std::format("FuncCallNode[ name:{}, params:{} ]", name, args.size()); + } + }; + struct letNode { static const int childCount = 1; std::string name; diff --git a/src/frontend/lexer.cpp b/src/frontend/lexer.cpp index 4145b3b..b79d063 100644 --- a/src/frontend/lexer.cpp +++ b/src/frontend/lexer.cpp @@ -11,7 +11,6 @@ namespace Winter { return (min <= val && val <= max); } - // TODO: unit test [[nodiscard]] std::string Token::toString(const Lexer* L) const noexcept { return std::string(L->src.substr(start, len)); } @@ -20,6 +19,10 @@ namespace Winter { return std::stoi(std::string(L->src.substr(start, len))); } + [[nodiscard]] char Token::toChar(const Lexer* L) const noexcept { + return L->src.at(start); + } + void Lexer::skipWhitespace() noexcept { static constexpr std::array whitespace = {' ', '\n', '\t'}; while (playhead < src.size()) { diff --git a/src/frontend/lexer.h b/src/frontend/lexer.h index f296873..eb9156e 100644 --- a/src/frontend/lexer.h +++ b/src/frontend/lexer.h @@ -87,6 +87,7 @@ namespace Winter { [[nodiscard]] std::string toString(const Lexer* L) const noexcept; [[nodiscard]] int toNum(const Lexer* L) const noexcept; + [[nodiscard]] char toChar(const Lexer* L) const noexcept; }; using namespace std::literals::string_view_literals; diff --git a/src/frontend/parser.cpp b/src/frontend/parser.cpp index ab4edd1..6c44e28 100644 --- a/src/frontend/parser.cpp +++ b/src/frontend/parser.cpp @@ -1,5 +1,6 @@ #include "parser.h" +#include #include #include @@ -28,9 +29,39 @@ namespace Winter { return false; } + [[nodiscard]] Node_Result Parser::parseArg() noexcept { + std::vector valid_types = { + TokenType::num_literal, + TokenType::char_literal, + TokenType::str_literal, + TokenType::ident, + }; + + if (std::none_of(valid_types.begin(), valid_types.end(), [this](const TokenType& t) { + return check(t); + })) { + return std::unexpected(Error(ErrType::Parser, "Unexpected token: invalid arg token")); + } + + if (check(TokenType::str_literal) || check(TokenType::ident)) { + return Node( + NodeType::argNode, argNode(current.toString(&L), std::nullopt, std::nullopt)); + } + + if (check(TokenType::num_literal)) { + return Node(NodeType::argNode, argNode(std::nullopt, current.toNum(&L), std::nullopt)); + } + + if (check(TokenType::char_literal)) { + return Node(NodeType::argNode, argNode(std::nullopt, std::nullopt, current.toChar(&L))); + } + + return std::unexpected(Error(ErrType::Parser, "Unknown arg type")); + } + [[nodiscard]] Node_Result Parser::parseBody() noexcept { - if (!check({TokenType::lbrace})) { - return std::unexpected(Error(ErrType::Parser, "Unexpected token: expected ident")); + if (!check(TokenType::lbrace)) { + return std::unexpected(Error(ErrType::Parser, "Unexpected token: expected lbrace")); } consume(); @@ -41,7 +72,12 @@ namespace Winter { Node_Result maybe_return = parseReturn(); if (!maybe_return.has_value()) { return std::unexpected(maybe_return.error()); } children.push_back(maybe_return.value()); + continue; + } else if (check(TokenType::ident)) { + Node_Result maybe_return = parseCallOrVariable(); + if (!maybe_return.has_value()) { return std::unexpected(maybe_return.error()); } + children.push_back(maybe_return.value()); continue; } @@ -52,6 +88,16 @@ namespace Winter { return Node(NodeType::bodyNode, bodyNode(static_cast(children.size())), children); } + [[nodiscard]] Node_Result Parser::parseCallOrVariable() noexcept { + if (!check({TokenType::ident})) { + return std::unexpected(Error(ErrType::Parser, "Unexpected token: expected ident")); + } + + consume(); + if (check(TokenType::lparen)) { return parseFuncCall(); } + return parseVariable(); + } + [[nodiscard]] Node_Result Parser::parseExpr(std::size_t min_bp) noexcept { Node lhs = Node::tombstone(); switch (current.type) { @@ -143,6 +189,31 @@ namespace Winter { return Node(NodeType::funcNode, funcNode(parameters, retType), {expected_body.value()}); } + [[nodiscard]] Node_Result Parser::parseFuncCall() noexcept { + if (!check({TokenType::lparen})) { + return std::unexpected(Error(ErrType::Parser, "Unexpected token: expected lparen")); + } + + // NOTE: the function name token is at `prev` + std::string funcName = prev.toString(&L); + std::vector args = {}; + + consume(); + + while (!check(TokenType::rparen)) { + auto arg = parseArg(); + if (!arg.has_value()) { return std::unexpected(arg.error()); } + + args.push_back(arg.value()); + // Move forward to next token. If it's a comma, move ahead again + if (consume({TokenType::comma})) { consume(); } + } + + consume(); // consume rparen + consume(); // consume semicolon + return Node(NodeType::callNode, funcCallNode(funcName, args)); + } + [[nodiscard]] Node_Result Parser::parseLet() noexcept { if (!check(TokenType::kw_let)) { return std::unexpected(Error(ErrType::Parser, "Unexpected token: expected kw_let")); @@ -223,6 +294,10 @@ namespace Winter { return Node(NodeType::returnNode, returnNode(), {expr.value()}); } + [[nodiscard]] Node_Result Parser::parseVariable() noexcept { + return std::unexpected(Error(ErrType::NotImplemented, "parseVariable")); + } + [[nodiscard]] std::expected, Error> Parser::operator()() { std::vector code = {}; diff --git a/src/frontend/parser.h b/src/frontend/parser.h index b7e078f..bacc121 100644 --- a/src/frontend/parser.h +++ b/src/frontend/parser.h @@ -34,13 +34,17 @@ namespace Winter { void consume() noexcept; [[nodiscard]] bool consume(std::initializer_list tokens) noexcept; + [[nodiscard]] Node_Result parseArg() noexcept; [[nodiscard]] Node_Result parseBody() noexcept; + [[nodiscard]] Node_Result parseCallOrVariable() noexcept; [[nodiscard]] Node_Result parseExpr(std::size_t) noexcept; [[nodiscard]] Node_Result parseFunc() noexcept; + [[nodiscard]] Node_Result parseFuncCall() noexcept; [[nodiscard]] Node_Result parseLet() noexcept; [[nodiscard]] Node_Result parseNumLit() noexcept; [[nodiscard]] Node_Result parseParam() noexcept; [[nodiscard]] Node_Result parseReturn() noexcept; + [[nodiscard]] Node_Result parseVariable() noexcept; [[nodiscard]] std::expected, Error> operator()(); void display_syntax_tree(const std::vector&) const noexcept; diff --git a/tests/lexer_test.h b/tests/lexer_test.h index fe51e59..64bc983 100644 --- a/tests/lexer_test.h +++ b/tests/lexer_test.h @@ -41,6 +41,10 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] constexpr int test_token_toChar([[maybe_unused]] Willow::Test* test) noexcept { + return 1; +} + [[nodiscard]] constexpr int test_skipWhitespace([[maybe_unused]] Willow::Test* test) noexcept { auto L = Lexer(" foo"sv); L.skipWhitespace(); diff --git a/tests/parser_test.h b/tests/parser_test.h index 4527e8e..a0ebf65 100644 --- a/tests/parser_test.h +++ b/tests/parser_test.h @@ -50,6 +50,10 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] int test_parser_parseArg([[maybe_unused]] Willow::Test* test) noexcept { + return 1; +} + [[nodiscard]] int test_parser_parseBody([[maybe_unused]] Willow::Test* test) noexcept { Parser P("{}"sv); P.consume(); @@ -70,6 +74,10 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] int test_parser_parseCallOrVariable([[maybe_unused]] Willow::Test* test) noexcept { + return 1; +} + [[nodiscard]] int test_parser_parseExpr([[maybe_unused]] Willow::Test* test) noexcept { Parser P("42;"sv); P.consume(); @@ -115,6 +123,10 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] int test_parser_parseFuncCall([[maybe_unused]] Willow::Test* test) noexcept { + return 1; +} + [[nodiscard]] int test_parser_parseLet([[maybe_unused]] Willow::Test* test) noexcept { Parser P("let main = func() int { return 0; }"sv); P.consume(); @@ -170,6 +182,10 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] int test_parser_parseVariable([[maybe_unused]] Willow::Test* test) noexcept { + return 1; +} + [[nodiscard]] int test_parser_operatorCall([[maybe_unused]] Willow::Test* test) noexcept { Parser P("let x = func() int { return 0; }"sv); auto r = P(); diff --git a/tests/test.cpp b/tests/test.cpp index 1077576..a361241 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -12,6 +12,7 @@ int main(int argc, char* argv[]) { {"between", test_between}, {"token_toString", test_token_toString}, {"token_toNum", test_token_toNum}, + {"token_toChar", test_token_toChar}, {"skipWhitespace", test_skipWhitespace}, {"skipComment", test_skipComment}, {"isNumeric", test_isNumeric}, @@ -28,13 +29,17 @@ int main(int argc, char* argv[]) { {"parserCheck", test_parser_check}, {"parserConsumeVoid", test_parser_consume_void}, {"parserConsumeTokens", test_parser_consume_tokens}, + {"parserParseArg", test_parser_parseArg}, {"parserParseBody", test_parser_parseBody}, + {"parserParseCallOrVariable", test_parser_parseCallOrVariable}, {"parserParseExpr", test_parser_parseExpr}, {"parserParseFunc", test_parser_parseFunc}, + {"parserParseFuncCall", test_parser_parseFuncCall}, {"parserParseLet", test_parser_parseLet}, {"parserParseNumLit", test_parser_parseNumLit}, {"parserParseParam", test_parser_parseParam}, {"parserParseReturn", test_parser_parseReturn}, + {"parserParseVariable", test_parser_parseVariable}, {"parserOperatorCall", test_parser_operatorCall}, }); From 12260676ec161885a0d1ad41f857706255629539 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Thu, 7 May 2026 11:57:00 +0000 Subject: [PATCH 3/5] args are children of func calls --- src/frontend/ast.h | 3 +-- src/frontend/parser.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/frontend/ast.h b/src/frontend/ast.h index a6d24d3..a508dcf 100644 --- a/src/frontend/ast.h +++ b/src/frontend/ast.h @@ -113,10 +113,9 @@ namespace Winter { struct funcCallNode { std::string name; - std::vector args; [[nodiscard]] std::string display() const { - return std::format("FuncCallNode[ name:{}, params:{} ]", name, args.size()); + return std::format("FuncCallNode[ name:{} ]", name); } }; diff --git a/src/frontend/parser.cpp b/src/frontend/parser.cpp index 6c44e28..1d50900 100644 --- a/src/frontend/parser.cpp +++ b/src/frontend/parser.cpp @@ -211,7 +211,7 @@ namespace Winter { consume(); // consume rparen consume(); // consume semicolon - return Node(NodeType::callNode, funcCallNode(funcName, args)); + return Node(NodeType::callNode, funcCallNode(funcName), args); } [[nodiscard]] Node_Result Parser::parseLet() noexcept { From af3b619b50267205891c0bc3df79accbd8bce06a Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Thu, 7 May 2026 12:02:21 +0000 Subject: [PATCH 4/5] toChar test --- tests/lexer_test.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/lexer_test.h b/tests/lexer_test.h index 64bc983..e7e69ec 100644 --- a/tests/lexer_test.h +++ b/tests/lexer_test.h @@ -42,7 +42,12 @@ using namespace std::literals::string_view_literals; } [[nodiscard]] constexpr int test_token_toChar([[maybe_unused]] Willow::Test* test) noexcept { - return 1; + const std::string str = "'c'"; + Lexer L = Lexer(str); + const Token t = Token(TokenType::char_literal, 1, 1); + + if (t.toChar(&L) != 'c') { return 1; } + return 0; } [[nodiscard]] constexpr int test_skipWhitespace([[maybe_unused]] Willow::Test* test) noexcept { From 83b8197416d9cc0beb69d341f80267a2b7b4c6e2 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Thu, 7 May 2026 16:15:33 +0000 Subject: [PATCH 5/5] tests passing --- tests/parser_test.h | 108 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/tests/parser_test.h b/tests/parser_test.h index a0ebf65..f57480e 100644 --- a/tests/parser_test.h +++ b/tests/parser_test.h @@ -51,7 +51,42 @@ using namespace std::literals::string_view_literals; } [[nodiscard]] int test_parser_parseArg([[maybe_unused]] Willow::Test* test) noexcept { - return 1; + Parser P("42"sv); + P.consume(); + auto r = P.parseArg(); + if (!r.has_value()) { return 1; } + const auto* an = std::get_if(&r.value().data); + if (an == nullptr || !an->num.has_value() || an->num.value() != 42 || an->str.has_value() || + an->ch.has_value()) { + return 2; + } + if (!P.check(TokenType::num_literal)) { return 3; } + + Parser P2("count"sv); + P2.consume(); + auto r2 = P2.parseArg(); + if (!r2.has_value()) { return 4; } + const auto* an2 = std::get_if(&r2.value().data); + if (an2 == nullptr || !an2->str.has_value() || an2->str.value() != "count" || + an2->num.has_value() || an2->ch.has_value()) { + return 5; + } + + Parser P3("\"hi\""sv); + P3.consume(); + auto r3 = P3.parseArg(); + if (!r3.has_value()) { return 6; } + const auto* an3 = std::get_if(&r3.value().data); + if (an3 == nullptr || !an3->str.has_value() || an3->str.value() != "\"hi\"" || + an3->num.has_value() || an3->ch.has_value()) { + return 7; + } + + Parser P4("@"sv); + P4.consume(); + if (P4.parseArg().has_value()) { return 8; } + + return 0; } [[nodiscard]] int test_parser_parseBody([[maybe_unused]] Willow::Test* test) noexcept { @@ -75,7 +110,38 @@ using namespace std::literals::string_view_literals; } [[nodiscard]] int test_parser_parseCallOrVariable([[maybe_unused]] Willow::Test* test) noexcept { - return 1; + Parser P("foo();"sv); + P.consume(); + auto r = P.parseCallOrVariable(); + if (!r.has_value()) { return 1; } + if (r.value().type != NodeType::callNode) { return 2; } + const auto* cn = std::get_if(&r.value().data); + if (cn == nullptr || cn->name != "foo" || r.value().children.size() != 0) { return 3; } + + Parser P2("foo(1, 2);"sv); + P2.consume(); + auto r2 = P2.parseCallOrVariable(); + if (!r2.has_value()) { return 4; } + const auto* cn2 = std::get_if(&r2.value().data); + if (cn2 == nullptr || cn2->name != "foo" || r2.value().children.size() != 2) { return 5; } + const auto* a0 = std::get_if(&r2.value().children[0].data); + const auto* a1 = std::get_if(&r2.value().children[1].data); + if (a0 == nullptr || a1 == nullptr || !a0->num.has_value() || a0->num.value() != 1 || + !a1->num.has_value() || a1->num.value() != 2) { + return 6; + } + + Parser P3("x"sv); + P3.consume(); + auto r3 = P3.parseCallOrVariable(); + if (r3.has_value()) { return 7; } + if (r3.error().type != ErrType::NotImplemented) { return 8; } + + Parser P4("123"sv); + P4.consume(); + if (P4.parseCallOrVariable().has_value()) { return 9; } + + return 0; } [[nodiscard]] int test_parser_parseExpr([[maybe_unused]] Willow::Test* test) noexcept { @@ -124,7 +190,34 @@ using namespace std::literals::string_view_literals; } [[nodiscard]] int test_parser_parseFuncCall([[maybe_unused]] Willow::Test* test) noexcept { - return 1; + Parser P("bar();"sv); + P.consume(); + P.consume(); + auto r = P.parseFuncCall(); + if (!r.has_value()) { return 1; } + if (r.value().type != NodeType::callNode) { return 2; } + const auto* cn = std::get_if(&r.value().data); + if (cn == nullptr || cn->name != "bar" || r.value().children.size() != 0) { return 3; } + + Parser P2("quux(9, n);"sv); + P2.consume(); + P2.consume(); + auto r2 = P2.parseFuncCall(); + if (!r2.has_value()) { return 4; } + const auto* cn2 = std::get_if(&r2.value().data); + if (cn2 == nullptr || cn2->name != "quux" || r2.value().children.size() != 2) { return 5; } + const auto* argNum = std::get_if(&r2.value().children[0].data); + const auto* argIdent = std::get_if(&r2.value().children[1].data); + if (argNum == nullptr || argIdent == nullptr || !argNum->num.has_value() || + argNum->num.value() != 9 || !argIdent->str.has_value() || argIdent->str.value() != "n") { + return 6; + } + + Parser P3("y);"sv); + P3.consume(); + if (P3.parseFuncCall().has_value()) { return 7; } + + return 0; } [[nodiscard]] int test_parser_parseLet([[maybe_unused]] Willow::Test* test) noexcept { @@ -183,7 +276,14 @@ using namespace std::literals::string_view_literals; } [[nodiscard]] int test_parser_parseVariable([[maybe_unused]] Willow::Test* test) noexcept { - return 1; + Parser P("y"sv); + P.consume(); + auto r = P.parseVariable(); + if (r.has_value()) { return 1; } + if (r.error().type != ErrType::NotImplemented) { return 2; } + if (r.error().msg != "parseVariable") { return 3; } + + return 0; } [[nodiscard]] int test_parser_operatorCall([[maybe_unused]] Willow::Test* test) noexcept {