blob: 9862c811dc7e252ad2d5a477a6d2eea50c9e9f7f [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is the filter language parser. Given a filter specification string, the goal is to construct
// an object that represents the filter described by the specification.
//
// The parser is composed of a public interface, described in this file, and internal syntax and
// state implemented in the other parser files. The current design is that the parser is coupled
// with the lexer, as the knowledge about reserved keywords in the filter language must be shared
// between them. However, the parser is agnostic to the concrete form the filter object takes.
// In the classes relating to the parser, the template argument `T` is the type of the filter object
// that will be constructed.
//
// Key to the public parser interface is the `FilterBuilder<T>` class, declared in its own file. Due
// to template classes, the body of parser code is organized as a series of header files.
#pragma once
#include <ostream>
#include <sstream>
#include <variant>
#include "parser_internal.h"
namespace netdump::parser {
// ANSI escape sequences used for highlighting.
constexpr auto ANSI_HIGHLIGHT = "\033[1;30;43m";
constexpr auto ANSI_HIGHLIGHT_ERROR = "\033[4;31;43m";
constexpr auto ANSI_RESET = "\033[0m";
using ParseError = std::string;
// Clients access the parsing functionality through this class.
class Parser {
public:
// Specifies the `Tokenizer` to use for parsing.
explicit Parser(const Tokenizer& tkz)
: tkz_(tkz) {}
// Attempt a parse of `filter_spec` and return a filter of type `T` built with `builder`.
// If the input is successfully parsed, a `T` (variant index 0) is returned.
// If parsing is unsuccessful, an `Error` (index 1) is returned that explains the syntax error.
template <class T>
std::variant<T, ParseError> parse(const std::string& filter_spec,
FilterBuilder<T>* builder) {
Environment env(tkz_.tokenize(filter_spec));
if (env.at_end()) {
return error_result<T>("Filter string expected.\n");
}
std::optional<T> filter = Syntax<T>().parse(false, tkz_, &env, builder);
if (filter != std::nullopt) {
// Parse success, return the filter.
return filter_result(*filter);
}
// On parse error, build the error message.
std::string msg = highlight_error(filter_spec, &env);
return error_result<T>(msg + "\nFilter syntax error: " + env.error_cause + "\n");
}
Parser(const Parser&) = delete;
Parser& operator=(const Parser&) = delete;
protected: // Protected instead of private for testing.
const Tokenizer& tkz_;
// Insert some ANSI escape characters to highlight the syntax error in console.
// If the error is at the end location, highlight this by reproducing `filter_spec` and
// appending an error marker. Otherwise, the the tokens in `env` are reproduced, and the
// location where the error occurred is highlighted.
std::string highlight_error(const std::string& filter_spec, Environment* env) {
if (!env->error_loc.has_value()) {
return filter_spec;
}
std::stringstream msg;
auto error_loc = *(env->error_loc);
if (error_loc == env->end()) {
// The mistake is at the end of the input.
msg << filter_spec << ANSI_HIGHLIGHT_ERROR << "*" << ANSI_RESET;
return msg.str();
}
// Go to the mistake and highlight it.
env->reset();
while (!env->at_end()) {
if (error_loc == env->cur()) {
msg << ANSI_HIGHLIGHT_ERROR << (**env)->get_term() << ANSI_RESET;
} else {
msg << (**env)->get_term();
}
++(*env);
if (!env->at_end()) {
msg << " ";
}
}
return msg.str();
}
template <class T>
inline std::variant<T, ParseError> filter_result(T filter) {
return std::variant<T, ParseError>(std::in_place_index_t<0>(), filter);
}
template <class T>
inline std::variant<T, ParseError> error_result(std::string msg) {
return std::variant<T, ParseError>(std::in_place_index_t<1>(), std::move(msg));
}
};
// Write a human-readable description of the parser syntax to `output`,
// such as for display on screen. No significant logic should be performed.
void parser_syntax(std::ostream output);
} // namespace netdump::parser