Skip to content

expression_parser.hpp

SECTIONData Structure INCLUDEnoya/ds/expression_parser.hpp

按优先级和结合性递归解析表达式,支持括号与前缀一元运算符;返回拥有完整字符串的语法树或错误位置,不负责求值。

Complexity: Time: \(O(n + o \log o + t o)\) for bounded-length operators, where \(n\) is input length, \(o\) operator-table size and \(t\) token count. Space: \(O(n + o)\), including \(O(t)\) recursive parse depth.

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

/// @complexity Time: O(n + o log o + t o) for bounded-length operators,
/// where n is input length, o operator-table size and t token count.
/// Space: O(n + o), including O(t) recursive parse depth.

#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace noya {

struct expression_binary_operator {
  std::string opr;
  int pri = 0;
  bool ras = false;
};

struct expression_unary_operator {
  std::string opr;
  int pri = 0;
};

struct expression_node {
  enum class kind { operand, unary, binary } tp = kind::operand;
  std::string tok;
  int ls = -1;
  int rs = -1;
};

struct expression_tree {
  std::vector<expression_node> tr;
  int rt = -1;
};

struct expression_parse_error {
  std::size_t ps = 0;
  std::string msg;
};

struct expression_parse_result {
  std::optional<expression_tree> t;
  std::optional<expression_parse_error> err;

  explicit operator bool() const { return t.has_value(); }
};

