Skip to content

expression_parser.hpp

SECTIONData Structure INCLUDEnoya/expression_parser.hpp

Parse identifiers, integers, parentheses, configurable binary operators, and configurable prefix unary operators into an expression tree.

解析并计算带括号和运算符优先级的表达式;适合题目要求实现算术语法或把字符串转为表达式树。

Implementation

View on GitHub

#ifndef NOYA_EXPRESSION_PARSER_HPP
#define NOYA_EXPRESSION_PARSER_HPP 1

/// @complexity Time: O(t log o) for t tokens and ordered operator tables.
/// Space: O(t) parse stacks/output.

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

namespace noya {

struct expression_binary_operator {
  std::string symbol;
  int precedence = 0;
  bool right_associative = false;
};

struct expression_unary_operator {
  std::string symbol;
  int precedence = 0;
};

struct expression_node {
  enum class kind { operand, unary, binary } type = kind::operand;
  std::string token;
  int left = -1;
  int right = -1;
};

struct expression_tree {
  std::vector<expression_node> nodes;
  int root = -1;
};

struct expression_parse_error {
  std::size_t position = 0;
  std::string message;
};

struct expression_parse_result {
  std::optional<expression_tree> tree;
  std::optional<expression_parse_error> error;

  explicit operator bool() const { return tree.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 expression,
    const std::vector<expression_binary_operator> &binary_operators,
    const std::vector<expression_unary_operator> &unary_operators = {}) {
  struct token {
    enum class kind { operand, operation, left_parenthesis, right_parenthesis,
                      end } type = kind::end;
    std::string text;
    std::size_t position = 0;
  };

  std::vector<std::string> symbols;
  for (const auto &operation : binary_operators) {
    if (operation.symbol.empty() || operation.precedence < 0) {
      return {{}, expression_parse_error{0, "invalid binary operator table"}};
    }
    symbols.push_back(operation.symbol);
  }
  for (const auto &operation : unary_operators) {
    if (operation.symbol.empty() || operation.precedence < 0) {
      return {{}, expression_parse_error{0, "invalid unary operator table"}};
    }
    symbols.push_back(operation.symbol);
  }
  std::sort(symbols.begin(), symbols.end(), [](const auto &first,
                                                const auto &second) {
    if (first.size() != second.size()) {
      return first.size() > second.size();
    }
    return first < second;
  });
  symbols.erase(std::unique(symbols.begin(), symbols.end()), symbols.end());

  std::vector<token> tokens;
  for (std::size_t position = 0; position < expression.size();) {
    unsigned char character = expression[position];
    if (std::isspace(character)) {
      position++;
      continue;
    }
    if (character == '(' || character == ')') {
      tokens.push_back({character == '(' ? token::kind::left_parenthesis
                                         : token::kind::right_parenthesis,
                        std::string(1, char(character)), position});
      position++;
      continue;
    }
    if (std::isalnum(character) || character == '_') {
      std::size_t end = position + 1;
      while (end < expression.size()) {
        unsigned char next = expression[end];
        if (!std::isalnum(next) && next != '_') {
          break;
        }
        end++;
      }
      tokens.push_back({token::kind::operand,
                        std::string(expression.substr(position, end - position)),
                        position});
      position = end;
      continue;
    }
    auto match = std::find_if(symbols.begin(), symbols.end(),
                              [&](const std::string &symbol) {
      return expression.substr(position, symbol.size()) == symbol;
    });
    if (match == symbols.end()) {
      return {{}, expression_parse_error{position, "unknown token"}};
    }
    tokens.push_back({token::kind::operation, *match, position});
    position += match->size();
  }
  tokens.push_back({token::kind::end, {}, expression.size()});

  expression_tree tree;
  std::size_t cursor = 0;
  std::optional<expression_parse_error> error;
  auto fail = [&](std::string message) {
    if (!error) {
      error = expression_parse_error{tokens[cursor].position,
                                     std::move(message)};
    }
    return -1;
  };
  auto binary = [&](const std::string &symbol)
      -> const expression_binary_operator * {
    auto iterator = std::find_if(
        binary_operators.begin(), binary_operators.end(),
        [&](const auto &operation) { return operation.symbol == symbol; });
    return iterator == binary_operators.end() ? nullptr : &*iterator;
  };
  auto unary = [&](const std::string &symbol)
      -> const expression_unary_operator * {
    auto iterator = std::find_if(
        unary_operators.begin(), unary_operators.end(),
        [&](const auto &operation) { return operation.symbol == symbol; });
    return iterator == unary_operators.end() ? nullptr : &*iterator;
  };
  auto make_node = [&](expression_node node) {
    tree.nodes.push_back(std::move(node));
    return int(tree.nodes.size()) - 1;
  };

  auto parse = [&](auto &self, int minimum_precedence) -> int {
    int left = -1;
    if (tokens[cursor].type == token::kind::operand) {
      left = make_node(
          {expression_node::kind::operand, tokens[cursor].text, -1, -1});
      cursor++;
    } else if (tokens[cursor].type == token::kind::left_parenthesis) {
      cursor++;
      left = self(self, 0);
      if (left == -1) {
        return -1;
      }
      if (tokens[cursor].type != token::kind::right_parenthesis) {
        return fail("missing closing parenthesis");
      }
      cursor++;
    } else if (tokens[cursor].type == token::kind::operation) {
      const auto *operation = unary(tokens[cursor].text);
      if (operation == nullptr) {
        return fail("expected operand or prefix unary operator");
      }
      std::string symbol = tokens[cursor++].text;
      int child = self(self, operation->precedence);
      if (child == -1) {
        return -1;
      }
      left = make_node(
          {expression_node::kind::unary, std::move(symbol), child, -1});
    } else {
      return fail("expected operand");
    }

    while (tokens[cursor].type == token::kind::operation) {
      const auto *operation = binary(tokens[cursor].text);
      if (operation == nullptr || operation->precedence < minimum_precedence) {
        break;
      }
      std::string symbol = tokens[cursor++].text;
      int next_precedence =
          operation->precedence + (operation->right_associative ? 0 : 1);
      int right = self(self, next_precedence);
      if (right == -1) {
        return -1;
      }
      left = make_node({expression_node::kind::binary, std::move(symbol), left,
                        right});
    }
    return left;
  };

  tree.root = parse(parse, 0);
  if (tree.root == -1) {
    return {{}, error};
  }
  if (tokens[cursor].type != token::kind::end) {
    fail(tokens[cursor].type == token::kind::right_parenthesis
             ? "unmatched closing parenthesis"
             : "unexpected token after expression");
    return {{}, error};
  }
  return {std::move(tree), {}};
}

} // namespace noya

#endif // NOYA_EXPRESSION_PARSER_HPP
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

/// @complexity Time: O(t log o) for t tokens and ordered operator tables.
/// Space: O(t) parse stacks/output.

namespace noya {

struct expression_binary_operator {
  std::string symbol;
  int precedence = 0;
  bool right_associative = false;
};

struct expression_unary_operator {
  std::string symbol;
  int precedence = 0;
};

struct expression_node {
  enum class kind { operand, unary, binary } type = kind::operand;
  std::string token;
  int left = -1;
  int right = -1;
};

struct expression_tree {
  std::vector<expression_node> nodes;
  int root = -1;
};

struct expression_parse_error {
  std::size_t position = 0;
  std::string message;
};

struct expression_parse_result {
  std::optional<expression_tree> tree;
  std::optional<expression_parse_error> error;

