Add unary expression parsing

Signed-off-by: CHatingPython <chatingpython@gmail.com>
This commit is contained in:
CHatingPython
2026-05-29 20:09:09 +02:00
committed by CHatingPython
parent 86988ffea5
commit f178b09f89
8 changed files with 286 additions and 69 deletions
+37 -1
View File
@@ -9,7 +9,8 @@ namespace ast {
enum class expression_node_t { enum class expression_node_t {
Literal, Literal,
Binop Unaryop,
Binop,
}; };
class expression_node : public statement_node { class expression_node : public statement_node {
@@ -25,6 +26,37 @@ protected:
using expression_node_h = node_handle<expression_node>; using expression_node_h = node_handle<expression_node>;
enum class unaryop_expression_node_t {
Positive,
Negative,
PrefixIncrement,
PostfixIncrement,
PrefixDecrement,
PostfixDecrement,
};
class unaryop_expression_node : public expression_node {
public:
unaryop_expression_node(unaryop_expression_node_t type, expression_node_h&& node)
: m_type(type), m_node(std::move(node)) {}
void set_node(expression_node_h&& node) { m_node = std::move(node); }
unaryop_expression_node_t type() const { return m_type; }
const expression_node_h& get_node() const { return m_node; }
expression_node_h& get_node() { return m_node; }
expression_node_h&& move_node() { return std::move(m_node); }
public:
expression_node_t expression_type() const override { return expression_node_t::Unaryop; }
std::ostream& print(std::ostream& os) const override;
private:
unaryop_expression_node_t m_type;
expression_node_h m_node;
};
using unaryop_expression_node_h = node_handle<unaryop_expression_node>;
enum class binop_expression_node_t { enum class binop_expression_node_t {
Add, Add,
Sub, Sub,
@@ -40,7 +72,11 @@ public:
binop_expression_node_t type() const { return m_type; }; binop_expression_node_t type() const { return m_type; };
const expression_node_h& lhs() const { return m_lhs; }; const expression_node_h& lhs() const { return m_lhs; };
expression_node_h& lhs() { return m_lhs; };
expression_node_h&& move_lhs() { return std::move(m_lhs); };
const expression_node_h& rhs() const { return m_rhs; }; const expression_node_h& rhs() const { return m_rhs; };
expression_node_h& rhs() { return m_rhs; };
expression_node_h&& move_rhs() { return std::move(m_rhs); };
public: public:
expression_node_t expression_type() const override { return expression_node_t::Binop; } expression_node_t expression_type() const override { return expression_node_t::Binop; }
+2 -1
View File
@@ -33,7 +33,8 @@ private:
ast::literal_node_h parse_literal(); ast::literal_node_h parse_literal();
ast::expression_node_h parse_expression_primary(); ast::expression_node_h parse_expression_primary();
ast::expression_node_h parse_expression_rhs(const ast::expression_node_h& init, std::uint32_t precedence); ast::expression_node_h parse_expression_unary(std::uint32_t precedence);
ast::expression_node_h parse_expression_rhs(ast::expression_node_h&& init, std::uint32_t precedence);
ast::function_body_h parse_body(); ast::function_body_h parse_body();
private: private:
+9 -1
View File
@@ -32,6 +32,8 @@ enum class token_t {
Star, Star,
Slash, Slash,
Percent, Percent,
DPlus,
DMinus,
}; };
static inline std::ostream& operator<<(std::ostream& os, token_t type) { static inline std::ostream& operator<<(std::ostream& os, token_t type) {
@@ -56,6 +58,8 @@ static inline std::ostream& operator<<(std::ostream& os, token_t type) {
case token_t::Star: return os << "'*'"; case token_t::Star: return os << "'*'";
case token_t::Slash: return os << "'/'"; case token_t::Slash: return os << "'/'";
case token_t::Percent: return os << "'%'"; case token_t::Percent: return os << "'%'";
case token_t::DPlus: return os << "++";
case token_t::DMinus: return os << "--";
} }
return os; return os;
} }
@@ -82,6 +86,8 @@ static inline std::string operator+(const std::string& str, token_t type) {
case token_t::Star: return str + "'*'"; case token_t::Star: return str + "'*'";
case token_t::Slash: return str + "'/'"; case token_t::Slash: return str + "'/'";
case token_t::Percent: return str + "'%'"; case token_t::Percent: return str + "'%'";
case token_t::DPlus: return str + "++";
case token_t::DMinus: return str + "--";
} }
return str; return str;
} }
@@ -167,7 +173,9 @@ struct token {
case token_t::Minus: case token_t::Minus:
case token_t::Star: case token_t::Star:
case token_t::Slash: case token_t::Slash:
case token_t::Percent: return true; case token_t::Percent:
case token_t::DPlus:
case token_t::DMinus: return true;
} }
} }
}; };
+28 -4
View File
@@ -14,7 +14,7 @@ bool literal_node::equal(const node& rhs) const {
std::ostream& integer_literal_node::print(std::ostream& os) const { std::ostream& integer_literal_node::print(std::ostream& os) const {
if (m_value.has_error()) return os << m_value.error(); if (m_value.has_error()) return os << m_value.error();
return os << "integer literal (" << *m_value << ")"; return os << *m_value;
} }
bool integer_literal_node::equal(const node& rhs) const { bool integer_literal_node::equal(const node& rhs) const {
@@ -23,7 +23,7 @@ bool integer_literal_node::equal(const node& rhs) const {
std::ostream& string_literal_node::print(std::ostream& os) const { std::ostream& string_literal_node::print(std::ostream& os) const {
if (m_value.has_error()) return os << m_value.error(); if (m_value.has_error()) return os << m_value.error();
return os << "string literal (" << *m_value << ")"; return os << '"' << *m_value << '"';
} }
bool string_literal_node::equal(const node& rhs) const { bool string_literal_node::equal(const node& rhs) const {
@@ -34,6 +34,30 @@ bool expression_node::equal(const node& rhs) const {
return expression_type() == reinterpret_cast<const expression_node&>(rhs).expression_type(); return expression_type() == reinterpret_cast<const expression_node&>(rhs).expression_type();
} }
std::ostream& operator<<(std::ostream& os, unaryop_expression_node_t type) {
switch (type) {
case unaryop_expression_node_t::Positive: return os << "+";
case unaryop_expression_node_t::Negative: return os << "-";
case unaryop_expression_node_t::PrefixIncrement:
case unaryop_expression_node_t::PostfixIncrement: return os << "++";
case unaryop_expression_node_t::PrefixDecrement:
case unaryop_expression_node_t::PostfixDecrement: return os << "--";
}
return os;
}
std::ostream& unaryop_expression_node::print(std::ostream& os) const {
switch (m_type) {
case unaryop_expression_node_t::Positive:
case unaryop_expression_node_t::Negative:
case unaryop_expression_node_t::PrefixIncrement:
case unaryop_expression_node_t::PrefixDecrement: return os << '(' << m_type << *m_node << ')';
case unaryop_expression_node_t::PostfixIncrement:
case unaryop_expression_node_t::PostfixDecrement: return os << '(' << *m_node << m_type << ')';
}
return os;
}
std::ostream& operator<<(std::ostream& os, binop_expression_node_t type) { std::ostream& operator<<(std::ostream& os, binop_expression_node_t type) {
switch (type) { switch (type) {
case binop_expression_node_t::Add: return os << '+'; case binop_expression_node_t::Add: return os << '+';
@@ -45,7 +69,7 @@ std::ostream& operator<<(std::ostream& os, binop_expression_node_t type) {
} }
std::ostream& binop_expression_node::print(std::ostream& os) const { std::ostream& binop_expression_node::print(std::ostream& os) const {
return os << *m_lhs << ' ' << m_type << ' ' << *m_rhs; return os << '(' << *m_lhs << ' ' << m_type << ' ' << *m_rhs << ')';
} }
bool binop_expression_node::equal(const node& rhsNode) const { bool binop_expression_node::equal(const node& rhsNode) const {
@@ -87,7 +111,7 @@ bool statement_node::equal(const node& rhs) const {
std::ostream& return_statement_node::print(std::ostream& os) const { std::ostream& return_statement_node::print(std::ostream& os) const {
os << "return statement"; os << "return statement";
if (m_value.present()) return os << " (" << *m_value << ')'; if (m_value.present()) return os << ' ' << *m_value;
return os; return os;
} }
+45 -20
View File
@@ -2,6 +2,7 @@
#include <cctype> #include <cctype>
#include <limits> #include <limits>
#include <map>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -45,23 +46,7 @@ token_handle<> lexer::next_token() {
location location = current_location(); location location = current_location();
char ch = get(); switch (get()) {
switch (ch) {
case '(': ++m_cursor; return { location, token_t::Lparen };
case ')': ++m_cursor; return { location, token_t::Rparen };
case '{': ++m_cursor; return { location, token_t::Lbrace };
case '}': ++m_cursor; return { location, token_t::Rbrace };
case '[': ++m_cursor; return { location, token_t::Lbracket };
case ']': ++m_cursor; return { location, token_t::Rbracket };
case ';': ++m_cursor; return { location, token_t::Semicolon };
case ':': ++m_cursor; return { location, token_t::Colon };
case ',': ++m_cursor; return { location, token_t::Comma };
case '.': ++m_cursor; return { location, token_t::Dot };
case '+': ++m_cursor; return { location, token_t::Plus };
case '-': ++m_cursor; return { location, token_t::Minus };
case '*': ++m_cursor; return { location, token_t::Star };
case '/': ++m_cursor; return { location, token_t::Slash };
case '%': ++m_cursor; return { location, token_t::Percent };
case '"': { case '"': {
std::size_t begin = ++m_cursor; std::size_t begin = ++m_cursor;
while (m_cursor < m_content.size() && m_content[m_cursor] != '"') while (m_cursor < m_content.size() && m_content[m_cursor] != '"')
@@ -73,7 +58,7 @@ token_handle<> lexer::next_token() {
} }
case std::char_traits<char>::eof(): return { location, token_t::None }; case std::char_traits<char>::eof(): return { location, token_t::None };
default: { default: {
if (std::isdigit(ch) != 0) { if (std::isdigit(get()) != 0) {
integer_token integer = 0; integer_token integer = 0;
integer_token max = std::numeric_limits<integer_token>::max(); integer_token max = std::numeric_limits<integer_token>::max();
integer_token upperBound = max / 10; integer_token upperBound = max / 10;
@@ -95,7 +80,7 @@ token_handle<> lexer::next_token() {
return { location, integer }; return { location, integer };
} }
if (std::isalnum(ch) != 0 || ch == '_') { if (std::isalnum(get()) != 0 || get() == '_') {
std::size_t start = m_cursor++; std::size_t start = m_cursor++;
while (std::isalnum(get()) != 0 || get() == '_') while (std::isalnum(get()) != 0 || get() == '_')
next(); next();
@@ -111,7 +96,47 @@ token_handle<> lexer::next_token() {
return { location, token_t::Identifier, value }; return { location, token_t::Identifier, value };
} }
return { location, "unexpected character '"s.append(m_content.substr(m_cursor, 1)) + "'" }; struct compare {
bool operator()(const std::string_view& lhs, const std::string_view& rhs) const {
if (lhs.size() != rhs.size()) return lhs.size() > rhs.size();
return lhs < rhs;
}
};
static std::map<std::string_view, token_t, compare> s_tokens = {
{ "(", token_t::Lparen },
{ ")", token_t::Rparen },
{ "{", token_t::Lbrace },
{ "}", token_t::Rbrace },
{ "[", token_t::Lbracket },
{ "]", token_t::Rbracket },
{ ";", token_t::Semicolon },
{ ":", token_t::Colon },
{ ",", token_t::Comma },
{ ".", token_t::Dot },
{ "+", token_t::Plus },
{ "-", token_t::Minus },
{ "*", token_t::Star },
{ "/", token_t::Slash },
{ "%", token_t::Percent },
{ "++", token_t::DPlus },
{ "--", token_t::DMinus },
};
token_t type = token_t::None;
std::size_t length = 1;
while (m_cursor + length <= m_content.size()) {
auto it = s_tokens.find(m_content.substr(m_cursor, length));
if (it == s_tokens.end()) break;
type = it->second;
++length;
}
if (type != token_t::None) {
m_cursor += length - 1;
return { location, type };
}
return { location, "unexpected character '"s.append(m_content.substr(m_cursor, length)) + "'" };
} }
} }
} }
+117 -41
View File
@@ -29,7 +29,7 @@ parser::parser(std::string_view filename)
ast::program_node_h parser::parse() & { ast::program_node_h parser::parse() & {
auto program = m_arena.allocate_shared<ast::program_node>(); auto program = m_arena.allocate_shared<ast::program_node>();
while (peek_token()->type != token_t::None && !m_lexer.empty()) { while (peek_token().present() && peek_token()->type != token_t::None && !m_lexer.empty()) {
program->push(std::move(parse_declaration())); program->push(std::move(parse_declaration()));
} }
@@ -119,7 +119,7 @@ ast::statement_node_h parser::parse_statement() {
} }
ast::expression_node_h parser::parse_expression() { ast::expression_node_h parser::parse_expression() {
return parse_expression_rhs(parse_expression_primary(), 16); return parse_expression_rhs(parse_expression_unary(16), 16);
} }
ast::literal_node_h parser::parse_literal() { ast::literal_node_h parser::parse_literal() {
@@ -143,25 +143,6 @@ ast::literal_node_h parser::parse_literal() {
return { tok.location(), "unexpected token "s + tok->type + ", expected literal" }; return { tok.location(), "unexpected token "s + tok->type + ", expected literal" };
} }
enum class binop_associativity {
Left,
Right,
};
struct binop_info {
ast::binop_expression_node_t type;
std::uint32_t precedence;
binop_associativity associativity;
static binop_info left(ast::binop_expression_node_t type, std::uint32_t precedence) {
return { type, precedence, binop_associativity::Left };
}
static binop_info right(ast::binop_expression_node_t type, std::uint32_t precedence) {
return { type, precedence, binop_associativity::Right };
}
};
ast::expression_node_h parser::parse_expression_primary() { ast::expression_node_h parser::parse_expression_primary() {
const auto& tok = peek_token(); const auto& tok = peek_token();
@@ -170,37 +151,132 @@ ast::expression_node_h parser::parse_expression_primary() {
return { tok.location(), "unexpected token"s + tok->type + ", expected expression or literal" }; return { tok.location(), "unexpected token"s + tok->type + ", expected expression or literal" };
} }
ast::expression_node_h parser::parse_expression_rhs(const ast::expression_node_h& init, std::uint32_t precedence) { struct unaryop_info {
static std::unordered_map<token_t, binop_info> s_binops = { ast::unaryop_expression_node_t type;
{ token_t::Plus, binop_info::left(ast::binop_expression_node_t::Add, 5) }, std::uint32_t precedence;
{ token_t::Minus, binop_info::left(ast::binop_expression_node_t::Sub, 5) },
{ token_t::Star, binop_info::left(ast::binop_expression_node_t::Mul, 4) },
{ token_t::Slash, binop_info::left(ast::binop_expression_node_t::Div, 4) },
{ token_t::Percent, binop_info::left(ast::binop_expression_node_t::Mod, 5) },
}; };
ast::expression_node_h lhs = init; ast::expression_node_h parser::parse_expression_unary(std::uint32_t precedence) {
static std::unordered_map<token_t, unaryop_info> s_prefixes = {
{ token_t::Plus, unaryop_info{ ast::unaryop_expression_node_t::Positive, 3 } },
{ token_t::Minus, unaryop_info{ ast::unaryop_expression_node_t::Negative, 3 } },
{ token_t::DPlus, unaryop_info{ ast::unaryop_expression_node_t::PrefixIncrement, 2 } },
{ token_t::DMinus, unaryop_info{ ast::unaryop_expression_node_t::PrefixDecrement, 2 } },
};
ast::unaryop_expression_node_h result;
while (true) { while (true) {
auto it = s_binops.find(peek_token()->type); auto it = s_prefixes.find(peek_token()->type);
if (it == s_binops.end()) return lhs; if (it == s_prefixes.end()) break;
binop_info current = it->second; auto current = it->second;
if (current.precedence >= precedence) break;
auto token = next_token();
ast::expression_node_h expression;
auto nextIt = s_prefixes.find(peek_token()->type);
if (nextIt != s_prefixes.end()) {
auto next = nextIt->second;
expression = parse_expression_unary(current.precedence + 1);
}
result = { token.location(), m_arena, current.type, std::move(expression) };
}
if (!result.present()) return parse_expression_primary();
if (!result->get_node().present()) result->set_node(parse_expression_primary());
return result;
}
enum class associativity {
Left,
Right,
};
enum class rhsop_info_t {
Unaryop,
Binop,
};
struct rhsop_info {
rhsop_info_t type;
std::uint32_t precedence;
associativity associativity;
union {
ast::unaryop_expression_node_t unary;
ast::binop_expression_node_t binary;
};
static rhsop_info create(ast::unaryop_expression_node_t type, std::uint32_t precedence) {
rhsop_info info{};
info.type = rhsop_info_t::Unaryop;
info.precedence = precedence;
info.associativity = associativity::Left;
info.unary = type;
return info;
}
static rhsop_info create(ast::binop_expression_node_t type,
std::uint32_t precedence,
enum associativity associativity) {
rhsop_info info{};
info.type = rhsop_info_t::Binop;
info.precedence = precedence;
info.associativity = associativity;
info.binary = type;
return info;
}
};
ast::expression_node_h parser::parse_expression_rhs(ast::expression_node_h&& init, std::uint32_t precedence) {
static std::unordered_map<token_t, rhsop_info> s_rhsops = {
{ token_t::Plus, rhsop_info::create(ast::binop_expression_node_t::Add, 5, associativity::Left) },
{ token_t::Minus, rhsop_info::create(ast::binop_expression_node_t::Sub, 5, associativity::Left) },
{ token_t::Star, rhsop_info::create(ast::binop_expression_node_t::Mul, 4, associativity::Left) },
{ token_t::Slash, rhsop_info::create(ast::binop_expression_node_t::Div, 4, associativity::Left) },
{ token_t::Percent, rhsop_info::create(ast::binop_expression_node_t::Mod, 5, associativity::Left) },
{ token_t::DPlus, rhsop_info::create(ast::unaryop_expression_node_t::PostfixIncrement, 1) },
{ token_t::DMinus, rhsop_info::create(ast::unaryop_expression_node_t::PostfixDecrement, 1) },
};
ast::expression_node_h lhs = std::move(init);
while (peek_token().present()) {
auto it = s_rhsops.find(peek_token()->type);
if (it == s_rhsops.end()) return lhs;
rhsop_info current = it->second;
if (current.precedence >= precedence) return lhs; if (current.precedence >= precedence) return lhs;
auto opToken = next_token(); auto opToken = next_token();
auto rhs = parse_expression_primary(); ast::expression_node_h rhs;
if (current.type == rhsop_info_t::Binop) {
auto nextIt = s_binops.find(peek_token()->type); rhs = parse_expression_unary(current.precedence + 1); // unary prefix is always right-associative
if (nextIt != s_binops.end()) {
binop_info next = nextIt->second;
rhs = std::move(parse_expression_rhs(rhs,
current.precedence + static_cast<std::uint32_t>(current.associativity == binop_associativity::Right)));
} }
lhs = ast::binop_expression_node_h{ opToken.location(), m_arena, current.type, std::move(lhs), std::move(rhs) }; auto nextIt = s_rhsops.find(peek_token()->type);
if (nextIt != s_rhsops.end()) {
rhsop_info next = nextIt->second;
if (current.type == rhsop_info_t::Binop) {
rhs = std::move(parse_expression_rhs(std::move(rhs),
current.precedence + static_cast<std::uint32_t>(current.associativity == associativity::Right)));
} else {
lhs = std::move(parse_expression_rhs(std::move(lhs), current.precedence));
} }
} }
lhs = current.type == rhsop_info_t::Binop
? ast::expression_node_h(ast::binop_expression_node_h{ opToken.location(),
m_arena,
current.binary,
std::move(lhs),
std::move(rhs) })
: ast::expression_node_h(
ast::unaryop_expression_node_h{ opToken.location(), m_arena, current.unary, std::move(lhs) });
}
return lhs;
}
ast::function_body_h parser::parse_body() { ast::function_body_h parser::parse_body() {
ast::function_body body; ast::function_body body;
+1 -1
View File
@@ -5,7 +5,7 @@
#include <iostream> #include <iostream>
int main(void) { int main(void) {
furc::front::parser parser("<TEMP>", "func main() {\n return 7 + 6 * 10; // 130\n}"); furc::front::parser parser("<TEMP>", "func main() {\n return 6 - -7 * 9++;\n}");
std::cout << parser.parse() << '\n'; std::cout << parser.parse() << '\n';
return 0; return 0;
+47
View File
@@ -133,4 +133,51 @@ TEST(Parser, OperatorPrecedence_Complex) {
EXPECT_INTLIT(div->rhs(), 2); EXPECT_INTLIT(div->rhs(), 2);
} }
TEST(Parser, UnaryOperator_Simple) {
parser parser("<TEMP>", "func main() { return -5; }");
auto program = parser.parse();
EXPECT_TRUE(program.present());
EXPECT_EQ(program->declarations().size(), 1);
auto func = program->declarations()[0];
EXPECT_TRUE(func.present());
EXPECT_EQ(func->declaration_type(), declaration_node_t::FunctionDefinition);
function_definition_node_h funcDef = func;
EXPECT_EQ(funcDef->name()->string, "main");
EXPECT_EQ(funcDef->body()->statements.size(), 1);
return_statement_node_h ret = funcDef->body()->statements[0];
auto retVal = ret->value();
EXPECT_TRUE(retVal.present());
EXPECT_EQ(retVal->expression_type(), expression_node_t::Unaryop);
unaryop_expression_node_h neg = retVal;
EXPECT_EQ(neg->type(), unaryop_expression_node_t::Negative);
EXPECT_INTLIT(neg->get_node(), 5);
}
TEST(Parser, UnaryOperator_PrePost) {
parser parser("<TEMP>", "func main() { return --5++; }");
auto program = parser.parse();
EXPECT_TRUE(program.present());
EXPECT_EQ(program->declarations().size(), 1);
auto func = program->declarations()[0];
EXPECT_TRUE(func.present());
EXPECT_EQ(func->declaration_type(), declaration_node_t::FunctionDefinition);
function_definition_node_h funcDef = func;
EXPECT_EQ(funcDef->name()->string, "main");
EXPECT_EQ(funcDef->body()->statements.size(), 1);
return_statement_node_h ret = funcDef->body()->statements[0];
auto retVal = ret->value();
EXPECT_TRUE(retVal.present());
EXPECT_EQ(retVal->expression_type(), expression_node_t::Unaryop);
unaryop_expression_node_h inc = retVal;
EXPECT_EQ(inc->type(), unaryop_expression_node_t::PostfixIncrement);
EXPECT_EQ(inc->get_node()->expression_type(), expression_node_t::Unaryop);
unaryop_expression_node_h dec = inc->get_node();
EXPECT_INTLIT(dec->get_node(), 5);
}
} // namespace } // namespace