/// @brief Parse identifiers, integers, parentheses, configurable binary
/// operators, and configurable prefix unary operators into an expression tree.
inline expression_parse_result
parse_expression(std::string_view s,
                 const std::vector<expression_binary_operator> &bop,
                 const std::vector<expression_unary_operator> &uop = {}) {
  struct operation {
    std::string_view spelling;
    const expression_binary_operator *binary = nullptr;
    const expression_unary_operator *unary = nullptr;
  };
  std::vector<operation> ops;
  for (const auto &op : bop) {
    if (op.opr.empty() || op.pri < 0)
      return {{}, expression_parse_error{0, "invalid binary operator table"}};
    ops.push_back({op.opr, &op, nullptr});
  }
  for (const auto &op : uop) {
    if (op.opr.empty() || op.pri < 0)
      return {{}, expression_parse_error{0, "invalid unary operator table"}};
    ops.push_back({op.opr, nullptr, &op});
  }
  // Longest spelling wins; the first declaration of each kind still wins ties.
  std::stable_sort(ops.begin(), ops.end(), [](const auto &a, const auto &b) {
    if (a.spelling.size() != b.spelling.size())
      return a.spelling.size() > b.spelling.size();
    return a.spelling < b.spelling;
  });
  std::size_t used = 0;
  for (auto op : ops) {
    if (used && ops[used - 1].spelling == op.spelling) {
      auto &prev = ops[used - 1];
      if (!prev.binary)
        prev.binary = op.binary;
      if (!prev.unary)
        prev.unary = op.unary;
    } else {
      ops[used++] = op;
    }
  }
  ops.resize(used);

  struct token {
    enum class kind {
      operand,
      operation,
      left_parenthesis,
      right_parenthesis,
      end
    } tp;
    std::string_view text;
    std::size_t pos;
    const operation *op = nullptr;
  };
  std::vector<token> tokens;
  // Tokenize first so an unknown character keeps precedence over parse errors.
  for (std::size_t pos = 0; pos < s.size();) {
    unsigned char ch = s[pos];
    if (std::isspace(ch)) {
      ++pos;
      continue;
    }
    if (ch == '(' || ch == ')') {
      tokens.push_back({ch == '(' ? token::kind::left_parenthesis
                                  : token::kind::right_parenthesis,
                        s.substr(pos, 1), pos});
      ++pos;
      continue;
    }
    if (std::isalnum(ch) || ch == '_') {
      std::size_t end = pos + 1;
      while (end < s.size()) {
        unsigned char next = s[end];
        if (!std::isalnum(next) && next != '_')
          break;
        ++end;
      }
      tokens.push_back({token::kind::operand, s.substr(pos, end - pos), pos});
      pos = end;
      continue;
    }
    auto it = std::find_if(ops.begin(), ops.end(), [&](const auto &op) {
      return s.substr(pos, op.spelling.size()) == op.spelling;
    });
    if (it == ops.end())
      return {{}, expression_parse_error{pos, "unknown token"}};
    tokens.push_back({token::kind::operation, it->spelling, pos, &*it});
    pos += it->spelling.size();
  }
  tokens.push_back({token::kind::end, {}, s.size()});

  expression_tree tree;
  std::size_t cur = 0;
  std::optional<expression_parse_error> error;
  auto fail = [&](const char *message) {
    error = expression_parse_error{tokens[cur].pos, message};
    return -1;
  };
  auto insert = [&](expression_node node) {
    tree.tr.push_back(std::move(node));
    return int(tree.tr.size()) - 1;
  };
  // Parse a prefix/atom, then binary operators with sufficient binding power.
  auto dfs = [&](auto &self, std::uint64_t lo) -> int {
    const auto &tok = tokens[cur];
    int left;
    if (tok.tp == token::kind::operand) {
      left = insert({expression_node::kind::operand, std::string(tok.text), -1, -1});
      ++cur;
    } else if (tok.tp == token::kind::left_parenthesis) {
      ++cur;
      left = self(self, 0);
      if (left == -1) return -1;
      if (tokens[cur].tp != token::kind::right_parenthesis)
        return fail("missing closing parenthesis");
      ++cur;
    } else if (tok.tp == token::kind::operation) {
      if (!tok.op->unary) return fail("expected operand or prefix unary operator");
      ++cur;
      int child = self(self, tok.op->unary->pri);
      if (child == -1) return -1;
      left = insert({expression_node::kind::unary, std::string(tok.text), child, -1});
    } else return fail("expected operand");
    while (tokens[cur].tp == token::kind::operation) {
      const auto &next = tokens[cur];
      const auto *op = next.op->binary;
      if (!op || std::uint64_t(op->pri) < lo) break;
      ++cur;
      // Widen before +1, including when the priority is INT_MAX.
      int right = self(self, std::uint64_t(op->pri) + !op->ras);
      if (right == -1) return -1;
      left = insert({expression_node::kind::binary, std::string(next.text), left, right});
    }
    return left;
  };
  tree.rt = dfs(dfs, 0);
  if (tree.rt == -1) return {{}, error};
  if (tokens[cur].tp != token::kind::end) {
    fail(tokens[cur].tp == token::kind::right_parenthesis
             ? "unmatched closing parenthesis" : "unexpected token after expression");
    return {{}, error};
  }
  return {std::move(tree), {}};
}

} // namespace noya

 // NOYA_EXPRESSION_PARSER_HPP
#ifndef NOYA_EXPRESSION_PARSER_HPP
#define NOYA_EXPRESSION_PARSER_HPP 1

/// @complexity Time: O(n + o log o + t o) for bounded-length operators,
/// where n is input length, o operator-table size and t token count.
/// Space: O(n + o), including O(t) recursive parse depth.

#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace noya {

struct expression_binary_operator {
  std::string opr;
  int pri = 0;
  bool ras = false;
};

struct expression_unary_operator {
  std::string opr;
  int pri = 0;
};

struct expression_node {
  enum class kind { operand, unary, binary } tp = kind::operand;
  std::string tok;
  int ls = -1;
  int rs = -1;
};

struct expression_tree {
  std::vector<expression_node> tr;
  int rt = -1;
};

struct expression_parse_error {
  std::size_t ps = 0;
  std::string msg;
};

struct expression_parse_result {
  std::optional<expression_tree> t;
  std::optional<expression_parse_error> err;

  explicit operator bool() const { return t.has_value(); }
};