  explicit operator bool() const { return tree.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 expression,
    const std::vector<expression_binary_operator> &binary_operators,
    const std::vector<expression_unary_operator> &unary_operators = {}) {
  struct token {
    enum class kind { operand, operation, left_parenthesis, right_parenthesis,
                      end } type = kind::end;
    std::string text;
    std::size_t position = 0;
  };

  std::vector<std::string> symbols;
  for (const auto &operation : binary_operators) {
    if (operation.symbol.empty() || operation.precedence < 0) {
      return {{}, expression_parse_error{0, "invalid binary operator table"}};
    }
    symbols.push_back(operation.symbol);
  }
  for (const auto &operation : unary_operators) {
    if (operation.symbol.empty() || operation.precedence < 0) {
      return {{}, expression_parse_error{0, "invalid unary operator table"}};
    }
    symbols.push_back(operation.symbol);
  }
  std::sort(symbols.begin(), symbols.end(), [](const auto &first,
                                                const auto &second) {
    if (first.size() != second.size()) {
      return first.size() > second.size();
    }
    return first < second;
  });
  symbols.erase(std::unique(symbols.begin(), symbols.end()), symbols.end());

  std::vector<token> tokens;
  for (std::size_t position = 0; position < expression.size();) {
    unsigned char character = expression[position];
    if (std::isspace(character)) {
      position++;
      continue;
    }
    if (character == '(' || character == ')') {
      tokens.push_back({character == '(' ? token::kind::left_parenthesis
                                         : token::kind::right_parenthesis,
                        std::string(1, char(character)), position});
      position++;
      continue;
    }
    if (std::isalnum(character) || character == '_') {
      std::size_t end = position + 1;
      while (end < expression.size()) {
        unsigned char next = expression[end];
        if (!std::isalnum(next) && next != '_') {
          break;
        }
        end++;
      }
      tokens.push_back({token::kind::operand,
                        std::string(expression.substr(position, end - position)),
                        position});
      position = end;
      continue;
    }
    auto match = std::find_if(symbols.begin(), symbols.end(),
                              [&](const std::string &symbol) {
      return expression.substr(position, symbol.size()) == symbol;
    });
    if (match == symbols.end()) {
      return {{}, expression_parse_error{position, "unknown token"}};
    }
    tokens.push_back({token::kind::operation, *match, position});
    position += match->size();
  }
  tokens.push_back({token::kind::end, {}, expression.size()});

  expression_tree tree;
  std::size_t cursor = 0;
  std::optional<expression_parse_error> error;
  auto fail = [&](std::string message) {
    if (!error) {
      error = expression_parse_error{tokens[cursor].position,
                                     std::move(message)};
    }
    return -1;
  };
  auto binary = [&](const std::string &symbol)
      -> const expression_binary_operator * {
    auto iterator = std::find_if(
        binary_operators.begin(), binary_operators.end(),
        [&](const auto &operation) { return operation.symbol == symbol; });
    return iterator == binary_operators.end() ? nullptr : &*iterator;
  };
  auto unary = [&](const std::string &symbol)
      -> const expression_unary_operator * {
    auto iterator = std::find_if(
        unary_operators.begin(), unary_operators.end(),
        [&](const auto &operation) { return operation.symbol == symbol; });
    return iterator == unary_operators.end() ? nullptr : &*iterator;
  };
  auto make_node = [&](expression_node node) {
    tree.nodes.push_back(std::move(node));
    return int(tree.nodes.size()) - 1;
  };

  auto parse = [&](auto &self, int minimum_precedence) -> int {
    int left = -1;
    if (tokens[cursor].type == token::kind::operand) {
      left = make_node(
          {expression_node::kind::operand, tokens[cursor].text, -1, -1});
      cursor++;
    } else if (tokens[cursor].type == token::kind::left_parenthesis) {
      cursor++;
      left = self(self, 0);
      if (left == -1) {
        return -1;
      }
      if (tokens[cursor].type != token::kind::right_parenthesis) {
        return fail("missing closing parenthesis");
      }
      cursor++;
    } else if (tokens[cursor].type == token::kind::operation) {
      const auto *operation = unary(tokens[cursor].text);
      if (operation == nullptr) {
        return fail("expected operand or prefix unary operator");
      }
      std::string symbol = tokens[cursor++].text;
      int child = self(self, operation->precedence);
      if (child == -1) {
        return -1;
      }
      left = make_node(
          {expression_node::kind::unary, std::move(symbol), child, -1});
    } else {
      return fail("expected operand");
    }

    while (tokens[cursor].type == token::kind::operation) {
      const auto *operation = binary(tokens[cursor].text);
      if (operation == nullptr || operation->precedence < minimum_precedence) {
        break;
      }
      std::string symbol = tokens[cursor++].text;
      int next_precedence =
          operation->precedence + (operation->right_associative ? 0 : 1);
      int right = self(self, next_precedence);
      if (right == -1) {
        return -1;
      }
      left = make_node({expression_node::kind::binary, std::move(symbol), left,
                        right});
    }
    return left;
  };

  tree.root = parse(parse, 0);
  if (tree.root == -1) {
    return {{}, error};
  }
  if (tokens[cursor].type != token::kind::end) {
    fail(tokens[cursor].type == token::kind::right_parenthesis
             ? "unmatched closing parenthesis"
             : "unexpected token after expression");
    return {{}, error};
  }
  return {std::move(tree), {}};
}

} // namespace noya