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; +} diff --git a/src/frontend/ast.h b/src/frontend/ast.h index f81512f..a508dcf 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,14 @@ namespace Winter { } }; + struct funcCallNode { + std::string name; + + [[nodiscard]] std::string display() const { + return std::format("FuncCallNode[ name:{} ]", name); + } + }; + 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..1d50900 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..e7e69ec 100644 --- a/tests/lexer_test.h +++ b/tests/lexer_test.h @@ -41,6 +41,15 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] constexpr int test_token_toChar([[maybe_unused]] Willow::Test* test) noexcept { + 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 { auto L = Lexer(" foo"sv); L.skipWhitespace(); diff --git a/tests/parser_test.h b/tests/parser_test.h index 4527e8e..f57480e 100644 --- a/tests/parser_test.h +++ b/tests/parser_test.h @@ -50,6 +50,45 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] int test_parser_parseArg([[maybe_unused]] Willow::Test* test) noexcept { + 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 { Parser P("{}"sv); P.consume(); @@ -70,6 +109,41 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] int test_parser_parseCallOrVariable([[maybe_unused]] Willow::Test* test) noexcept { + 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 { Parser P("42;"sv); P.consume(); @@ -115,6 +189,37 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] int test_parser_parseFuncCall([[maybe_unused]] Willow::Test* test) noexcept { + 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 { Parser P("let main = func() int { return 0; }"sv); P.consume(); @@ -170,6 +275,17 @@ using namespace std::literals::string_view_literals; return 0; } +[[nodiscard]] int test_parser_parseVariable([[maybe_unused]] Willow::Test* test) noexcept { + 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 { 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}, });