/// @brief Parse identifiers, integers, parentheses, configurable binary
/// operators, and configurable prefix unary operators into an expression tree.
inline expression_parse_result
parse_expression(std::string_view s,
                 const std::vector<expression_binary_operator> &bop,
                 const std::vector<expression_unary_operator> &uop = {}) {
  struct operation {
    std::string_view spelling;
    const expression_binary_operator *binary = nullptr;
    const expression_unary_operator *unary = nullptr;
  };
  std::vector<operation> ops;
  for (const auto &op : bop) {
    if (op.opr.empty() || op.pri < 0)
      return {{}, expression_parse_error{0, "invalid binary operator table"}};
    ops.push_back({op.opr, &op, nullptr});
  }
  for (const auto &op : uop) {
    if (op.opr.empty() || op.pri < 0)
      return {{}, expression_parse_error{0, "invalid unary operator table"}};
    ops.push_back({op.opr, nullptr, &op});
  }
  // Longest spelling wins; the first declaration of each kind still wins ties.
  std::stable_sort(ops.begin(), ops.end(), [](const auto &a, const auto &b) {
    if (a.spelling.size() != b.spelling.size())
      return a.spelling.size() > b.spelling.size();
    return a.spelling < b.spelling;
  });
  std::size_t used = 0;
  for (auto op : ops) {
    if (used && ops[used - 1].spelling == op.spelling) {
      auto &prev = ops[used - 1];
      if (!prev.binary)
        prev.binary = op.binary;
      if (!prev.unary)
        prev.unary = op.unary;
    } else {
      ops[used++] = op;
    }
  }
  ops.resize(used);

  struct token {
    enum class kind {
      operand,
      operation,
      left_parenthesis,
      right_parenthesis,
      end
    } tp;
    std::string_view text;
    std::size_t pos;
    const operation *op = nullptr;
  };
  std::vector<token> tokens;
  // Tokenize first so an unknown character keeps precedence over parse errors.
  for (std::size_t pos = 0; pos < s.size();) {
    unsigned char ch = s[pos];
    if (std::isspace(ch)) {
      ++pos;
      continue;
    }
    if (ch == '(' || ch == ')') {
      tokens.push_back({ch == '(' ? token::kind::left_parenthesis
                                  : token::kind::right_parenthesis,
                        s.substr(pos, 1), pos});
      ++pos;
      continue;
    }
    if (std::isalnum(ch) || ch == '_') {
      std::size_t end = pos + 1;
      while (end < s.size()) {
        unsigned char next = s[end];
        if (!std::isalnum(next) && next != '_')
          break;
        ++end;
      }
      tokens.push_back({token::kind::operand, s.substr(pos, end - pos), pos});
      pos = end;
      continue;
    }
    auto it = std::find_if(ops.begin(), ops.end(), [&](const auto &op) {
      return s.substr(pos, op.spelling.size()) == op.spelling;
    });
    if (it == ops.end())
      return {{}, expression_parse_error{pos, "unknown token"}};
    tokens.push_back({token::kind::operation, it->spelling, pos, &*it});
    pos += it->spelling.size();
  }
  tokens.push_back({token::kind::end, {}, s.size()});

  expression_tree tree;
  std::size_t cur = 0;
  std::optional<expression_parse_error> error;
  auto fail = [&](const char *message) {
    error = expression_parse_error{tokens[cur].pos, message};
    return -1;
  };
  auto insert = [&](expression_node node) {
    tree.tr.push_back(std::move(node));
    return int(tree.tr.size()) - 1;
  };
  // Parse a prefix/atom, then binary operators with sufficient binding power.
  auto dfs = [&](auto &self, std::uint64_t lo) -> int {
    const auto &tok = tokens[cur];
    int left;
    if (tok.tp == token::kind::operand) {
      left = insert({expression_node::kind::operand, std::string(tok.text), -1, -1});
      ++cur;
    } else if (tok.tp == token::kind::left_parenthesis) {
      ++cur;
      left = self(self, 0);
      if (left == -1) return -1;
      if (tokens[cur].tp != token::kind::right_parenthesis)
        return fail("missing closing parenthesis");
      ++cur;
    } else if (tok.tp == token::kind::operation) {
      if (!tok.op->unary) return fail("expected operand or prefix unary operator");
      ++cur;
      int child = self(self, tok.op->unary->pri);
      if (child == -1) return -1;
      left = insert({expression_node::kind::unary, std::string(tok.text), child, -1});
    } else return fail("expected operand");
    while (tokens[cur].tp == token::kind::operation) {
      const auto &next = tokens[cur];
      const auto *op = next.op->binary;
      if (!op || std::uint64_t(op->pri) < lo) break;
      ++cur;
      // Widen before +1, including when the priority is INT_MAX.
      int right = self(self, std::uint64_t(op->pri) + !op->ras);
      if (right == -1) return -1;
      left = insert({expression_node::kind::binary, std::string(next.text), left, right});
    }
    return left;
  };
  tree.rt = dfs(dfs, 0);
  if (tree.rt == -1) return {{}, error};
  if (tokens[cur].tp != token::kind::end) {
    fail(tokens[cur].tp == token::kind::right_parenthesis
             ? "unmatched closing parenthesis" : "unexpected token after expression");
    return {{}, error};
  }
  return {std::move(tree), {}};
}

} // namespace noya

