Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/examples/funccall.wtx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let main = func(argc: i32) i32 {
print("hello world");
return 0;
}
38 changes: 36 additions & 2 deletions src/frontend/ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

namespace Winter {
enum class NodeType : std::uint8_t {
argNode,
bodyNode,
callNode,
exprNode,
funcNode,
letNode,
Expand All @@ -23,9 +25,11 @@ namespace Winter {
error
};

struct argNode;
struct bodyNode;
struct exprNode;
struct funcNode;
struct funcCallNode;
struct letNode;
struct paramNode;
struct numlitNode;
Expand All @@ -50,8 +54,30 @@ namespace Winter {
[[nodiscard]] static _Node tombstone() { return _Node(NodeType::error, TOMBSTONE()); }
};

using Node =
_Node<bodyNode, exprNode, letNode, funcNode, paramNode, numlitNode, returnNode, TOMBSTONE>;
using Node = _Node<
argNode,
bodyNode,
exprNode,
letNode,
funcNode,
funcCallNode,
paramNode,
numlitNode,
returnNode,
TOMBSTONE>;

struct argNode {
std::optional<std::string> str;
std::optional<int> num;
std::optional<char> 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;
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion src/frontend/lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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<char, 3> whitespace = {' ', '\n', '\t'};
while (playhead < src.size()) {
Expand Down
1 change: 1 addition & 0 deletions src/frontend/lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
79 changes: 77 additions & 2 deletions src/frontend/parser.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "parser.h"

#include <algorithm>
#include <format>
#include <print>

Expand Down Expand Up @@ -28,9 +29,39 @@ namespace Winter {
return false;
}

[[nodiscard]] Node_Result Parser::parseArg() noexcept {
std::vector<TokenType> 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();
Expand All @@ -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;
}

Expand All @@ -52,6 +88,16 @@ namespace Winter {
return Node(NodeType::bodyNode, bodyNode(static_cast<int>(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) {
Expand Down Expand Up @@ -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<Node> 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"));
Expand Down Expand Up @@ -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<std::vector<Node>, Error> Parser::operator()() {
std::vector<Node> code = {};

Expand Down
4 changes: 4 additions & 0 deletions src/frontend/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ namespace Winter {
void consume() noexcept;
[[nodiscard]] bool consume(std::initializer_list<TokenType> 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<std::vector<Node>, Error> operator()();
void display_syntax_tree(const std::vector<Node>&) const noexcept;
Expand Down
9 changes: 9 additions & 0 deletions tests/lexer_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading
Loading