Files
MapLibreParticleLab/include/mbgl/style/expression/parsing_context.hpp
Tim Sylvester 705bdfb3de add noexcepts (#2119)
Co-authored-by: Bart Louwers <bart@emeel.net>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bart Louwers <bart.louwers@gmail.com>
2024-04-08 15:39:18 +00:00

185 lines
6.3 KiB
C++

#pragma once
#include <mbgl/util/string.hpp>
#include <mbgl/style/expression/type.hpp>
#include <mbgl/style/conversion.hpp>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <optional>
namespace mbgl {
namespace style {
namespace expression {
class Expression;
struct ParsingError {
std::string message;
std::string key;
bool operator==(const ParsingError& rhs) const { return message == rhs.message && key == rhs.key; }
};
using ParseResult = std::optional<std::unique_ptr<Expression>>;
namespace detail {
class Scope {
public:
Scope(const std::map<std::string, std::shared_ptr<Expression>>& bindings_, std::shared_ptr<Scope> parent_ = nullptr)
: bindings(bindings_),
parent(std::move(parent_)) {}
const std::map<std::string, std::shared_ptr<Expression>>& bindings;
std::shared_ptr<Scope> parent;
std::optional<std::shared_ptr<Expression>> get(const std::string& name) {
auto it = bindings.find(name);
if (it != bindings.end()) {
return {it->second};
} else if (parent) {
return parent->get(name);
} else {
return std::optional<std::shared_ptr<Expression>>();
}
}
};
} // namespace detail
/**
Controls the annotation behavior of the parser when encountering an
expression whose type is not a subtype of the expected type. The default
behavior, used when optional<TypeAnnotationOption> is a nullopt, is as
follows:
When we expect a number, string, boolean, or array but have a value, wrap it
in an assertion. When we expect a color or formatted string, but have a
string or value, wrap it in a coercion. Otherwise, we do static
type-checking.
These behaviors are overridable for:
* The "coalesce" operator, which needs to omit type annotations.
* String-valued properties (e.g. `text-field`), where coercion is more
convenient than assertion.
*/
enum class TypeAnnotationOption {
coerce,
assert,
omit
};
class ParsingContext {
public:
ParsingContext()
: errors(std::make_shared<std::vector<ParsingError>>()) {}
ParsingContext(std::string key_)
: key(std::move(key_)),
errors(std::make_shared<std::vector<ParsingError>>()) {}
explicit ParsingContext(type::Type expected_)
: expected(std::move(expected_)),
errors(std::make_shared<std::vector<ParsingError>>()) {}
ParsingContext(ParsingContext&&) = default;
ParsingContext(const ParsingContext&) = delete;
ParsingContext& operator=(const ParsingContext&) = delete;
std::string getKey() const { return key; }
std::optional<type::Type> getExpected() const { return expected; }
const std::vector<ParsingError>& getErrors() const noexcept { return *errors; }
std::string getCombinedErrors() const;
/**
Parse the given style-spec JSON value as an expression.
*/
ParseResult parseExpression(const mbgl::style::conversion::Convertible& value,
const std::optional<TypeAnnotationOption>& = std::nullopt);
/**
Parse the given style-spec JSON value as an expression intended to be
used in a layout or paint property. This entails checking additional
constraints that exist in that context but not, e.g., for filters.
*/
ParseResult parseLayerPropertyExpression(const mbgl::style::conversion::Convertible& value);
/**
Parse a child expression. For use by individual Expression::parse() methods.
*/
ParseResult parse(const mbgl::style::conversion::Convertible&,
std::size_t,
std::optional<type::Type> = std::nullopt,
const std::optional<TypeAnnotationOption>& = std::nullopt);
/**
Parse a child expression. For use by individual Expression::parse() methods.
*/
ParseResult parse(const mbgl::style::conversion::Convertible&,
std::size_t index,
std::optional<type::Type>,
const std::map<std::string, std::shared_ptr<Expression>>&);
/**
Check whether `t` is a subtype of `expected`, collecting an error if not.
*/
std::optional<std::string> checkType(const type::Type& t);
std::optional<std::shared_ptr<Expression>> getBinding(const std::string& name) {
if (!scope) return std::optional<std::shared_ptr<Expression>>();
return scope->get(name);
}
void error(std::string message) { errors->push_back({std::move(message), key}); }
void error(std::string message, std::size_t child) {
errors->push_back({std::move(message), key + "[" + util::toString(child) + "]"});
}
void error(std::string message, std::size_t child, std::size_t grandchild) {
errors->push_back(
{std::move(message), key + "[" + util::toString(child) + "][" + util::toString(grandchild) + "]"});
}
void appendErrors(ParsingContext&& ctx) {
errors->reserve(errors->size() + ctx.errors->size());
std::move(ctx.errors->begin(), ctx.errors->end(), std::inserter(*errors, errors->end()));
ctx.errors->clear();
}
void clearErrors() noexcept { errors->clear(); }
private:
ParsingContext(std::string key_,
std::shared_ptr<std::vector<ParsingError>> errors_,
std::optional<type::Type> expected_,
std::shared_ptr<detail::Scope> scope_)
: key(std::move(key_)),
expected(std::move(expected_)),
scope(std::move(scope_)),
errors(std::move(errors_)) {}
/**
Parse the given style-spec JSON value into an Expression object.
Specifically, this function is responsible for determining the expression
type (either Literal, or the one named in value[0]) and dispatching to the
appropriate ParseXxxx::parse(const V&, ParsingContext) method.
*/
ParseResult parse(const mbgl::style::conversion::Convertible& value,
const std::optional<TypeAnnotationOption>& = std::nullopt);
std::string key;
std::optional<type::Type> expected;
std::shared_ptr<detail::Scope> scope;
std::shared_ptr<std::vector<ParsingError>> errors;
};
bool isExpression(const std::string&) noexcept;
} // namespace expression
} // namespace style
} // namespace mbgl