#endif // NOYA_EXPRESSION_PARSER_HPP
/// @complexity Time: O(n + o log o + t o) for bounded-length operators,
/// where n is input length, o operator-table size and t token count.
/// Space: O(n + o), including O(t) recursive parse depth.

#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace noya {

struct expression_binary_operator {
  std::string opr;
  int pri = 0;
  bool ras = false;
};

struct expression_unary_operator {
  std::string opr;
  int pri = 0;
};

struct expression_node {
  enum class kind { operand, unary, binary } tp = kind::operand;
  std::string tok;
  int ls = -1;
  int rs = -1;
};

struct expression_tree {
  std::vector<expression_node> tr;
  int rt = -1;
};

struct expression_parse_error {
  std::size_t ps = 0;
  std::string msg;
};

struct expression_parse_result {
  std::optional<expression_tree> t;
  std::optional<expression_parse_error> err;

  explicit operator bool() const { return t.has_value(); }
};

/// @brief Parse identifiers, integers, parentheses, configurable binary
/// operators, and configurable prefix unary operators into an expression tree.
inline expression_parse_result
parse_expression(std::string_view s,
                 const std::vector<expression_binary_operator> &bop,
                 const std::vector<expression_unary_operator> &uop = {}) {
  struct operation {
    std::string_view spelling;
    const expression_binary_operator *binary = nullptr;
    const expression_unary_operator *unary = nullptr;
  };
  std::vector<operation> ops;
  for (const auto &op : bop) {
    if (op.opr.empty() || op.pri < 0)
      return {{}, expression_parse_error{0, "invalid binary operator table"}};
    ops.push_back({op.opr, &op, nullptr});
  }
  for (const auto &op : uop) {
    if (op.opr.empty() || op.pri < 0)
      return {{}, expression_parse_error{0, "invalid unary operator table"}};
    ops.push_back({op.opr, nullptr, &op});
  }
  // Longest spelling wins; the first declaration of each kind still wins ties.
  std::stable_sort(ops.begin(), ops.end(), [](const auto &a, const auto &b) {
    if (a.spelling.size() != b.spelling.size())
      return a.spelling.size() > b.spelling.size();
    return a.spelling < b.spelling;
  });
  std::size_t used = 0;
  for (auto op : ops) {
    if (used && ops[used - 1].spelling == op.spelling) {
      auto &prev = ops[used - 1];
      if (!prev.binary)
        prev.binary = op.binary;
      if (!prev.unary)
        prev.unary = op.unary;
    } else {
      ops[used++] = op;
    }
  }
  ops.resize(used);

  struct token {
    enum class kind {
      operand,
      operation,
      left_parenthesis,
      right_parenthesis,
      end
    } tp;
    std::string_view text;
    std::size_t pos;
    const operation *op = nullptr;
  };
  std::vector<token> tokens;
  // Tokenize first so an unknown character keeps precedence over parse errors.
  for (std::size_t pos = 0; pos < s.size();) {
    unsigned char ch = s[pos];
    if (std::isspace(ch)) {
      ++pos;
      continue;
    }
    if (ch == '(' || ch == ')') {
      tokens.push_back({ch == '(' ? token::kind::left_parenthesis
                                  : token::kind::right_parenthesis,
                        s.substr(pos, 1), pos});
      ++pos;
      continue;
    }
    if (std::isalnum(ch) || ch == '_') {
      std::size_t end = pos + 1;
      while (end < s.size()) {
        unsigned char next = s[end];
        if (!std::isalnum(next) && next != '_')
          break;
        ++end;
      }
      tokens.push_back({token::kind::operand, s.substr(pos, end - pos), pos});
      pos = end;
      continue;
    }
    auto it = std::find_if(ops.begin(), ops.end(), [&](const auto &op) {
      return s.substr(pos, op.spelling.size()) == op.spelling;
    });
    if (it == ops.end())
      return {{}, expression_parse_error{pos, "unknown token"}};
    tokens.push_back({token::kind::operation, it->spelling, pos, &*it});
    pos += it->spelling.size();
  }
  tokens.push_back({token::kind::end, {}, s.size()});

  expression_tree tree;
  std::size_t cur = 0;
  std::optional<expression_parse_error> error;
  auto fail = [&](const char *message) {
    error = expression_parse_error{tokens[cur].pos, message};
    return -1;
  };
  auto insert = [&](expression_node node) {
    tree.tr.push_back(std::move(node));
    return int(tree.tr.size()) - 1;
  };
  // Parse a prefix/atom, then binary operators with sufficient binding power.
  auto dfs = [&](auto &self, std::uint64_t lo) -> int {
    const auto &tok = tokens[cur];
    int left;
    if (tok.tp == token::kind::operand) {
      left = insert({expression_node::kind::operand, std::string(tok.text), -1, -1});
      ++cur;
    } else if (tok.tp == token::kind::left_parenthesis) {
      ++cur;
      left = self(self, 0);
      if (left == -1) return -1;
      if (tokens[cur].tp != token::kind::right_parenthesis)
        return fail("missing closing parenthesis");
      ++cur;
    } else if (tok.tp == token::kind::operation) {
      if (!tok.op->unary) return fail("expected operand or prefix unary operator");
      ++cur;
      int child = self(self, tok.op->unary->pri);
      if (child == -1) return -1;
      left = insert({expression_node::kind::unary, std::string(tok.text), child, -1});
    } else return fail("expected operand");
    while (tokens[cur].tp == token::kind::operation) {
      const auto &next = tokens[cur];
      const auto *op = next.op->binary;
      if (!op || std::uint64_t(op->pri) < lo) break;
      ++cur;
      // Widen before +1, including when the priority is INT_MAX.
      int right = self(self, std::uint64_t(op->pri) + !op->ras);
      if (right == -1) return -1;
      left = insert({expression_node::kind::binary, std::string(next.text), left, right});
    }
    return left;
  };
  tree.rt = dfs(dfs, 0);
  if (tree.rt == -1) return {{}, error};
  if (tokens[cur].tp != token::kind::end) {
    fail(tokens[cur].tp == token::kind::right_parenthesis
             ? "unmatched closing parenthesis" : "unexpected token after expression");
    return {{}, error};
  }
  return {std::move(tree), {}};
}

} // namespace noya

 // NOYA_EXPRESSION_PARSER_HPP