initial commit
This commit is contained in:
commit
09e10f6e3b
|
|
@ -0,0 +1 @@
|
|||
.idea
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(rsql_parser)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# RSQL Parser (C++) with SQL WHERE Generator
|
||||
|
||||
This small example demonstrates:
|
||||
- A lexer + parser for a subset of RSQL (supports `;` AND, `,` OR, parentheses).
|
||||
- `=in=` and `=out=` list operators.
|
||||
- An AST implementing `accept()` for the Visitor pattern.
|
||||
- A `SQLBuilderVisitor` that generates an SQL `WHERE` clause.
|
||||
|
||||
Build:
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
Run (example):
|
||||
```bash
|
||||
./rsql_parser "(status=in=(OPEN,CLOSED);priority==HIGH),category=out=(internal,test)"
|
||||
```
|
||||
|
||||
The program prints the AST (evaluator) and the generated SQL WHERE clause.
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef RSQL_PARSER_BINARY_CONDITION_NODE_HPP
|
||||
#define RSQL_PARSER_BINARY_CONDITION_NODE_HPP
|
||||
|
||||
#include "node.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace matador::rsql {
|
||||
enum class binary_operator {
|
||||
EQUALS,
|
||||
NOT_EQUALS,
|
||||
GREATER_THAN,
|
||||
GREATER_THAN_OR_EQUAL,
|
||||
LESS_THAN,
|
||||
LESS_THAN_OR_EQUAL,
|
||||
};
|
||||
|
||||
static const std::map<std::string, binary_operator> binary_operators = {
|
||||
{ "==", binary_operator::EQUALS },
|
||||
{ "!=", binary_operator::NOT_EQUALS },
|
||||
{ ">", binary_operator::GREATER_THAN },
|
||||
{ "=gt*", binary_operator::GREATER_THAN },
|
||||
{ ">=", binary_operator::GREATER_THAN_OR_EQUAL },
|
||||
{ "=ge=", binary_operator::GREATER_THAN_OR_EQUAL },
|
||||
{ "<", binary_operator::LESS_THAN },
|
||||
{ "=lt=", binary_operator::LESS_THAN },
|
||||
{ "<=", binary_operator::LESS_THAN_OR_EQUAL },
|
||||
{ "=le=", binary_operator::LESS_THAN_OR_EQUAL }
|
||||
};
|
||||
|
||||
class node_visitor;
|
||||
class binary_condition_node final : public node {
|
||||
public:
|
||||
binary_condition_node(std::string field, binary_operator op, std::string value);
|
||||
|
||||
void accept(node_visitor& visitor) const override;
|
||||
|
||||
const std::string& field() const;
|
||||
binary_operator operand() const;
|
||||
const std::string& value() const;
|
||||
|
||||
private:
|
||||
std::string field_;
|
||||
binary_operator op_;
|
||||
std::string value_;
|
||||
};
|
||||
|
||||
};
|
||||
#endif //RSQL_PARSER_BINARY_CONDITION_NODE_HPP
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef RSQL_PARSER_COLLECTION_CONDITION_NODE_HPP
|
||||
#define RSQL_PARSER_COLLECTION_CONDITION_NODE_HPP
|
||||
|
||||
#include "node.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace matador::rsql {
|
||||
enum class collection_operator {
|
||||
IN,
|
||||
OUT
|
||||
};
|
||||
|
||||
static const std::map<std::string, collection_operator> collection_operators = {
|
||||
{ "=in=", collection_operator::IN },
|
||||
{ "=out=", collection_operator::OUT }
|
||||
};
|
||||
|
||||
|
||||
class collection_condition_node final : public node {
|
||||
public:
|
||||
collection_condition_node(std::string field, collection_operator op, std::vector<std::string> value);
|
||||
|
||||
void accept(node_visitor& visitor) const override;
|
||||
|
||||
const std::string& field() const;
|
||||
collection_operator operand() const;
|
||||
const std::vector<std::string>& values() const;
|
||||
|
||||
private:
|
||||
std::string field_;
|
||||
collection_operator op_;
|
||||
std::vector<std::string> value_;
|
||||
};
|
||||
}
|
||||
#endif //RSQL_PARSER_COLLECTION_CONDITION_NODE_HPP
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef RSQL_PARSER_LEXER_HPP
|
||||
#define RSQL_PARSER_LEXER_HPP
|
||||
|
||||
#include "token.hpp"
|
||||
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace matador::rsql::lexer {
|
||||
std::vector<token> tokenize(const std::string& input);
|
||||
}
|
||||
#endif //RSQL_PARSER_LEXER_HPP
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef RSQL_PARSER_LOGICAL_NODE_HPP
|
||||
#define RSQL_PARSER_LOGICAL_NODE_HPP
|
||||
|
||||
#include "node.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace matador::rsql {
|
||||
enum class logical_operator {
|
||||
AND,
|
||||
OR,
|
||||
};
|
||||
|
||||
class node_visitor;
|
||||
|
||||
class logical_node final : public node {
|
||||
public:
|
||||
explicit logical_node(logical_operator op);
|
||||
void accept(node_visitor& visitor) const override;
|
||||
|
||||
const std::vector<std::shared_ptr<node>>& children() const;
|
||||
logical_operator operand() const;
|
||||
|
||||
private:
|
||||
friend class parser;
|
||||
logical_operator op_; // ";" for AND, "," for OR
|
||||
std::vector<std::shared_ptr<node>> children_;
|
||||
};
|
||||
|
||||
}
|
||||
#endif //RSQL_PARSER_LOGICAL_NODE_HPP
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef RSQL_PARSER_NODE_HPP
|
||||
#define RSQL_PARSER_NODE_HPP
|
||||
|
||||
namespace matador::rsql {
|
||||
class node_visitor;
|
||||
class node {
|
||||
public:
|
||||
virtual ~node() = default;
|
||||
virtual void accept(node_visitor& visitor) const = 0;
|
||||
};
|
||||
}
|
||||
#endif //RSQL_PARSER_NODE_HPP
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef RSQL_PARSER_NODE_VISITOR_HPP
|
||||
#define RSQL_PARSER_NODE_VISITOR_HPP
|
||||
|
||||
namespace matador::rsql {
|
||||
|
||||
class binary_condition_node;
|
||||
class collection_condition_node;
|
||||
class logical_node;
|
||||
|
||||
class node_visitor {
|
||||
public:
|
||||
virtual ~node_visitor() = default;
|
||||
virtual void visit(const binary_condition_node& node) = 0;
|
||||
virtual void visit(const collection_condition_node& node) = 0;
|
||||
virtual void visit(const logical_node& node) = 0;
|
||||
|
||||
};
|
||||
}
|
||||
#endif //RSQL_PARSER_NODE_VISITOR_HPP
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef RSQL_PARSER_PARSER_HPP
|
||||
#define RSQL_PARSER_PARSER_HPP
|
||||
|
||||
#include "token.hpp"
|
||||
#include "token_type.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace matador::rsql {
|
||||
class node;
|
||||
|
||||
class parser {
|
||||
public:
|
||||
explicit parser(std::vector<token> t);
|
||||
|
||||
std::shared_ptr<node> parse();
|
||||
|
||||
private:
|
||||
// OR level
|
||||
std::shared_ptr<node> parse_or_expression();
|
||||
|
||||
// AND level
|
||||
std::shared_ptr<node> parse_and_expression();
|
||||
|
||||
// Primary: parenthesis or condition
|
||||
std::shared_ptr<node> parse_primary();
|
||||
std::shared_ptr<node> parse_condition();
|
||||
|
||||
bool match(const std::vector<token_type>& types);
|
||||
|
||||
const token& previous() const;
|
||||
|
||||
private:
|
||||
std::vector<token> tokens_;
|
||||
size_t current_size_ = 0;
|
||||
};
|
||||
}
|
||||
#endif //RSQL_PARSER_PARSER_HPP
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef RSQL_PARSER_TOKEN_HPP
|
||||
#define RSQL_PARSER_TOKEN_HPP
|
||||
|
||||
#include "token_type.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace matador::rsql {
|
||||
struct token {
|
||||
token_type type;
|
||||
std::string value;
|
||||
};
|
||||
}
|
||||
#endif //RSQL_PARSER_TOKEN_HPP
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef RSQL_PARSER_TOKEN_TYPE_HPP
|
||||
#define RSQL_PARSER_TOKEN_TYPE_HPP
|
||||
|
||||
namespace matador::rsql {
|
||||
enum class token_type {
|
||||
IDENTIFIER,
|
||||
OPERATOR,
|
||||
VALUE,
|
||||
LOGICAL_AND,
|
||||
LOGICAL_OR,
|
||||
OPEN_PAREN,
|
||||
CLOSE_PAREN,
|
||||
UNKNOWN
|
||||
};
|
||||
}
|
||||
|
||||
#endif //RSQL_PARSER_TOKEN_TYPE_HPP
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
SET(SOURCE
|
||||
../include/matador/rsql/binary_condition_node.hpp
|
||||
../include/matador/rsql/collection_condition_node.hpp
|
||||
../include/matador/rsql/lexer.hpp
|
||||
../include/matador/rsql/logical_node.hpp
|
||||
../include/matador/rsql/node.hpp
|
||||
../include/matador/rsql/node_visitor.hpp
|
||||
../include/matador/rsql/parser.hpp
|
||||
../include/matador/rsql/token.hpp
|
||||
../include/matador/rsql/token_type.hpp
|
||||
rsql/binary_condition_node.cpp
|
||||
rsql/collection_condition_node.cpp
|
||||
rsql/lexer.cpp
|
||||
rsql/logical_node.cpp
|
||||
rsql/parser.cpp
|
||||
)
|
||||
|
||||
add_library(matador-rsql STATIC ${SOURCE})
|
||||
|
||||
target_include_directories(matador-rsql PUBLIC ../include)
|
||||
|
||||
add_executable(rsql_parser main.cpp)
|
||||
target_include_directories(rsql_parser PUBLIC ../include)
|
||||
target_link_libraries(rsql_parser matador-rsql)
|
||||
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
#include "matador/rsql/binary_condition_node.hpp"
|
||||
#include "matador/rsql/collection_condition_node.hpp"
|
||||
#include "matador/rsql/lexer.hpp"
|
||||
#include "matador/rsql/logical_node.hpp"
|
||||
#include "matador/rsql/node_visitor.hpp"
|
||||
#include "matador/rsql/parser.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// ---------------- Evaluator Visitor (prints AST) ----------------
|
||||
class Evaluator final : public matador::rsql::node_visitor {
|
||||
public:
|
||||
void visit(const matador::rsql::binary_condition_node& node) override {
|
||||
std::cout << "Condition: " << node.field() << " " << to_string(node.operand()) << " " << node.value() << "\n";
|
||||
}
|
||||
|
||||
void visit( const matador::rsql::collection_condition_node& node ) override {
|
||||
std::cout << "Condition: " << node.field() << " " << to_string(node.operand()) << " ";
|
||||
std::cout << "(";
|
||||
for (size_t i = 0; i < node.values().size(); ++i) {
|
||||
std::cout << node.values()[i];
|
||||
if (i + 1 < node.values().size()) std::cout << ",";
|
||||
}
|
||||
std::cout << ")\n";
|
||||
}
|
||||
|
||||
void visit(const matador::rsql::logical_node& node) override {
|
||||
std::cout << "Logical (" << (node.operand() == matador::rsql::logical_operator::AND ? "AND" : "OR") << ")\n";
|
||||
for (const auto& child : node.children()) {
|
||||
child->accept(*this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string to_string(const matador::rsql::binary_operator op) {
|
||||
static std::map<matador::rsql::binary_operator, std::string> bin_to_string {
|
||||
{ matador::rsql::binary_operator::EQUALS, "==" },
|
||||
{ matador::rsql::binary_operator::NOT_EQUALS, "!=" },
|
||||
{ matador::rsql::binary_operator::GREATER_THAN, ">" },
|
||||
{ matador::rsql::binary_operator::GREATER_THAN_OR_EQUAL, ">=" },
|
||||
{ matador::rsql::binary_operator::LESS_THAN, "<" },
|
||||
{ matador::rsql::binary_operator::LESS_THAN_OR_EQUAL, "<=" },
|
||||
};
|
||||
|
||||
return bin_to_string.at(op);
|
||||
}
|
||||
|
||||
static std::string to_string(const matador::rsql::collection_operator op) {
|
||||
static std::map<matador::rsql::collection_operator, std::string> col_to_string {
|
||||
{ matador::rsql::collection_operator::IN, "=IN=" },
|
||||
{ matador::rsql::collection_operator::OUT, "=OUT=" }
|
||||
};
|
||||
|
||||
return col_to_string.at(op);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------- SQLBuilderVisitor ----------------
|
||||
class SQLBuilderVisitor final : public matador::rsql::node_visitor {
|
||||
public:
|
||||
std::string result;
|
||||
|
||||
void visit(const matador::rsql::binary_condition_node& node) override {
|
||||
result += "(" + field(node.field()) + " " + binary_op_string(node.operand(), node.value()) + ")";
|
||||
}
|
||||
|
||||
void visit( const matador::rsql::collection_condition_node& node ) override {
|
||||
result += "(" + field(node.field()) + " " + collection_op_string(node.operand(), node.values()) + ")";
|
||||
}
|
||||
|
||||
void visit(const matador::rsql::logical_node& node) override {
|
||||
// Build children SQL and join
|
||||
std::vector<std::string> parts;
|
||||
for (const auto& child : node.children()) {
|
||||
SQLBuilderVisitor sub;
|
||||
child->accept(sub);
|
||||
parts.push_back(sub.result);
|
||||
}
|
||||
const std::string operand = (node.operand() == matador::rsql::logical_operator::AND ? " AND " : " OR ");
|
||||
result += "(";
|
||||
for (size_t i = 0; i < parts.size(); ++i) {
|
||||
result += parts[i];
|
||||
if (i + 1 < parts.size()) {
|
||||
result += operand;
|
||||
}
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string escape_sql(const std::string& s) {
|
||||
std::string out;
|
||||
out.reserve(s.size() + 2);
|
||||
for (const char c : s) {
|
||||
if (c == '\'') out.push_back('\''); // double single-quote
|
||||
out.push_back(c);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool is_number(const std::string& s) {
|
||||
if (s.empty()) {
|
||||
return false;
|
||||
}
|
||||
// allow optional leading '-' and digits, optional decimal point
|
||||
size_t i = 0;
|
||||
if (s[0] == '-') i = 1;
|
||||
bool seenDigit = false;
|
||||
bool seenDot = false;
|
||||
for (; i < s.size(); ++i) {
|
||||
if (std::isdigit(static_cast<unsigned char>(s[i]))) seenDigit = true;
|
||||
else if (s[i] == '.' && !seenDot) seenDot = true;
|
||||
else return false;
|
||||
}
|
||||
return seenDigit;
|
||||
}
|
||||
|
||||
static std::string quote(const std::string& str) {
|
||||
if (is_number(str)) {
|
||||
return str;
|
||||
}
|
||||
return std::string("'") + escape_sql(str) + std::string("'");
|
||||
}
|
||||
|
||||
static std::string field(const std::string& f) {
|
||||
// Todo: add quoting rules
|
||||
return f;
|
||||
}
|
||||
|
||||
static std::string binary_op_string(const matador::rsql::binary_operator op, const std::string& val) {
|
||||
using namespace matador::rsql;
|
||||
switch (op) {
|
||||
case binary_operator::EQUALS:
|
||||
return "= " + quote(val);
|
||||
case binary_operator::NOT_EQUALS:
|
||||
return "<> " + quote(val);
|
||||
case binary_operator::GREATER_THAN:
|
||||
return "> " + quote(val);
|
||||
case binary_operator::GREATER_THAN_OR_EQUAL:
|
||||
return ">= " + quote(val);
|
||||
case binary_operator::LESS_THAN:
|
||||
return "< " + quote(val);
|
||||
case binary_operator::LESS_THAN_OR_EQUAL:
|
||||
return "<= " + quote(val);
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string collection_op_string(const matador::rsql::collection_operator op, const std::vector<std::string>& list) {
|
||||
if (list.empty()) {
|
||||
return (op == matador::rsql::collection_operator::OUT ? "NOT IN (NULL)" : "IN (NULL)");
|
||||
}
|
||||
std::string joined;
|
||||
joined += "(";
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
joined += quote(list[i]);
|
||||
if (i + 1 < list.size()) joined += ", ";
|
||||
}
|
||||
joined += ")";
|
||||
if (op == matador::rsql::collection_operator::OUT) {
|
||||
return "IN " + joined;
|
||||
}
|
||||
return "NOT IN " + joined;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------- Main / usage ----------------
|
||||
int main(const int argc, char** argv) {
|
||||
using namespace matador::rsql;
|
||||
std::string input;
|
||||
if (argc >= 2) {
|
||||
input = argv[1];
|
||||
} else {
|
||||
// example default
|
||||
input = "(status=in=(OPEN,CLOSED);priority==HIGH),category=out=(internal,test)";
|
||||
std::cout << "No query provided, using default example:\n" << input << "\n\n";
|
||||
}
|
||||
|
||||
try {
|
||||
const auto tokens = lexer::tokenize(input);
|
||||
parser p(tokens);
|
||||
const auto ast = p.parse();
|
||||
|
||||
std::cout << "=== AST (Evaluator) ===\n";
|
||||
Evaluator ev;
|
||||
ast->accept(ev);
|
||||
|
||||
std::cout << "\n=== SQL WHERE clause ===\n";
|
||||
SQLBuilderVisitor sql;
|
||||
ast->accept(sql);
|
||||
std::cout << sql.result << "\n";
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "Error: " << ex.what() << "\n";
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#include "matador/rsql/binary_condition_node.hpp"
|
||||
#include "matador/rsql/node_visitor.hpp"
|
||||
|
||||
namespace matador::rsql {
|
||||
binary_condition_node::binary_condition_node(std::string field, const binary_operator op, std::string value)
|
||||
: field_(std::move(field))
|
||||
, op_(op)
|
||||
, value_(std::move(value))
|
||||
{}
|
||||
|
||||
inline void binary_condition_node::accept( node_visitor& visitor ) const {
|
||||
visitor.visit(*this);
|
||||
}
|
||||
|
||||
const std::string& binary_condition_node::field() const {
|
||||
return field_;
|
||||
}
|
||||
|
||||
binary_operator binary_condition_node::operand() const {
|
||||
return op_;
|
||||
}
|
||||
|
||||
const std::string& binary_condition_node::value() const {
|
||||
return value_;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#include "matador/rsql/collection_condition_node.hpp"
|
||||
|
||||
#include "matador/rsql/node_visitor.hpp"
|
||||
|
||||
namespace matador::rsql {
|
||||
collection_condition_node::collection_condition_node(std::string field, const collection_operator op, std::vector<std::string> value)
|
||||
: field_(std::move(field))
|
||||
, op_(op)
|
||||
, value_(std::move(value))
|
||||
{}
|
||||
|
||||
void collection_condition_node::accept(node_visitor& visitor) const {
|
||||
visitor.visit(*this);
|
||||
}
|
||||
|
||||
const std::string& collection_condition_node::field() const {
|
||||
return field_;
|
||||
}
|
||||
|
||||
collection_operator collection_condition_node::operand() const {
|
||||
return op_;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& collection_condition_node::values() const {
|
||||
return value_;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#include "matador/rsql/lexer.hpp"
|
||||
#include "matador/rsql/token_type.hpp"
|
||||
|
||||
namespace matador::rsql::lexer {
|
||||
std::vector<token> tokenize( const std::string& input ) {
|
||||
std::vector<token> tokens;
|
||||
|
||||
// Pattern catches: =in=, =out=, operators, quoted strings, identifiers, parentheses, commas/semicolons
|
||||
std::regex tokenRegex(R"((=in=|=out=|[=!><~]+)|('[^']*'|"[^"]*")|(\()|(\))|(;|,)|([A-Za-z_]\w*)|([^ \t\n\r]+))");
|
||||
auto begin = std::sregex_iterator(input.begin(), input.end(), tokenRegex);
|
||||
auto end = std::sregex_iterator();
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
const std::smatch& m = *it;
|
||||
|
||||
if (std::string match = m.str(); match == ";") {
|
||||
tokens.push_back({token_type::LOGICAL_AND, match});
|
||||
} else if (match == ",") {
|
||||
tokens.push_back({token_type::LOGICAL_OR, match});
|
||||
} else if (match == "(") {
|
||||
tokens.push_back({token_type::OPEN_PAREN, match});
|
||||
} else if (match == ")") {
|
||||
tokens.push_back({token_type::CLOSE_PAREN, match});
|
||||
} else if (std::regex_match(match, std::regex(R"(=in=|=out=|[=!><~]+)"))) {
|
||||
tokens.push_back({token_type::OPERATOR, match});
|
||||
} else if ((match.size() >= 2 && ((match.front() == '\'' && match.back() == '\'') ||
|
||||
(match.front() == '"' && match.back() == '"')))) {
|
||||
tokens.push_back({token_type::VALUE, match.substr(1, match.size() - 2)});
|
||||
} else if (std::regex_match(match, std::regex(R"([A-Za-z_]\w*)"))) {
|
||||
tokens.push_back({token_type::IDENTIFIER, match});
|
||||
} else {
|
||||
tokens.push_back({token_type::VALUE, match});
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#include "matador/rsql/logical_node.hpp"
|
||||
#include "matador/rsql/node_visitor.hpp"
|
||||
|
||||
namespace matador::rsql {
|
||||
|
||||
logical_node::logical_node(const logical_operator op)
|
||||
: op_(op) {}
|
||||
|
||||
void logical_node::accept( node_visitor& visitor ) const {
|
||||
visitor.visit(*this);
|
||||
}
|
||||
|
||||
logical_operator logical_node::operand() const {
|
||||
return op_;
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<node>>& logical_node::children() const {
|
||||
return children_;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
#include "matador/rsql/parser.hpp"
|
||||
|
||||
#include "matador/rsql/binary_condition_node.hpp"
|
||||
#include "matador/rsql/collection_condition_node.hpp"
|
||||
#include "matador/rsql/logical_node.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace matador::rsql {
|
||||
parser::parser( std::vector<token> t )
|
||||
: tokens_(std::move(t)) {}
|
||||
|
||||
std::shared_ptr<node> parser::parse() {
|
||||
if (tokens_.empty()) {
|
||||
throw std::runtime_error("Empty input");
|
||||
}
|
||||
auto res = parse_or_expression();
|
||||
if (current_size_ != tokens_.size()) {
|
||||
throw std::runtime_error("Unexpected token after end: " + tokens_[current_size_].value);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<node> parser::parse_or_expression() {
|
||||
auto node = parse_and_expression();
|
||||
while (match({token_type::LOGICAL_OR})) {
|
||||
std::string op = previous().value;
|
||||
const auto logical = std::make_shared<logical_node>(logical_operator::OR);
|
||||
logical->children_.push_back(node);
|
||||
logical->children_.push_back(parse_and_expression());
|
||||
node = logical;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<node> parser::parse_and_expression() {
|
||||
auto node = parse_primary();
|
||||
while (match({token_type::LOGICAL_AND})) {
|
||||
std::string op = previous().value;
|
||||
const auto logical = std::make_shared<logical_node>(logical_operator::AND);
|
||||
logical->children_.push_back(node);
|
||||
logical->children_.push_back(parse_primary());
|
||||
node = logical;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<node> parser::parse_primary() {
|
||||
if (match({token_type::OPEN_PAREN})) {
|
||||
auto node = parse_or_expression();
|
||||
if (!match({token_type::CLOSE_PAREN})) {
|
||||
throw std::runtime_error("Missing closing parenthesis");
|
||||
}
|
||||
return node;
|
||||
}
|
||||
return parse_condition();
|
||||
}
|
||||
|
||||
std::shared_ptr<node> parser::parse_condition() {
|
||||
if (!match({token_type::IDENTIFIER})) {
|
||||
throw std::runtime_error("Expected field name");
|
||||
}
|
||||
std::string field = previous().value;
|
||||
|
||||
if (!match({token_type::OPERATOR})) {
|
||||
throw std::runtime_error("Expected operator after field");
|
||||
}
|
||||
|
||||
const std::string op = previous().value;
|
||||
if (op == "=in=" || op == "=out=") {
|
||||
if (!match({token_type::OPEN_PAREN})) {
|
||||
throw std::runtime_error("Expected '(' after " + op);
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
bool first = true;
|
||||
while (!match({token_type::CLOSE_PAREN})) {
|
||||
if (!first) {
|
||||
// accept comma as separator: tokens use LOGICAL_OR for comma in lexer
|
||||
if (!match({token_type::LOGICAL_OR})) {
|
||||
throw std::runtime_error("Expected ',' between list values");
|
||||
}
|
||||
}
|
||||
if (!match({token_type::VALUE, token_type::IDENTIFIER})) {
|
||||
throw std::runtime_error("Expected value in list");
|
||||
}
|
||||
values.push_back(previous().value);
|
||||
first = false;
|
||||
}
|
||||
return std::make_shared<collection_condition_node>(field, collection_operators.at(op), values);
|
||||
}
|
||||
|
||||
if (!match({token_type::VALUE, token_type::IDENTIFIER})) {
|
||||
throw std::runtime_error("Expected value after operator");
|
||||
}
|
||||
return std::make_shared<binary_condition_node>(field, binary_operators.at(op), previous().value);
|
||||
}
|
||||
|
||||
bool parser::match( const std::vector<token_type>& types ) {
|
||||
if (current_size_ < tokens_.size() &&
|
||||
std::find(types.begin(), types.end(), tokens_[current_size_].type) != types.end()) {
|
||||
++current_size_;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const token& parser::previous() const {
|
||||
return tokens_[current_size_ - 1];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue