Binary operator's parsing

This commit is contained in:
Robert Bendun 2022-05-09 19:42:02 +02:00
parent 655cc57cde
commit f17f3dc49f
3 changed files with 111 additions and 5 deletions

View File

@ -288,16 +288,22 @@ struct Ast
{
// Named constructors of AST structure
static Ast literal(Token);
static Ast binary(Token, Ast lhs, Ast rhs);
enum class Type
{
Literal
Literal, // Compile time known constant like c or 1
Binary // Binary operator application like 1 + 2
};
Type type;
Token token;
std::vector<Ast> arguments;
};
bool operator==(Ast const& lhs, Ast const& rhs);
std::ostream& operator<<(std::ostream& os, Ast const& tree);
struct Parser
{
std::vector<Token> tokens;

View File

@ -30,7 +30,16 @@ Result<Ast> Parser::parse_expression()
Result<Ast> Parser::parse_binary_operator()
{
return parse_literal();
return parse_literal().and_then([&](Ast lhs) -> tl::expected<Ast, Error> {
if (expect(Token::Type::Operator)) {
auto op = consume();
return parse_expression().map([&](Ast rhs) {
return Ast::binary(std::move(op), std::move(lhs), std::move(rhs));
});
} else {
return lhs;
}
});
}
Result<Ast> Parser::parse_literal()
@ -61,7 +70,50 @@ Result<void> Parser::ensure(Token::Type type) const
Ast Ast::literal(Token token)
{
Ast ast;
ast.type = Ast::Type::Literal;
ast.type = Type::Literal;
ast.token = std::move(token);
return ast;
}
Ast Ast::binary(Token token, Ast lhs, Ast rhs)
{
Ast ast;
ast.type = Type::Binary;
ast.token = std::move(token);
ast.arguments.push_back(std::move(lhs));
ast.arguments.push_back(std::move(rhs));
return ast;
}
bool operator==(Ast const& lhs, Ast const& rhs)
{
if (lhs.type != rhs.type) {
return false;
}
switch (lhs.type) {
case Ast::Type::Literal:
return lhs.token.type == rhs.token.type && lhs.token.source == rhs.token.source;
case Ast::Type::Binary:
return lhs.token.type == rhs.token.type
&& lhs.token.source == rhs.token.source
&& lhs.arguments.size() == rhs.arguments.size()
&& std::equal(lhs.arguments.begin(), lhs.arguments.end(), rhs.arguments.begin());
}
unreachable();
}
std::ostream& operator<<(std::ostream& os, Ast const& tree)
{
os << "Ast(" << tree.token.source << ")";
if (!tree.arguments.empty()) {
os << " { ";
for (auto const& arg : tree.arguments) {
os << arg << ' ';
}
os << '}';
}
return os;
}

View File

@ -3,9 +3,57 @@
using namespace boost::ut;
void expect_ast(
std::string_view source,
Ast const& expected,
reflection::source_location sl = reflection::source_location::current())
{
auto result = Parser::parse(source, "test");
expect(result.has_value(), sl) << "code was expect to parse, but had not";
expect(eq(*result, expected)) << "parser yielded unexpected tree";
}
suite parser_test = [] {
"Literal parsing"_test = [] {
auto result = Parser::parse("1", "test");
expect(result.has_value()) << "code was expected to parse, but had not";
expect_ast("1", Ast::literal(Token { Token::Type::Numeric, "1", {} }));
};
"Binary opreator parsing"_test = [] {
expect_ast("1 + 2", Ast::binary(
Token { Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Numeric, "1", {} }),
Ast::literal({ Token::Type::Numeric, "2", {} })
));
expect_ast("100 * 200", Ast::binary(
Token { Token::Type::Operator, "*", {} },
Ast::literal({ Token::Type::Numeric, "100", {} }),
Ast::literal({ Token::Type::Numeric, "200", {} })
));
expect_ast("101 + 202 * 303", Ast::binary(
Token { Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Numeric, "101", {} }),
Ast::binary(
Token { Token::Type::Operator, "*", {} },
Ast::literal({ Token::Type::Numeric, "202", {} }),
Ast::literal({ Token::Type::Numeric, "303", {} })
)
));
};
// This test shouldn't be skipped since language will support precedense.
// It stays here as reminder that this feature should be implemented.
skip / "Binary operator precedense"_test = [] {
expect_ast("101 * 202 + 303", Ast::binary(
Token { Token::Type::Operator, "+", {} },
Ast::binary(
Token { Token::Type::Operator, "*", {} },
Ast::literal({ Token::Type::Numeric, "101", {} }),
Ast::literal({ Token::Type::Numeric, "202", {} })
),
Ast::literal({ Token::Type::Numeric, "303", {} })
));
};
};