diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 80f1f7b0..e847d892 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -35,8 +35,10 @@ jobs: run: | mkdir coverage gcovr --html-details coverage/coverage.html \ + --exclude-lines-by-pattern 'TRY_EXPECTED' \ --filter src/ --filter include/ gcovr --cobertura coverage.xml \ + --exclude-lines-by-pattern 'TRY_EXPECTED' \ --html coverage.html \ --filter src/ --filter include/ less coverage.xml @@ -63,7 +65,7 @@ jobs: with: filename: coverage.xml badge: false - fail_below_min: true + fail_below_min: false format: markdown hide_branch_rate: false hide_complexity: true diff --git a/README.md b/README.md index 8e08c4eb..66be7190 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,6 @@ The query language can be extended by additional functions and addititonal types ## Diagnostics Simfil can output query diagnostics messages that can include a fixed query string. -Currently, it supports the following types of messages: -- "No matches for field '...'. Did you mean '...'?" ## Using the Library ### CMake FetchContent diff --git a/examples/minimal/main.cpp b/examples/minimal/main.cpp index 23da1331..75d4c464 100644 --- a/examples/minimal/main.cpp +++ b/examples/minimal/main.cpp @@ -32,7 +32,13 @@ int main() } // Evalualte query and get result of type simfil::Value. - auto result = simfil::eval(env, **ast, *model->root(0), nullptr); + auto root = model->root(0); + if (!root) { + std::cerr << root.error().message << '\n'; + return -1; + } + + auto result = simfil::eval(env, **ast, **root, nullptr); if (!result) { std::cerr << result.error().message << '\n'; return -1; diff --git a/include/simfil/environment.h b/include/simfil/environment.h index 1c1e7878..2a83b39a 100644 --- a/include/simfil/environment.h +++ b/include/simfil/environment.h @@ -175,8 +175,8 @@ struct Debug */ struct CompletionOptions { - // Auto insert a wildcard if the first token is a field name. - bool autoWildcard = false; + // Show hints about completing certain queries to a wildcard query. + bool showWildcardHints = true; // Limit of candidates. size_t limit = 15; @@ -200,6 +200,7 @@ struct CompletionCandidate CONSTANT = 1, FIELD = 2, FUNCTION = 3, + HINT = 4, }; std::string text; // Text to insert diff --git a/include/simfil/error.h b/include/simfil/error.h index 6dd372f6..46b1070d 100644 --- a/include/simfil/error.h +++ b/include/simfil/error.h @@ -12,12 +12,32 @@ struct Token; struct Error { enum Type { + // Parser Errors ParserError, InvalidType, InvalidExpression, ExpectedEOF, NullModel, IOError, + + // Evaluation errors + DivisionByZero, + UnknownFunction, + RuntimeError, + InternalError, + InvalidOperator, + InvalidOperands, + InvalidArguments, + ExpectedSingleValue, + TypeMissmatch, + + // Model related runtime errors + StringPoolOverflow, + EncodeDecodeError, + FieldNotFound, + IndexOutOfRange, + + Unimplemented, }; explicit Error(Type type); @@ -25,6 +45,8 @@ struct Error Error(Type type, std::string message, SourceLocation location); Error(Type type, std::string message, const Token& token); + auto operator==(const Error& o) const -> bool = default; + Type type; SourceLocation location; std::string message; diff --git a/include/simfil/exception-handler.h b/include/simfil/exception-handler.h index bde4f525..79c4244c 100644 --- a/include/simfil/exception-handler.h +++ b/include/simfil/exception-handler.h @@ -46,6 +46,7 @@ class ThrowHandler template [[noreturn]] void raise(Args&&... args) { + // GCOVR_EXCL_START ExceptionType exceptionInstance(std::forward(args)...); if (auto const& excHandler = ThrowHandler::instance().get()) { @@ -55,6 +56,7 @@ template errorMessage = exceptionInstance.what(); excHandler(typeName, errorMessage); } + // GCOVR_EXCL_STOP throw std::move(exceptionInstance); } diff --git a/include/simfil/expression.h b/include/simfil/expression.h index a2298055..01fb0ad0 100644 --- a/include/simfil/expression.h +++ b/include/simfil/expression.h @@ -50,17 +50,35 @@ class Expr /* Debug */ virtual auto toString() const -> std::string = 0; - /* Evaluation wrapper */ - auto eval(Context ctx, Value val, const ResultFn& res) -> Result + auto eval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { if (ctx.canceled()) return Result::Stop; - auto dbg = ctx.env->debug; - if (dbg) dbg->evalBegin(*this, ctx, val, res); - auto r = ieval(ctx, val, res); - if (dbg) dbg->evalEnd(*this); - return r; + if (auto dbg = ctx.env->debug) [[unlikely]] { + auto dbgVal = Value{val}; + dbg->evalBegin(*this, ctx, dbgVal, res); + auto r = ieval(ctx, std::move(dbgVal), res); + dbg->evalEnd(*this); + return r; + } + + return ieval(ctx, val, res); + } + + auto eval(Context ctx, Value&& val, const ResultFn& res) -> tl::expected + { + if (ctx.canceled()) + return Result::Stop; + + if (auto dbg = ctx.env->debug) [[unlikely]] { + dbg->evalBegin(*this, ctx, val, res); + auto r = ieval(ctx, std::move(val), res); + dbg->evalEnd(*this); + return r; + } + + return ieval(ctx, std::move(val), res); } /* Recursive clone */ @@ -79,7 +97,12 @@ class Expr private: /* Abstract evaluation implementation */ - virtual auto ieval(Context ctx, const Value& value, const ResultFn& result) -> Result = 0; + virtual auto ieval(Context ctx, const Value& value, const ResultFn& result) -> tl::expected = 0; + + /* Move-optimized evaluation implementation */ + virtual auto ieval(Context ctx, Value&& value, const ResultFn& result) -> tl::expected { + return ieval(ctx, value, result); + } SourceLocation sourceLocation_; }; @@ -110,7 +133,7 @@ class AST /* The original query string of the AST */ std::string queryString_; - /* The actuall AST */ + /* The actual AST */ ExprPtr expr_; }; diff --git a/include/simfil/function.h b/include/simfil/function.h index a7332a3a..19265b1d 100644 --- a/include/simfil/function.h +++ b/include/simfil/function.h @@ -14,49 +14,6 @@ namespace simfil { struct Context; -struct ArgumentCountError : std::exception -{ - const Function* fn; - size_t min; - size_t max; - size_t have; - - ArgumentCountError(const Function& fn, size_t min, size_t max, size_t have) - : fn(&fn), min(min), max(max), have(have) - {} - - mutable std::string msg; - auto what() const noexcept -> const char* override; -}; - -struct ArgumentTypeError : std::exception -{ - const Function* fn; - size_t index; - std::string want; - std::string have; - - ArgumentTypeError(const Function& fn, size_t index, std::string want, std::string have) - : fn(&fn), index(index), want(std::move(want)), have(std::move(have)) - {} - - mutable std::string msg; - auto what() const noexcept -> const char* override; -}; - -struct ArgumentValueCountError : std::exception -{ - const Function* fn; - size_t index; - - ArgumentValueCountError(const Function& fn, size_t index) - : fn(&fn), index(index) - {} - - mutable std::string msg; - auto what() const noexcept -> const char* override; -}; - /** * Function info. */ @@ -76,7 +33,7 @@ class Function virtual ~Function() = default; virtual auto ident() const -> const FnInfo& = 0; - virtual auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result = 0; + virtual auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected = 0; }; class CountFn : public Function @@ -87,7 +44,7 @@ class CountFn : public Function CountFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; class TraceFn : public Function @@ -98,7 +55,7 @@ class TraceFn : public Function TraceFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; class RangeFn : public Function @@ -109,7 +66,7 @@ class RangeFn : public Function RangeFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; class ReFn : public Function @@ -120,7 +77,7 @@ class ReFn : public Function ReFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; class ArrFn : public Function @@ -131,7 +88,7 @@ class ArrFn : public Function ArrFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; class SplitFn : public Function @@ -142,7 +99,7 @@ class SplitFn : public Function SplitFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; class SelectFn : public Function @@ -153,7 +110,7 @@ class SelectFn : public Function SelectFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; class SumFn : public Function @@ -164,7 +121,7 @@ class SumFn : public Function SumFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; class KeysFn : public Function @@ -175,14 +132,14 @@ class KeysFn : public Function KeysFn(); auto ident() const -> const FnInfo& override; - auto eval(Context, Value, const std::vector&, const ResultFn&) const -> Result override; + auto eval(Context, const Value&, const std::vector&, const ResultFn&) const -> tl::expected override; }; /** Utility functions for working with arguments*/ namespace util { -inline auto evalArg1Any(Context ctx, Value val, const ExprPtr& expr) -> std::tuple +inline auto evalArg1Any(Context ctx, const Value& val, const ExprPtr& expr) -> std::tuple { if (!expr) return {false, Value::undef()}; @@ -198,7 +155,7 @@ inline auto evalArg1Any(Context ctx, Value val, const ExprPtr& expr) -> std::tup } template -auto evalArg1(Context ctx, Value val, const ExprPtr& expr, CType fallback = {}) -> std::tuple +auto evalArg1(Context ctx, const Value& val, const ExprPtr& expr, CType fallback = {}) -> std::tuple { auto&& [ok, value] = evalArg1Any(ctx, val, expr); if (ok) diff --git a/include/simfil/model/arena.h b/include/simfil/model/arena.h index a9994fbd..92ed45f6 100644 --- a/include/simfil/model/arena.h +++ b/include/simfil/model/arena.h @@ -2,13 +2,16 @@ #pragma once +#include #include #include #include #include +#include #include -#include "../exception-handler.h" +#include "simfil/exception-handler.h" +#include "simfil/error.h" // Define this to enable array arena read-write locking. // #define ARRAY_ARENA_THREAD_SAFE @@ -101,11 +104,13 @@ class ArrayArena * @return A reference to the element at the specified index. * @throws std::out_of_range if the index is out of the array bounds. */ - ElementType_& at(ArrayIndex const& a, size_t const& i) { - return at_impl(*this, a, i); + tl::expected, Error> + at(ArrayIndex const& a, size_t const& i) { + return at_impl(*this, a, i); } - ElementType_ const& at(ArrayIndex const& a, size_t const& i) const { - return at_impl(*this, a, i); + tl::expected, Error> + at(ArrayIndex const& a, size_t const& i) const { + return at_impl(*this, a, i); } /** @@ -206,8 +211,11 @@ class ArrayArena ArrayIterator(ArrayArenaRef arena, ArrayIndex array_index, size_t elem_index) : arena_(arena), array_index_(array_index), elem_index_(elem_index) {} - ElementRef operator*() { - return arena_.at(array_index_, elem_index_); + ElementRef operator*() noexcept { + auto res = arena_.at(array_index_, elem_index_); + assert(res); + // Unchecked access! + return *res; } ArrayIterator& operator++() { @@ -385,7 +393,8 @@ class ArrayArena } template - static ElementTypeRef at_impl(Self& self, ArrayIndex const& a, size_t const& i) + static tl::expected, Error> + at_impl(Self& self, ArrayIndex const& a, size_t const& i) { #ifdef ARRAY_ARENA_THREAD_SAFE std::shared_lock guard(self.lock_); @@ -396,7 +405,7 @@ class ArrayArena if (remaining < current->capacity && remaining < current->size) return self.data_[current->offset + remaining]; if (current->next == InvalidArrayIndex) - raise("Index out of range"); + return tl::unexpected(Error::IndexOutOfRange, "index out of range"); remaining -= current->capacity; current = &self.continuations_[current->next]; } diff --git a/include/simfil/model/bitsery-traits.h b/include/simfil/model/bitsery-traits.h index 15fe8f71..7c306d41 100644 --- a/include/simfil/model/bitsery-traits.h +++ b/include/simfil/model/bitsery-traits.h @@ -1,11 +1,13 @@ #pragma once +#include #include #include #include #include #include +#include "bitsery/details/adapter_common.h" #include "nodes.h" #include "arena.h" @@ -58,8 +60,12 @@ struct ArrayArenaExt for (simfil::ArrayIndex i = 0; i < numArrays; ++i) { auto size = arena.size(i); s.value4b(size); - for (size_t j = 0; j < size; ++j) - fnc(s, const_cast(arena.at(i, j))); + for (size_t j = 0; j < size; ++j) { + if (auto value = arena.at(i, j)) + fnc(s, const_cast(value->get())); + else + raise(std::move(value.error())); // Bitsery does not support propagating errors + } } } diff --git a/include/simfil/model/json.h b/include/simfil/model/json.h index 0a8af63d..8e3ee5a2 100644 --- a/include/simfil/model/json.h +++ b/include/simfil/model/json.h @@ -7,12 +7,15 @@ #endif #include "model.h" +#include "simfil/error.h" + +#include namespace simfil::json { -void parse(std::istream& input, ModelPoolPtr const& model); -void parse(const std::string& input, ModelPoolPtr const& model); -ModelPoolPtr parse(const std::string& input); +auto parse(std::istream& input, ModelPoolPtr const& model) -> tl::expected; +auto parse(const std::string& input, ModelPoolPtr const& model) -> tl::expected; +auto parse(const std::string& input) -> tl::expected; } diff --git a/include/simfil/model/model.h b/include/simfil/model/model.h index 39eeab7a..951bbb8b 100644 --- a/include/simfil/model/model.h +++ b/include/simfil/model/model.h @@ -2,6 +2,7 @@ #pragma once #include "simfil/model/string-pool.h" +#include "tl/expected.hpp" #if defined(SIMFIL_WITH_MODEL_JSON) # include "nlohmann/json.hpp" #endif @@ -55,7 +56,7 @@ class Model : public std::enable_shared_from_this * Get a callback with the actual class of the given node. * This facilitates the Virtual Function Table role of the Model. */ - virtual void resolve(ModelNode const& n, ResolveFn const& cb) const; + virtual tl::expected resolve(ModelNode const& n, ResolveFn const& cb) const; /** Add a small scalar value and get its model node view */ ModelNode::Ptr newSmallValue(bool value); @@ -116,19 +117,19 @@ class ModelPool : public Model * Get a callback with the actual class of the given node. * This facilitates the Virtual Function Table role of the ModelPool. */ - void resolve(ModelNode const& n, ResolveFn const& cb) const override; + tl::expected resolve(ModelNode const& n, ResolveFn const& cb) const override; /** Clear all columns and roots */ virtual void clear(); - /** Check for errors, throw if there are any */ - void validate() const; + /** Check for errors and returns them */ + tl::expected validate() const; /** Get number of root nodes */ [[nodiscard]] size_t numRoots() const; /** Get specific root node */ - [[nodiscard]] ModelNode::Ptr root(size_t const& i) const; + [[nodiscard]] tl::expected root(size_t const& i) const; /** Designate a model node index as a root */ void addRoot(ModelNode::Ptr const& rootNode); @@ -166,13 +167,13 @@ class ModelPool : public Model * Note: This will potentially create new field entries in the newDict, * for field names which were not there before. */ - virtual void setStrings(std::shared_ptr const& strings); + virtual auto setStrings(std::shared_ptr const& strings) -> tl::expected; std::optional lookupStringId(StringId id) const override; /** Serialization */ - virtual void write(std::ostream& outputStream); - virtual void read(std::istream& inputStream); + virtual tl::expected write(std::ostream& outputStream); + virtual tl::expected read(std::istream& inputStream); #if defined(SIMFIL_WITH_MODEL_JSON) /** JSON Serialization */ diff --git a/include/simfil/model/nodes.h b/include/simfil/model/nodes.h index 72e34078..36a2c1c9 100644 --- a/include/simfil/model/nodes.h +++ b/include/simfil/model/nodes.h @@ -5,7 +5,6 @@ #include #include #include -#include #include "arena.h" #include "string-pool.h" @@ -110,29 +109,23 @@ struct model_ptr return model_ptr(std::in_place, std::forward(args)...); } - inline void ensureModelIsNotNull() const { - if (!data_.model_ || !data_.addr_) { - raise("Attempt to dereference null model_ptr!"); - } - } - inline T& operator* () { - ensureModelIsNotNull(); + assert(data_.model_ && data_.addr_); return data_; } inline T* operator-> () { - ensureModelIsNotNull(); + assert(data_.model_ && data_.addr_); return &data_; } inline T const& operator* () const { - ensureModelIsNotNull(); + assert(data_.model_ && data_.addr_); return data_; } inline T const* operator-> () const { - ensureModelIsNotNull(); + assert(data_.model_ && data_.addr_); return &data_; } @@ -500,7 +493,7 @@ struct Array : public BaseArray /** * Append all elements from `other` to this array. */ - Array& extend(model_ptr const& other); + tl::expected extend(model_ptr const& other); protected: Array() = default; @@ -518,7 +511,8 @@ struct BaseObject : public MandatoryDerivedModelNodeBase template requires std::derived_from - BaseObject& addField(std::string_view const& name, model_ptr const& value) { + tl::expected>, Error> + addField(std::string_view const& name, model_ptr const& value) { return addFieldInternal(name, static_cast(value)); } @@ -556,7 +550,8 @@ struct BaseObject : public MandatoryDerivedModelNodeBase BaseObject(ModelConstPtr pool, ModelNodeAddress); BaseObject(ArrayIndex members, ModelConstPtr pool, ModelNodeAddress); - BaseObject& addFieldInternal(std::string_view const& name, ModelNode::Ptr const& value={}); + tl::expected>, Error> + addFieldInternal(std::string_view const& name, ModelNode::Ptr const& value={}); Storage* storage_ = nullptr; ArrayIndex members_ = 0; @@ -573,19 +568,19 @@ struct Object : public BaseObject using BaseObject::get; using BaseObject::addField; - Object& addBool(std::string_view const& name, bool value); - Object& addField(std::string_view const& name, uint16_t value); - Object& addField(std::string_view const& name, int16_t value); - Object& addField(std::string_view const& name, int64_t const& value); - Object& addField(std::string_view const& name, double const& value); - Object& addField(std::string_view const& name, std::string_view const& value); + tl::expected addBool(std::string_view const& name, bool value); + tl::expected addField(std::string_view const& name, uint16_t value); + tl::expected addField(std::string_view const& name, int16_t value); + tl::expected addField(std::string_view const& name, int64_t const& value); + tl::expected addField(std::string_view const& name, double const& value); + tl::expected addField(std::string_view const& name, std::string_view const& value); - [[nodiscard]] ModelNode::Ptr get(std::string_view const& fieldName) const; + [[nodiscard]] tl::expected get(std::string_view const& fieldName) const; /** * Adopt all fields from the `other` object into this one. */ - Object& extend(model_ptr const& other); + tl::expected extend(model_ptr const& other); protected: Object() = default; diff --git a/include/simfil/model/nodes.impl.h b/include/simfil/model/nodes.impl.h index 775e2dce..4f1fa48b 100644 --- a/include/simfil/model/nodes.impl.h +++ b/include/simfil/model/nodes.impl.h @@ -1,5 +1,7 @@ #pragma once #include "nodes.h" +#include "tl/expected.hpp" +#include namespace simfil { @@ -24,7 +26,9 @@ ModelNode::Ptr BaseArray::at(int64_t i) const { if (i < 0 || i >= (int64_t)storage_->size(members_)) return {}; - return ModelNode::Ptr::make(model_, storage_->at(members_, i)); + if (auto value = storage_->at(members_, i); value) + return ModelNode::Ptr::make(model_, value->get()); + return {}; } template @@ -96,7 +100,9 @@ ModelNode::Ptr BaseObject::at(int64_t i) const { if (i < 0 || i >= (int64_t)storage_->size(members_)) return {}; - return ModelNode::Ptr::make(model_, storage_->at(members_, i).node_); + if (auto value = storage_->at(members_, i); value) + return ModelNode::Ptr::make(model_, value->get().node_); + return {}; } template @@ -104,7 +110,9 @@ StringId BaseObject::keyAt(int64_t i) const { if (i < 0 || i >= (int64_t)storage_->size(members_)) return {}; - return storage_->at(members_, i).name_; + if (auto value = storage_->at(members_, i); value) + return value->get().name_; + return {}; } template @@ -146,13 +154,15 @@ bool BaseObject::iterate(const ModelNode::IterCallback } template -BaseObject& BaseObject::addFieldInternal( +tl::expected>, Error> BaseObject::addFieldInternal( std::string_view const& name, ModelNode::Ptr const& value) { auto fieldId = model().strings()->emplace(name); - storage_->emplace_back(members_, fieldId, value->addr()); - return *this; + if (!fieldId) + return tl::unexpected(std::move(fieldId.error())); + storage_->emplace_back(members_, *fieldId, value->addr()); + return std::ref(*this); } -} // namespace simfil \ No newline at end of file +} // namespace simfil diff --git a/include/simfil/model/string-pool.h b/include/simfil/model/string-pool.h index 14412ed4..1887b565 100644 --- a/include/simfil/model/string-pool.h +++ b/include/simfil/model/string-pool.h @@ -11,6 +11,9 @@ #include #include #include +#include + +#include "simfil/error.h" namespace simfil { @@ -45,7 +48,7 @@ struct StringPool /// Use this function to lookup a stored string, or insert it /// if it doesn't exist yet. - StringId emplace(std::string_view const& str); + auto emplace(std::string_view const& str) -> tl::expected; /// Returns the ID of the given string, or `Empty` if /// no such string was ever inserted. @@ -67,12 +70,12 @@ struct StringPool size_t misses() const; /// Add a static key-string mapping - Warning: Not thread-safe. - void addStaticKey(StringId k, std::string const& v); + void addStaticKey(StringId id, std::string const& value); /// Serialization - write to stream, starting from a specific /// id offset if necessary (for partial serialisation). - virtual void write(std::ostream& outputStream, StringId offset = {}) const; // NOLINT - virtual void read(std::istream& inputStream); + virtual auto write(std::ostream& outputStream, StringId offset = {}) const -> tl::expected; // NOLINT + virtual auto read(std::istream& inputStream) -> tl::expected; /// Check if the content of the string pools is logically identical. bool operator== (StringPool const& other) const; diff --git a/include/simfil/operator.h b/include/simfil/operator.h index dc276cbf..6e874cd9 100644 --- a/include/simfil/operator.h +++ b/include/simfil/operator.h @@ -2,10 +2,12 @@ #pragma once -#include "fmt/format.h" +#include "simfil/error.h" #include "simfil/value.h" #include "exception-handler.h" +#include "fmt/format.h" +#include #include #include #include @@ -14,6 +16,7 @@ namespace simfil { using namespace std::string_literals; +using namespace std::string_view_literals; /** * Special return type for operator type mismatch. @@ -25,19 +28,6 @@ struct InvalidOperands { } }; -/** - * Exception for invalid operand types. - */ -struct InvalidOperandsError : std::exception -{ - std::string operatorName; - - explicit InvalidOperandsError(std::string_view opName) - : operatorName(opName) - {} -}; - - #define NAME(str) \ static const char * name() { \ return str; \ @@ -81,6 +71,7 @@ struct InvalidOperandsError : std::exception struct OperatorNegate { NAME("-") + NULL_AS_NULL() DENY_OTHER() DECL_OPERATION(int64_t, -) DECL_OPERATION(double, -) @@ -112,8 +103,8 @@ struct OperatorNot { NAME("not") - template - auto operator()(const _Type& value) const + template + auto operator()(const Type_& value) const { return !OperatorBool()(value); } @@ -150,46 +141,46 @@ struct OperatorTypeof { NAME("typeof") - auto operator()(NullType) const -> const std::string& + auto operator()(NullType) const -> std::string_view { - static auto n = "null"s; + static auto n = "null"sv; return n; } - auto operator()(bool) const -> const std::string& + auto operator()(bool) const -> std::string_view { - static auto n = "bool"s; + static auto n = "bool"sv; return n; } - auto operator()(int64_t) const -> const std::string& + auto operator()(int64_t) const -> std::string_view { - static auto n = "int"s; + static auto n = "int"sv; return n; } - auto operator()(double) const -> const std::string& + auto operator()(double) const -> std::string_view { - static auto n = "float"s; + static auto n = "float"sv; return n; } - auto operator()(const std::string&) const -> const std::string& + auto operator()(const std::string&) const -> std::string_view { - static auto n = "string"s; + static auto n = "string"sv; return n; } - auto operator()(const ModelNode& v) const -> const std::string& + auto operator()(const ModelNode&) const -> std::string_view { - static auto n = "model"s; + static auto n = "model"sv; return n; } - auto operator()(const TransientObject& v) const + auto operator()(const TransientObject&) const -> std::string_view { // Handled by MetaType::unaryOp - return ""s; + return ""sv; } }; @@ -430,32 +421,32 @@ struct OperatorDiv DENY_OTHER() NULL_AS_NULL() - auto operator()(int64_t l, int64_t r) const -> int64_t + auto operator()(int64_t l, int64_t r) const -> tl::expected { if (r == 0) - raise("Division by zero"); + return tl::unexpected(Error::DivisionByZero, "Division by zero"); return static_cast(l / r); } - auto operator()(int64_t l, double r) const -> double + auto operator()(int64_t l, double r) const -> tl::expected { if (r == 0) - raise("Division by zero"); - return static_cast(l / r); + return tl::unexpected(Error::DivisionByZero, "Division by zero"); + return static_cast(l) / r; } - auto operator()(double l, int64_t r) const -> double + auto operator()(double l, int64_t r) const -> tl::expected { if (r == 0) - raise("Division by zero"); - return static_cast(l / r); + return tl::unexpected(Error::DivisionByZero, "Division by zero"); + return l / static_cast(r); } - auto operator()(double l, double r) const -> double + auto operator()(double l, double r) const -> tl::expected { if (r == 0) - raise("Division by zero"); - return static_cast(l / r); + return tl::unexpected(Error::DivisionByZero, "Division by zero"); + return l / r; } }; @@ -465,10 +456,10 @@ struct OperatorMod DENY_OTHER() NULL_AS_NULL() - auto operator()(int64_t l, int64_t r) const -> int64_t + auto operator()(int64_t l, int64_t r) const -> tl::expected { if (r == 0) - raise("Division by zero"); + return tl::unexpected(Error::DivisionByZero, "Division by zero"); return static_cast(l % r); } }; @@ -627,63 +618,60 @@ struct OperatorShr namespace impl { -template -Value makeOperatorResult(_CType&& value) +template +inline auto makeOperatorResult(CType_&& value) -> tl::expected { - return Value::make(std::forward<_CType>(value)); + return Value::make(std::forward(value)); } -template -inline Value makeOperatorResult(Value value) +template +inline auto makeOperatorResult(Value value) -> tl::expected { return value; } -template -inline Value makeOperatorResult(InvalidOperands) +template +inline auto makeOperatorResult(tl::expected&& value) -> tl::expected +{ + if (!value) + return tl::unexpected(std::move(value.error())); + return Value::make(std::forward(std::move(value.value()))); +} + +template +inline auto makeOperatorResult(InvalidOperands) -> tl::expected { - raise(_Operator::name()); + return tl::unexpected(Error::InvalidOperands, fmt::format("Invalid operands for operator '{}'", Operator_::name())); } } -template +template struct UnaryOperatorDispatcher { - static auto dispatch(const Value& value) -> Value + static auto dispatch(const Value& value) -> tl::expected { - try { - if (value.isa(ValueType::TransientObject)) { - const auto& obj = value.as(); - return obj.meta->unaryOp(_Operator::name(), obj); - } - - return value.visit(UnaryOperatorDispatcher()); - } catch (const InvalidOperandsError& err) { - std::string ltype; - try { - ltype = UnaryOperatorDispatcher::dispatch(value).toString(); - } catch (...) { - ltype = valueType2String(value.type); - } - raise("Invalid operand "s + ltype + - " for operator "s + std::string(err.operatorName)); + if (value.isa(ValueType::TransientObject)) { + const auto& obj = value.as(); + return obj.meta->unaryOp(Operator_::name(), obj); } + + return value.visit(UnaryOperatorDispatcher()); } - auto operator()(UndefinedType) -> Value + auto operator()(UndefinedType) -> tl::expected { return Value::undef(); } - template - auto operator()(const _Value& rhs) -> Value + template + auto operator()(const Value_& rhs) -> tl::expected { - return impl::makeOperatorResult<_Operator>(_Operator()(rhs)); + return impl::makeOperatorResult(Operator_()(rhs)); } }; -template +template struct BinaryOperatorDispatcherRHS { const Left& lhs; @@ -691,15 +679,15 @@ struct BinaryOperatorDispatcherRHS : lhs(lhs) {} - auto operator()(UndefinedType) -> Value + auto operator()(UndefinedType) -> tl::expected { return Value::undef(); } template - auto operator()(const Right& rhs) -> Value + auto operator()(const Right& rhs) -> tl::expected { - return impl::makeOperatorResult(Operator()(lhs, rhs)); + return impl::makeOperatorResult(Operator_()(lhs, rhs)); } }; @@ -708,48 +696,55 @@ struct BinaryOperatorDispatcher { const Value& value; - static auto dispatch(const Value& lhs, const Value& rhs) -> Value + static auto dispatch(const Value& lhs, const Value& rhs) -> tl::expected { - try { + auto result = ([&]() -> tl::expected { if (lhs.isa(ValueType::TransientObject)) { - if (rhs.isa(ValueType::Undef)) + if (rhs.isa(ValueType::Undef)) { return Value::undef(); - const auto& obj = lhs.as(); - return obj.meta->binaryOp(Operator::name(), obj, rhs); + } else { + const auto& obj = lhs.as(); + return obj.meta->binaryOp(Operator::name(), obj, rhs); + } } - - if (rhs.isa(ValueType::TransientObject)) { - if (lhs.isa(ValueType::Undef)) + else if (rhs.isa(ValueType::TransientObject)) { + if (lhs.isa(ValueType::Undef)) { return Value::undef(); - const auto& obj = rhs.as(); - return obj.meta->binaryOp(Operator::name(), lhs, obj); + } else { + const auto& obj = rhs.as(); + return obj.meta->binaryOp(Operator::name(), lhs, obj); + } } - - return lhs.visit(BinaryOperatorDispatcher(rhs)); - } catch (const InvalidOperandsError& err) { - std::string ltype, rtype; - try { - ltype = UnaryOperatorDispatcher::dispatch(lhs).toString(); - rtype = UnaryOperatorDispatcher::dispatch(rhs).toString(); - } catch (...) { - ltype = valueType2String(lhs.type); - rtype = valueType2String(rhs.type); + else { + return lhs.visit(BinaryOperatorDispatcher(rhs)); + } + })(); + + if (!result) { + // Try to find the operand types + const auto& error = result.error(); + if (error.type == Error::InvalidOperands) { + auto ltype = UnaryOperatorDispatcher::dispatch(lhs).value_or(Value::strref("unknown")).toString(); + auto rtype = UnaryOperatorDispatcher::dispatch(rhs).value_or(Value::strref("unknown")).toString(); + return tl::unexpected(Error::InvalidOperands, + fmt::format("Invalid operands {} and {} for operator {}", ltype, rtype, Operator::name())); } - raise(fmt::format("Invalid operands {} and {} for operator {}", ltype, rtype, err.operatorName)); } + + return result; } explicit BinaryOperatorDispatcher(const Value& value) : value(value) {} - auto operator()(UndefinedType) -> Value + auto operator()(UndefinedType) -> tl::expected { return Value::undef(); } template - auto operator()(const Left& lhs) -> Value + auto operator()(const Left& lhs) -> tl::expected { return value.visit(BinaryOperatorDispatcherRHS(lhs)); } diff --git a/include/simfil/overlay.h b/include/simfil/overlay.h index 2539550f..14fe57a5 100644 --- a/include/simfil/overlay.h +++ b/include/simfil/overlay.h @@ -14,7 +14,7 @@ struct OverlayNodeStorage final : public Model explicit OverlayNodeStorage(Value const& val) : value_(val) {} // NOLINT - void resolve(ModelNode const& n, ResolveFn const& cb) const override; + tl::expected resolve(ModelNode const& n, ResolveFn const& cb) const override; }; /** Node for injecting member fields */ diff --git a/include/simfil/parser.h b/include/simfil/parser.h index 083493a3..43fe4c2b 100644 --- a/include/simfil/parser.h +++ b/include/simfil/parser.h @@ -105,8 +105,8 @@ class Parser Context ctx; Environment* const env; - std::unordered_map> prefixParsers; - std::unordered_map> infixParsers; + std::unordered_map prefixParsers; + std::unordered_map infixParsers; private: auto findPrefixParser(const Token& t) const -> const PrefixParselet*; diff --git a/include/simfil/result.h b/include/simfil/result.h index ded79f99..ce4e9891 100644 --- a/include/simfil/result.h +++ b/include/simfil/result.h @@ -1,21 +1,21 @@ #pragma once +#include + #include "simfil/environment.h" +#include "simfil/error.h" namespace simfil { -/** - * Result value callback. - * Return `false` to stop evaluation. - */ enum Result { Continue = 1, Stop = 0 }; struct ResultFn { virtual ~ResultFn() = default; - virtual auto operator()(Context ctx, Value value) const -> Result = 0; + virtual auto operator()(Context ctx, const Value& value) const noexcept -> tl::expected = 0; + virtual auto operator()(Context ctx, Value&& value) const noexcept -> tl::expected = 0; }; template @@ -27,7 +27,16 @@ struct LambdaResultFn final : ResultFn : lambda(std::move(ref)) {} - auto operator()(Context ctx, Value value) const -> Result override + auto operator()(Context ctx, const Value& value) const noexcept -> tl::expected override + { + if constexpr (std::is_invocable_v) { + return lambda(std::move(ctx), value); + } else { + return lambda(std::move(ctx), Value{value}); + } + } + + auto operator()(Context ctx, Value&& value) const noexcept -> tl::expected override { return lambda(std::move(ctx), std::move(value)); } diff --git a/include/simfil/sourcelocation.h b/include/simfil/sourcelocation.h index 6d412ef1..a3017501 100644 --- a/include/simfil/sourcelocation.h +++ b/include/simfil/sourcelocation.h @@ -19,6 +19,8 @@ struct SourceLocation {} SourceLocation(const SourceLocation&) = default; + + auto operator==(const SourceLocation& o) const -> bool = default; }; } diff --git a/include/simfil/transient.h b/include/simfil/transient.h index 03f27de9..d907a6a9 100644 --- a/include/simfil/transient.h +++ b/include/simfil/transient.h @@ -4,6 +4,9 @@ #include #include +#include + +#include "error.h" namespace simfil { @@ -20,7 +23,7 @@ struct MetaType * - Make Value::node optionally owning its ptr * - Set ModelNode instance for objects you create */ - MetaType(std::string ident) + explicit MetaType(std::string ident) : ident(std::move(ident)) {} @@ -32,12 +35,12 @@ struct MetaType virtual auto deinit(void*) const -> void = 0; /* Operators */ - virtual auto unaryOp(std::string_view op, const TransientObject&) const -> Value = 0; - virtual auto binaryOp(std::string_view op, const TransientObject&, const Value&) const -> Value = 0; - virtual auto binaryOp(std::string_view op, const Value&, const TransientObject&) const -> Value = 0; + virtual auto unaryOp(std::string_view op, const TransientObject&) const -> tl::expected = 0; + virtual auto binaryOp(std::string_view op, const TransientObject&, const Value&) const -> tl::expected = 0; + virtual auto binaryOp(std::string_view op, const Value&, const TransientObject&) const -> tl::expected = 0; /* Unpack */ - virtual auto unpack(const TransientObject&, std::function) const -> void = 0; /* cb return false to stop! */ + virtual auto unpack(const TransientObject&, std::function) const -> tl::expected = 0; /* cb return false to stop! */ }; /** @@ -61,19 +64,19 @@ struct TransientObject return *this; } - TransientObject(TransientObject&& other) - : meta(std::move(other.meta)) - , data(std::move(other.data)) + TransientObject(TransientObject&& other) noexcept + : meta(other.meta) + , data(other.data) { other.meta = nullptr; other.data = nullptr; } - auto operator=(TransientObject&& other) + TransientObject& operator=(TransientObject&& other) noexcept { meta->deinit(data); - meta = std::move(other.meta); - data = std::move(other.data); + meta = other.meta; + data = other.data; other.meta = nullptr; other.data = nullptr; return *this; diff --git a/include/simfil/typed-meta-type.h b/include/simfil/typed-meta-type.h index 7dee909a..3506e094 100644 --- a/include/simfil/typed-meta-type.h +++ b/include/simfil/typed-meta-type.h @@ -5,6 +5,7 @@ #include "value.h" #include "operator.h" #include "exception-handler.h" +#include "transient.h" namespace simfil { @@ -17,7 +18,7 @@ namespace simfil template struct TypedMetaType : MetaType { - TypedMetaType(std::string ident) + explicit TypedMetaType(std::string ident) : MetaType(std::move(ident)) {} @@ -35,48 +36,48 @@ struct TypedMetaType : MetaType return nullptr; } - auto init() const -> void* + auto init() const -> void* override { return new Type(); } - auto copy(void* ptr) const -> void* + auto copy(void* ptr) const -> void* override { return new Type(*((Type*)ptr)); } - auto deinit(void* ptr) const -> void + auto deinit(void* ptr) const -> void override { delete (Type*)ptr; } - auto unaryOp(std::string_view op, const TransientObject& obj) const -> Value + auto unaryOp(std::string_view op, const TransientObject& obj) const -> tl::expected override { return unaryOp(op, *(const Type*)obj.data); } - auto binaryOp(std::string_view op, const TransientObject& obj, const Value& v) const -> Value + auto binaryOp(std::string_view op, const TransientObject& obj, const Value& v) const -> tl::expected override { return binaryOp(op, *(const Type*)obj.data, v); } - auto binaryOp(std::string_view op, const Value& v, const TransientObject& obj) const -> Value + auto binaryOp(std::string_view op, const Value& v, const TransientObject& obj) const -> tl::expected override { return binaryOp(op, v, *(const Type*)obj.data); } - auto unpack(const TransientObject& obj, std::function fn) const -> void + auto unpack(const TransientObject& obj, std::function fn) const -> tl::expected override { return unpack(*(const Type*)obj.data, fn); } - virtual auto unaryOp(std::string_view op, const Type&) const -> Value = 0; - virtual auto binaryOp(std::string_view op, const Type&, const Value&) const -> Value = 0; - virtual auto binaryOp(std::string_view op, const Value&, const Type&) const -> Value = 0; + virtual auto unaryOp(std::string_view op, const Type&) const -> tl::expected = 0; + virtual auto binaryOp(std::string_view op, const Type&, const Value&) const -> tl::expected = 0; + virtual auto binaryOp(std::string_view op, const Value&, const Type&) const -> tl::expected = 0; - virtual auto unpack(const Type&, std::function fn) const -> void + virtual auto unpack(const Type&, std::function fn) const -> tl::expected { - raise("..."); + return tl::unexpected(Error::Unimplemented, "Type has no unpack operator"); } }; diff --git a/include/simfil/types.h b/include/simfil/types.h index 2117f38f..683d668e 100644 --- a/include/simfil/types.h +++ b/include/simfil/types.h @@ -36,11 +36,11 @@ class IRangeType : public TypedMetaType auto make(int64_t a, int64_t b) -> Value; - auto unaryOp(std::string_view op, const IRange& self) const -> Value override; - auto binaryOp(std::string_view op, const IRange& l, const Value& r) const -> Value override; - auto binaryOp(std::string_view op, const Value& l, const IRange& r) const -> Value override; + auto unaryOp(std::string_view op, const IRange& self) const -> tl::expected override; + auto binaryOp(std::string_view op, const IRange& l, const Value& r) const -> tl::expected override; + auto binaryOp(std::string_view op, const Value& l, const IRange& r) const -> tl::expected override; - auto unpack(const IRange& , std::function res) const -> void override; + auto unpack(const IRange& , std::function res) const -> tl::expected override; }; struct Re @@ -58,9 +58,9 @@ class ReType : public TypedMetaType auto make(std::string_view expr) -> Value; - auto unaryOp(std::string_view op, const Re&) const -> Value override; - auto binaryOp(std::string_view op, const Re&, const Value&) const -> Value override; - auto binaryOp(std::string_view op, const Value&, const Re&) const -> Value override; + auto unaryOp(std::string_view op, const Re&) const -> tl::expected override; + auto binaryOp(std::string_view op, const Re&, const Value&) const -> tl::expected override; + auto binaryOp(std::string_view op, const Value&, const Re&) const -> tl::expected override; }; } diff --git a/include/simfil/value.h b/include/simfil/value.h index fd8bcf7a..5c871c39 100644 --- a/include/simfil/value.h +++ b/include/simfil/value.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "model/nodes.h" #include "transient.h" @@ -86,10 +87,8 @@ inline auto valueType2String(ValueType t) -> const char* case ValueType::TransientObject: return "transient"; case ValueType::Object: return "object"; case ValueType::Array: return "array"; - default: - assert(0 && "unreachable"); - return "unknown"; } + assert(0 && "unreachable"); return "unknown"; // GCOVR_EXCL_LINE } @@ -169,6 +168,11 @@ struct ValueType4CType { static constexpr ValueType Type = ValueType::Object; }; +template <> +struct ValueType4CType { + static constexpr ValueType Type = ValueType::Object; +}; + template struct ValueTypeInfo; @@ -216,7 +220,7 @@ template struct ValueAs { template - static auto get(const VariantType& v) -> decltype(auto) + static inline auto get(const VariantType& v) noexcept -> decltype(auto) { return std::get::Type>(v); } @@ -236,6 +240,30 @@ struct ValueAs } }; +template <> +struct ValueAs +{ + template + static auto get(const VariantType& v) -> ModelNode::Ptr + { + if (auto nodePtr = std::get_if(&v)) + return *nodePtr; + return {}; + } +}; + +template <> +struct ValueAs +{ + template + static auto get(const VariantType& v) -> ModelNode::Ptr + { + if (auto nodePtr = std::get_if(&v)) + return *nodePtr; + return {}; + } +}; + class Value { public: @@ -277,20 +305,43 @@ class Value static auto field(const ModelNode& node) -> Value { - return {node.type(), node.value(), model_ptr(node)}; + const auto type = node.type(); + if (type == ValueType::Object || type == ValueType::Array) { + return Value{type, model_ptr(node)}; + } else if (type == ValueType::String) { + auto value = node.value(); + if (auto view = std::get_if(&value)) + return Value::make(std::string(*view)); + return Value{std::move(value)}; + } else { + return Value{node.value()}; + } } static auto field(ModelNode&& node) -> Value { - auto type = node.type(); - auto value = node.value(); - return {type, std::move(value), model_ptr(std::move(node))}; + const auto type = node.type(); + if (type == ValueType::Object || type == ValueType::Array) { + return {type, model_ptr(std::move(node))}; + } else if (type == ValueType::String) { + auto value = node.value(); + if (auto view = std::get_if(&value)) + return Value::make(std::string(*view)); + return Value{std::move(value)}; + } else { + return Value{node.value()}; + } } template static auto field(const model_ptr& node) -> Value { - return {node->type(), node->value(), node}; + const auto type = node->type(); + if (type == ValueType::Object || type == ValueType::Array) { + return {type, model_ptr(node)}; + } else { + return Value{node->value()}; + } } Value(ValueType type) // NOLINT @@ -303,17 +354,12 @@ class Value , value(std::forward(value)) {} - Value(ValueType type, ScalarValueType&& value_, ModelNode::Ptr node) - : type(type), node(std::move(node)) - { - std::visit([this](auto&& v){value = v;}, value_); - } Value(ScalarValueType&& value_) // NOLINT { std::visit([this](auto&& v){ type = ValueType4CType>::Type; - value = v; + value = std::forward(v); }, value_); } @@ -322,42 +368,50 @@ class Value Value& operator=(const Value&) = default; Value& operator=(Value&&) = default; - [[nodiscard]] auto isa(ValueType test) const + [[nodiscard]] auto isa(ValueType test) const noexcept { return type == test; } - + + [[nodiscard]] bool asBool() const noexcept { + return std::get(value); + } + template [[nodiscard]] auto as() const -> decltype(auto) { return ValueAs::get(value); } - [[nodiscard]] auto isBool(bool v) const + [[nodiscard]] auto isBool(bool v) const noexcept { - return isa(ValueType::Bool) && this->as() == v; + return type == ValueType::Bool && asBool() == v; } template [[nodiscard]] auto visit(Visitor fn) const { - if (type == ValueType::Undef) + switch (type) { + case ValueType::Undef: return fn(UndefinedType{}); - if (type == ValueType::Null) + case ValueType::Null: return fn(NullType{}); - if (type == ValueType::Bool) - return fn(this->template as()); - if (type == ValueType::Int) + case ValueType::Bool: + return fn(asBool()); + case ValueType::Int: return fn(this->template as()); - if (type == ValueType::Float) + case ValueType::Float: return fn(this->template as()); - if (type == ValueType::String) + case ValueType::String: return fn(this->template as()); - if (type == ValueType::TransientObject) + case ValueType::TransientObject: return fn(this->template as()); - if (type == ValueType::Object || type == ValueType::Array) { - if (node) return fn(*node); - else return fn(NullType{}); + case ValueType::Object: + case ValueType::Array: + if (auto nodePtr = std::get_if(&value); nodePtr && *nodePtr) [[likely]] + return fn(**nodePtr); + else + return fn(NullType{}); } return fn(UndefinedType{}); } @@ -367,15 +421,16 @@ class Value if (isa(ValueType::TransientObject)) { const auto& obj = std::get(value); if (obj.meta) { - if (Value vv = obj.meta->unaryOp("string", obj); vv.isa(ValueType::String)) - return vv.as(); + if (auto vv = obj.meta->unaryOp("string", obj); vv && vv->isa(ValueType::String)) + return vv->template as(); return "<"s + obj.meta->ident + ">"s; } } return visit(impl::ValueToString()); } - [[nodiscard]] auto getScalar() { + [[nodiscard]] auto getScalar() noexcept + { struct { void operator() (std::monostate const& v) {result = v;} void operator() (bool const& v) {result = v;} @@ -384,17 +439,30 @@ class Value void operator() (std::string const& v) {result = v;} void operator() (std::string_view const& v) {result = v;} void operator() (TransientObject const&) {} + void operator() (ModelNode::Ptr const&) {} ScalarValueType result; } scalarVisitor; std::visit(scalarVisitor, value); return scalarVisitor.result; } - /// Get the string_view of this Value if it has one. - std::string_view const* stringViewValue() { + std::string_view const* stringViewValue() noexcept + { return std::get_if(&value); } + ModelNode::Ptr const* nodePtr() const noexcept + { + return std::get_if(&value); + } + + ModelNode const* node() const noexcept + { + if (auto ptr = nodePtr(); ptr) + return &**ptr; + return nullptr; + } + ValueType type; std::variant< std::monostate, @@ -403,9 +471,8 @@ class Value double, std::string, std::string_view, - TransientObject> value; - - ModelNode::Ptr node; + TransientObject, + ModelNode::Ptr> value; }; template diff --git a/repl/repl.cpp b/repl/repl.cpp index ddd6c04b..6239a212 100644 --- a/repl/repl.cpp +++ b/repl/repl.cpp @@ -79,13 +79,12 @@ char* command_generator(const char* text, int state) if (current_env) { simfil::CompletionOptions opts; opts.limit = 25; - opts.autoWildcard = options.auto_wildcard; auto root = model->root(0); if (!root) return nullptr; - auto comp = simfil::complete(*current_env, query, rl_point, *root, opts); + auto comp = simfil::complete(*current_env, query, rl_point, **root, opts); if (!comp) return nullptr; @@ -142,7 +141,11 @@ static auto eval_mt(simfil::Environment& env, const simfil::AST& ast, const std: if (!model->numRoots()) { model->addRoot(simfil::ModelNode::Ptr()); - auto res = simfil::eval(env, ast, *model->root(0), &diag); + auto root = model->root(0); + if (!root) + return result; + + auto res = simfil::eval(env, ast, **root, &diag); if (res) result[0] = std::move(*res); // TODO: Output eval errors @@ -163,7 +166,13 @@ static auto eval_mt(simfil::Environment& env, const simfil::AST& ast, const std: size_t next; while ((next = idx++) < model->numRoots()) { try { - auto res = simfil::eval(env, ast, *model->root(next), &diag); + auto root = model->root(next); + if (!root) { + std::cerr << "Error: " << root.error().message << "\n"; + continue; + } + + auto res = simfil::eval(env, ast, **root, &diag); if (res) result[next] = std::move(*res); // TODO: Output eval errors @@ -226,7 +235,8 @@ int main(int argc, char *argv[]) #if defined(SIMFIL_WITH_MODEL_JSON) std::cout << "Parsing " << filename << "\n"; auto f = std::ifstream(std::string(filename)); - simfil::json::parse(f, model); + if (auto res = simfil::json::parse(f, model); !res) + std::cerr << "Error: " << res.error().message << "\n"; #endif }; diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..8e27eddf --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,2 @@ +# Exclude from duplicate lines/blocks check +sonar.cpd.exclusions=test/simfil.cpp diff --git a/src/completion.cpp b/src/completion.cpp index eceb1d38..21e46e68 100644 --- a/src/completion.cpp +++ b/src/completion.cpp @@ -1,6 +1,7 @@ #include "completion.h" #include "expressions.h" +#include "simfil/model/string-pool.h" #include "simfil/result.h" #include "simfil/simfil.h" #include "simfil/function.h" @@ -133,10 +134,10 @@ CompletionFieldOrWordExpr::CompletionFieldOrWordExpr(std::string prefix, Complet auto CompletionFieldOrWordExpr::type() const -> Type { - return Type::PATH; + return Type::FIELD; } -auto CompletionFieldOrWordExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto CompletionFieldOrWordExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { if (ctx.phase == Context::Phase::Compilation) return res(ctx, Value::undef()); @@ -144,19 +145,27 @@ auto CompletionFieldOrWordExpr::ieval(Context ctx, const Value& val, const Resul if (val.isa(ValueType::Undef)) return res(ctx, val); + const auto node = val.node(); + if (!node) + return res(ctx, val); + const auto caseSensitive = comp_->options.smartCase && containsUppercaseCharacter(prefix_); // First we try to complete fields - for (StringId id : val.node->fieldNames()) { - if (comp_->size() >= comp_->limit) + for (StringId id : node->fieldNames()) { + if (comp_->size() >= comp_->limit) { return Result::Stop; + } + + if (id == StringPool::Empty) + continue; auto keyPtr = ctx.env->strings()->resolve(id); if (!keyPtr || keyPtr->empty()) continue; const auto& key = *keyPtr; - if (key.size() >= prefix_.size() && startsWith(key, prefix_, caseSensitive)) { + if (startsWith(key, prefix_, caseSensitive)) { if (needsEscaping(key)) { comp_->add(escapeKey(key), sourceLocation(), CompletionCandidate::Type::FIELD); } else { @@ -245,7 +254,7 @@ auto CompletionAndExpr::type() const -> Type return Type::VALUE; } -auto CompletionAndExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto CompletionAndExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { if (left_) (void)left_->eval(ctx, val, LambdaResultFn([](const Context&, const Value&) { @@ -305,7 +314,7 @@ auto CompletionOrExpr::type() const -> Type return Type::VALUE; } -auto CompletionOrExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto CompletionOrExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { if (left_) (void)left_->eval(ctx, val, LambdaResultFn([](const Context&, const Value&) { @@ -352,7 +361,12 @@ auto CompletionWordExpr::type() const -> Type return Type::VALUE; } -auto CompletionWordExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto CompletionWordExpr::constant() const -> bool +{ + return true; +} + +auto CompletionWordExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { if (ctx.phase == Context::Phase::Compilation) return res(ctx, Value::undef()); @@ -378,4 +392,21 @@ auto CompletionWordExpr::accept(ExprVisitor& v) -> void v.visit(*this); } +auto CompletionConstExpr::constant() const -> bool +{ + return false; +} + +auto CompletionConstExpr::clone() const -> ExprPtr +{ + return std::make_unique(value_); +} + +auto CompletionConstExpr::ieval(Context ctx, const Value&, const ResultFn& res) -> tl::expected +{ + if (ctx.phase == Context::Compilation) + return res(ctx, Value::undef()); + return res(ctx, value_); +} + } diff --git a/src/completion.h b/src/completion.h index d6842799..17eca844 100644 --- a/src/completion.h +++ b/src/completion.h @@ -1,10 +1,10 @@ #pragma once -#include "simfil/expression.h" #include "simfil/token.h" #include "simfil/environment.h" -#include +#include "expressions.h" + #include #include #include @@ -48,7 +48,7 @@ class CompletionFieldOrWordExpr : public Expr CompletionFieldOrWordExpr(std::string prefix, Completion* comp, const Token& token, bool inPath); auto type() const -> Type override; - auto ieval(Context ctx, const Value& value, const ResultFn& result) -> Result override; + auto ieval(Context ctx, const Value& value, const ResultFn& result) -> tl::expected override; auto clone() const -> std::unique_ptr override; auto accept(ExprVisitor& v) -> void override; auto toString() const -> std::string override; @@ -64,7 +64,7 @@ class CompletionAndExpr : public Expr CompletionAndExpr(ExprPtr left, ExprPtr right, const Completion* comp); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; void accept(ExprVisitor& v) override; auto clone() const -> ExprPtr override; auto toString() const -> std::string override; @@ -78,7 +78,7 @@ class CompletionOrExpr : public Expr CompletionOrExpr(ExprPtr left, ExprPtr right, const Completion* comp); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; void accept(ExprVisitor& v) override; auto clone() const -> ExprPtr override; auto toString() const -> std::string override; @@ -92,7 +92,8 @@ class CompletionWordExpr : public Expr CompletionWordExpr(std::string prefix, Completion* comp, const Token& token); auto type() const -> Type override; - auto ieval(Context ctx, const Value& value, const ResultFn& result) -> Result override; + auto constant() const -> bool override; + auto ieval(Context ctx, const Value& value, const ResultFn& result) -> tl::expected override; auto clone() const -> std::unique_ptr override; auto accept(ExprVisitor& v) -> void override; auto toString() const -> std::string override; @@ -101,4 +102,18 @@ class CompletionWordExpr : public Expr Completion* comp_; }; +/** + * A special expression to prevent constant value + * evaluation during completion. + */ +class CompletionConstExpr : public ConstExpr +{ +public: + using ConstExpr::ConstExpr; + + auto constant() const -> bool override; + auto clone() const -> ExprPtr override; + auto ieval(Context ctx, const Value&, const ResultFn& res) -> tl::expected override; +}; + } diff --git a/src/diagnostics.cpp b/src/diagnostics.cpp index 3f1620a6..2929bb87 100644 --- a/src/diagnostics.cpp +++ b/src/diagnostics.cpp @@ -2,9 +2,10 @@ #include "simfil/diagnostics.h" -#include "levenshtein.h" #include "expressions.h" +#include +#include #include #include #include @@ -111,31 +112,6 @@ auto Diagnostics::read(std::istream& stream) -> tl::expected return {}; } - -static auto findSimilarString(std::string_view source, const StringPool& pool) -> std::string -{ - std::string_view best; - auto bestScore = std::numeric_limits::max(); - - const auto isDollar = source[0] == '$'; - for (const auto& target : pool.strings()) { - const auto targetIsDollar = target[0] == '$'; - if (isDollar != targetIsDollar) - continue; - if (target == source) - continue; - - const auto score = levenshtein(source, target); - if (score < bestScore) { - bestScore = score; - best = target; - } - } - - return std::string(best); -} - - auto Diagnostics::buildMessages(Environment& env, const AST& ast) const -> std::vector { struct Visitor : ExprVisitor @@ -164,17 +140,7 @@ auto Diagnostics::buildMessages(Environment& env, const AST& ast) const -> std:: if (iter->second > 0) return; - // Generate "did you mean ...?" messages for missing fields - auto guess = findSimilarString(e.name_, *env.strings()); - if (!guess.empty()) { - std::string fix = ast.query(); - if (auto loc = e.sourceLocation(); loc.size > 0) - fix.replace(loc.offset, loc.size, guess); - - addMessage(fmt::format("No matches for field '{}'. Did you mean '{}'?", e.name_, guess), e, fix); - } else { - addMessage(fmt::format("No matches for field '{}'.", e.name_, guess), e, {}); - } + addMessage(fmt::format("No matches for field '{}'.", e.name_), e, {}); } void visitComparisonOperator(ComparisonExprBase& e, bool expectedResult) diff --git a/src/expected.h b/src/expected.h index 2c1d4cc5..93ee1f36 100644 --- a/src/expected.h +++ b/src/expected.h @@ -1,11 +1,11 @@ #pragma once -#include "tl/expected.hpp" +#include // Helper macro for bubbling-up tl::expected errors. #define TRY_EXPECTED(res) \ do { \ - if (!(res).has_value()) { \ + if (!(res).has_value()) [[unlikely]] { \ return tl::unexpected(std::move((res).error())); \ } \ } while (false) diff --git a/src/expression-patterns.h b/src/expression-patterns.h new file mode 100644 index 00000000..58fdd698 --- /dev/null +++ b/src/expression-patterns.h @@ -0,0 +1,110 @@ +#pragma once + +#include "expressions.h" +#include "simfil/expression.h" +#include "simfil/model/nodes.h" +#include "src/completion.h" + +#include +#include + +namespace simfil +{ + +inline auto isSingleValueExpression(const Expr* expr) -> bool { + if (!expr) + return false; + + if (dynamic_cast(expr)) + return true; + + if (auto* v = dynamic_cast(expr)) + return true; + + return false; +} + +/** + * Checks if the root expression is a single (constant) value + * or a field. + */ +inline auto isSingleValueOrFieldExpression(const Expr* expr) -> bool { + if (!expr) + return false; + + if (expr->type() == Expr::Type::FIELD) + return true; + + if (dynamic_cast(expr)) + return true; + + if (auto* v = dynamic_cast(expr)) { + const auto& value = v->value(); + if (value.isa(ValueType::String)) { + auto str = value.as(); + auto loc = std::locale(); + return std::ranges::all_of(str, [&](auto c) { + return c == '_' || std::isupper(c, loc); + }) && !str.empty(); + } + } + + return false; +} + +/** + * Checks if the root expression is a comparison of the following form: + * ` ` + */ +inline auto isFieldComparison(const Expr* expr) -> bool { + if (!expr) + return false; + + auto checkComparison = [](const auto* compExpr) -> bool { + if (!compExpr) + return false; + + auto leftIsFieldOrEnum = false; + if (const auto* left = dynamic_cast(compExpr->left_.get())) { + leftIsFieldOrEnum = true; + } else if (const auto* left = dynamic_cast(compExpr->left_.get())) { + // Test if the value is a WORD. + // This is not optimal + const auto& value = left->value(); + if (value.isa(ValueType::String)) { + auto str = value.as(); + auto loc = std::locale(); + leftIsFieldOrEnum = std::ranges::all_of(str, [&](auto c) { + return c == '_' || std::isupper(c, loc); + }) && str.size() > 0; + } + } + + auto rightIsConstant = compExpr->right_->constant(); + + return leftIsFieldOrEnum && rightIsConstant; + }; + + if (auto* e = dynamic_cast*>(expr)) { + return checkComparison(e); + } + if (auto* e = dynamic_cast*>(expr)) { + return checkComparison(e); + } + if (auto* e = dynamic_cast*>(expr)) { + return checkComparison(e); + } + if (auto* e = dynamic_cast*>(expr)) { + return checkComparison(e); + } + if (auto* e = dynamic_cast*>(expr)) { + return checkComparison(e); + } + if (auto* e = dynamic_cast*>(expr)) { + return checkComparison(e); + } + + return false; +} + +} diff --git a/src/expressions.cpp b/src/expressions.cpp index 42fe857b..ce3f03f8 100644 --- a/src/expressions.cpp +++ b/src/expressions.cpp @@ -1,11 +1,16 @@ #include "expressions.h" +#include "fmt/format.h" #include "simfil/environment.h" #include "simfil/result.h" #include "simfil/value.h" #include "simfil/function.h" #include "fmt/core.h" +#include "fmt/ranges.h" +#include "src/expected.h" +#include +#include namespace simfil { @@ -32,7 +37,14 @@ struct CountedResultFn : ResultFn CountedResultFn(const CountedResultFn&) = delete; CountedResultFn(CountedResultFn&&) = delete; - auto operator()(Context ctx, Value vv) const -> Result override + auto operator()(Context ctx, const Value& vv) const noexcept -> tl::expected override + { + assert(!finished); + ++calls; + return fn(ctx, vv); + } + + auto operator()(Context ctx, Value&& vv) const noexcept -> tl::expected override { assert(!finished); ++calls; @@ -59,7 +71,7 @@ auto boolify(const Value& v) -> bool * Undef if any argument is Undef. */ if (v.isa(ValueType::Undef)) return false; - return UnaryOperatorDispatcher::dispatch(v).as(); + return UnaryOperatorDispatcher::dispatch(v).value_or(Value::f()).as(); } } @@ -71,7 +83,7 @@ auto WildcardExpr::type() const -> Type return Type::PATH; } -auto WildcardExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result +auto WildcardExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected { if (ctx.phase == Context::Phase::Compilation) return ores(ctx, Value::undef()); @@ -83,28 +95,37 @@ auto WildcardExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> Context& ctx; ResultFn& res; - auto iterate(ModelNode const& val, int depth) + [[nodiscard]] auto iterate(ModelNode const& val) noexcept -> tl::expected { - if (val.type() == ValueType::Null) + if (val.type() == ValueType::Null) [[unlikely]] return Result::Continue; - if (res(ctx, Value::field(val)) == Result::Stop) - return Result::Stop; + auto result = res(ctx, Value::field(val)); + TRY_EXPECTED(result); + if (*result == Result::Stop) [[unlikely]] + return *result; - auto result = Result::Continue; + tl::expected finalResult = Result::Continue; val.iterate(ModelNode::IterLambda([&, this](const auto& subNode) { - if (iterate(subNode, depth + 1) == Result::Stop) { - result = Result::Stop; + auto subResult = iterate(subNode); + if (!subResult) { + finalResult = std::move(subResult); + return false; + } + + if (*subResult == Result::Stop) { + finalResult = Result::Stop; return false; } + return true; })); - return result; - }; + return finalResult; + } }; - auto r = Iterate{ctx, res}.iterate(*val.node, 0); + auto r = val.nodePtr() ? Iterate{ctx, res}.iterate(**val.nodePtr()) : tl::expected(Result::Continue); res.ensureCall(); return r; } @@ -121,7 +142,7 @@ void WildcardExpr::accept(ExprVisitor& v) auto WildcardExpr::toString() const -> std::string { - return "**"; + return "**"s; } AnyChildExpr::AnyChildExpr() = default; @@ -131,25 +152,28 @@ auto AnyChildExpr::type() const -> Type return Type::PATH; } -auto AnyChildExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto AnyChildExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { if (ctx.phase == Context::Phase::Compilation) return res(ctx, Value::undef()); - if (!val.node || !val.node->size()) + if (!val.node() || !val.node()->size()) return res(ctx, Value::null()); - auto result = Result::Continue; - val.node->iterate(ModelNode::IterLambda([&](auto subNode) { - if (res(ctx, Value::field(std::move(subNode))) == Result::Stop) { - result = Result::Stop; + std::optional error; + val.node()->iterate(ModelNode::IterLambda([&error, &ctx, &res](auto subNode) -> bool { + auto result = res(ctx, Value::field(std::move(subNode))); + if (!result) { + error = std::move(result.error()); return false; } + if (*result == Result::Stop) + return false; return true; })); - - return Result::Continue; - //return result; + if (error) + return tl::unexpected(std::move(*error)); + return Result::Continue; } auto AnyChildExpr::clone() const -> ExprPtr @@ -164,7 +188,7 @@ void AnyChildExpr::accept(ExprVisitor& v) auto AnyChildExpr::toString() const -> std::string { - return "*"; + return "*"s; } FieldExpr::FieldExpr(std::string name) @@ -184,34 +208,39 @@ FieldExpr::FieldExpr(std::string name, const Token& token) auto FieldExpr::type() const -> Type { - return Type::PATH; + return Type::FIELD; +} + +auto FieldExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected +{ + return ieval(ctx, Value{val}, res); } -auto FieldExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto FieldExpr::ieval(Context ctx, Value&& val, const ResultFn& res) -> tl::expected { if (ctx.phase != Context::Compilation) evaluations_++; if (val.isa(ValueType::Undef)) - return res(ctx, val); + return res(ctx, std::move(val)); /* Special case: _ points to the current node */ if (name_ == "_") - return res(ctx, val); + return res(ctx, std::move(val)); - if (!val.node) + if (!val.node()) return res(ctx, Value::null()); - if (!nameId_) + if (!nameId_) [[unlikely]] { nameId_ = ctx.env->strings()->get(name_); - - if (!nameId_) - /* If the field name is not in the string cache, then there - is no field with that name. */ - return res(ctx, Value::null()); + if (!nameId_) + /* If the field name is not in the string cache, then there + is no field with that name. */ + return res(ctx, Value::null()); + } /* Enter sub-node */ - if (auto sub = val.node->get(nameId_)) { + if (auto sub = val.node()->get(nameId_)) { hits_++; return res(ctx, Value::field(*sub)); } @@ -254,10 +283,12 @@ auto MultiConstExpr::constant() const -> bool return true; } -auto MultiConstExpr::ieval(Context ctx, const Value&, const ResultFn& res) -> Result +auto MultiConstExpr::ieval(Context ctx, const Value&, const ResultFn& res) -> tl::expected { for (const auto& v : values_) { - if (res(ctx, v) == Result::Stop) + auto r = res(ctx, v); + TRY_EXPECTED(r); + if (*r == Result::Stop) return Result::Stop; } @@ -276,14 +307,11 @@ void MultiConstExpr::accept(ExprVisitor& v) auto MultiConstExpr::toString() const -> std::string { - auto list = ""s; - for (const auto& v : values_) { - if (!list.empty()) - list += " "; - list += v.toString(); - } + auto items = values_ | std::views::transform([](const auto& arg) { + return arg.toString(); + }); - return "{"s + list + "}"s; + return fmt::format("{{{}}}", fmt::join(items, " ")); } ConstExpr::ConstExpr(Value value) @@ -300,7 +328,7 @@ auto ConstExpr::constant() const -> bool return true; } -auto ConstExpr::ieval(Context ctx, const Value&, const ResultFn& res) -> Result +auto ConstExpr::ieval(Context ctx, const Value&, const ResultFn& res) -> tl::expected { return res(ctx, value_); } @@ -318,10 +346,15 @@ void ConstExpr::accept(ExprVisitor& v) auto ConstExpr::toString() const -> std::string { if (value_.isa(ValueType::String)) - return "\""s + value_.toString() + "\""s; + return fmt::format("\"{}\"", value_.toString()); return value_.toString(); } +auto ConstExpr::value() const -> const Value& +{ + return value_; +} + SubscriptExpr::SubscriptExpr(ExprPtr left, ExprPtr index) : left_(std::move(left)) , index_(std::move(index)) @@ -332,26 +365,25 @@ auto SubscriptExpr::type() const -> Type return Type::SUBSCRIPT; } -auto SubscriptExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result +auto SubscriptExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected { auto res = CountedResultFn(ores, ctx); - - auto r = left_->eval(ctx, val, LambdaResultFn([this, &val, &res](Context ctx, Value lval) { - return index_->eval(ctx, val, LambdaResultFn([this, &res, &lval](Context ctx, const Value& ival) { + auto r = left_->eval(ctx, val, LambdaResultFn([this, &val, &res](Context ctx, const Value& lval) { + return index_->eval(ctx, val, LambdaResultFn([this, &res, &lval](Context ctx, const Value& ival) -> tl::expected { /* Field subscript */ - if (lval.node) { + if (lval.node()) { ModelNode::Ptr node; /* Array subscript */ if (ival.isa(ValueType::Int)) { auto index = ival.as(); - node = lval.node->at(index); + node = lval.node()->at(index); } /* String subscript */ else if (ival.isa(ValueType::String)) { auto key = ival.as(); if (auto keyStrId = ctx.env->strings()->get(key)) - node = lval.node->get(keyStrId); + node = lval.node()->get(keyStrId); } if (node) @@ -359,12 +391,15 @@ auto SubscriptExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) - else ctx.env->warn("Invalid subscript index type "s + valueType2String(ival.type), this->toString()); } else { - return res(ctx, BinaryOperatorDispatcher::dispatch(lval, ival)); + auto v = BinaryOperatorDispatcher::dispatch(lval, ival); + TRY_EXPECTED(v); + return res(ctx, std::move(v.value())); } return Result::Continue; })); })); + TRY_EXPECTED(r); res.ensureCall(); return r; } @@ -381,7 +416,7 @@ void SubscriptExpr::accept(ExprVisitor& v) auto SubscriptExpr::toString() const -> std::string { - return "(index "s + left_->toString() + " "s + index_->toString() + ")"s; + return fmt::format("(index {} {})", left_->toString(), index_->toString()); } SubExpr::SubExpr(ExprPtr sub) @@ -399,18 +434,24 @@ auto SubExpr::type() const -> Type return Type::SUBEXPR; } -auto SubExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result +auto SubExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected +{ + return ieval(ctx, Value{val}, ores); +} + +auto SubExpr::ieval(Context ctx, Value&& val, const ResultFn& ores) -> tl::expected { /* Do not return null unless we have _no_ matching value. */ auto res = CountedResultFn(ores, ctx); - auto r = left_->eval(ctx, val, LambdaResultFn([this, &res](Context ctx, const Value& lv) { - return sub_->eval(ctx, lv, LambdaResultFn([&res, &lv](const Context& ctx, const Value& vv) { + auto r = left_->eval(ctx, val, LambdaResultFn([this, &res](Context ctx, const Value& lv) -> tl::expected { + return sub_->eval(ctx, lv, LambdaResultFn([&res, &lv](const Context& ctx, const Value& vv) -> tl::expected { auto bv = UnaryOperatorDispatcher::dispatch(vv); - if (bv.isa(ValueType::Undef)) + TRY_EXPECTED(bv); + if (bv->isa(ValueType::Undef)) return Result::Continue; - if (bv.isa(ValueType::Bool) && bv.as()) + if (bv->isa(ValueType::Bool) && bv->template as()) return res(ctx, lv); return Result::Continue; @@ -422,7 +463,7 @@ auto SubExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> Resu auto SubExpr::toString() const -> std::string { - return "(sub "s + left_->toString() + " "s + sub_->toString() + ")"s; + return fmt::format("(sub {} {})", left_->toString(), sub_->toString()); } auto SubExpr::clone() const -> ExprPtr @@ -444,14 +485,14 @@ auto AnyExpr::type() const -> Type return Type::VALUE; } -auto AnyExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto AnyExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { auto subctx = ctx; auto result = false; /* At least one value is true */ auto undef = false; /* At least one value is undef */ for (const auto& arg : args_) { - arg->eval(ctx, val, LambdaResultFn([&](Context, const Value& vv) { + auto res = arg->eval(ctx, val, LambdaResultFn([&](Context, const Value& vv) { if (ctx.phase == Context::Phase::Compilation) { if (vv.isa(ValueType::Undef)) { undef = true; @@ -462,7 +503,7 @@ auto AnyExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Resul result = result || boolify(vv); return result ? Result::Stop : Result::Continue; })); - + TRY_EXPECTED(res); if (result || undef) break; } @@ -498,11 +539,11 @@ auto AnyExpr::toString() const -> std::string if (args_.empty()) return "any()"s; - auto s = "(any"s; - for (const auto& arg : args_) { - s += " "s + arg->toString(); - } - return s + ")"s; + auto items = args_ | std::views::transform([](const auto& arg) { + return arg->toString(); + }); + + return fmt::format("(any {})", fmt::join(items, " ")); } EachExpr::EachExpr(std::vector args) @@ -514,14 +555,14 @@ auto EachExpr::type() const -> Type return Type::VALUE; } -auto EachExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto EachExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { auto subctx = ctx; auto result = true; /* All values are true */ auto undef = false; /* At least one value is undef */ for (const auto& arg : args_) { - arg->eval(ctx, val, LambdaResultFn([&](Context, const Value& vv) { + auto argRes = arg->eval(ctx, val, LambdaResultFn([&](Context, const Value& vv) { if (ctx.phase == Context::Phase::Compilation) { if (vv.isa(ValueType::Undef)) { undef = true; @@ -531,7 +572,7 @@ auto EachExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Resu result = result && boolify(vv); return result ? Result::Continue : Result::Stop; })); - + TRY_EXPECTED(argRes); if (!result || undef) break; } @@ -567,11 +608,11 @@ auto EachExpr::toString() const -> std::string if (args_.empty()) return "each()"s; - auto s = "(each"s; - for (const auto& arg : args_) { - s += " "s + arg->toString(); - } - return s + ")"s; + auto items = args_ | std::views::transform([](const auto& arg) { + return arg->toString(); + }); + + return fmt::format("(each {})", fmt::join(items, " ")); } CallExpression::CallExpression(std::string name, std::vector args) @@ -584,21 +625,29 @@ auto CallExpression::type() const -> Type return Type::VALUE; } -auto CallExpression::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto CallExpression::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { - if (!fn_) + return ieval(ctx, Value{val}, res); +} + +auto CallExpression::ieval(Context ctx, Value&& val, const ResultFn& res) -> tl::expected +{ + if (!fn_) [[unlikely]] { fn_ = ctx.env->findFunction(name_); - if (!fn_) - raise("Unknown function "s + name_); + if (!fn_) + return tl::unexpected(Error::UnknownFunction, fmt::format("Unknown function '{}'", name_)); + } auto anyval = false; - auto result = fn_->eval(ctx, val, args_, LambdaResultFn([&res, &anyval](const Context& ctx, Value vv) { + auto result = fn_->eval(ctx, std::move(val), args_, LambdaResultFn([&res, &anyval](const Context& ctx, Value&& vv) { anyval = true; return res(ctx, std::move(vv)); })); - + if (!result) + return result; if (!anyval) - return res(ctx, Value::null()); /* Expressions _must_ return at least one value! */ + return tl::unexpected(Error::InternalError, "Function did not call result callback"); + return result; } @@ -621,13 +670,13 @@ void CallExpression::accept(ExprVisitor& v) auto CallExpression::toString() const -> std::string { if (args_.empty()) - return "("s + name_ + ")"s; + return fmt::format("({})", name_); - std::string s = "("s + name_; - for (const auto& arg : args_) { - s += " "s + arg->toString(); - } - return s + ")"s; + auto items = args_ | std::views::transform([](const auto& arg) { + return arg->toString(); + }); + + return fmt::format("({} {})", name_, fmt::join(items, " ")); } PathExpr::PathExpr(ExprPtr right) @@ -648,24 +697,29 @@ auto PathExpr::type() const -> Type return Type::PATH; } -auto PathExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result +auto PathExpr::ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected +{ + return ieval(ctx, Value{val}, ores); +} + +auto PathExpr::ieval(Context ctx, Value&& val, const ResultFn& ores) -> tl::expected { auto res = CountedResultFn(ores, ctx); - auto r = left_->eval(ctx, val, LambdaResultFn([this, &res](Context ctx, Value v) { + auto r = left_->eval(ctx, std::move(val), LambdaResultFn([this, &res](Context ctx, Value&& v) -> tl::expected { if (v.isa(ValueType::Undef)) return Result::Continue; - if (v.isa(ValueType::Null) && !v.node) + if (v.isa(ValueType::Null) && !v.node()) return Result::Continue; ++hits_; - return right_->eval(ctx, std::move(v), LambdaResultFn([this, &res](Context ctx, Value vv) { + return right_->eval(ctx, std::move(v), LambdaResultFn([this, &res](Context ctx, Value&& vv) -> tl::expected { if (vv.isa(ValueType::Undef)) return Result::Continue; - if (vv.isa(ValueType::Null) && !vv.node) + if (vv.isa(ValueType::Null) && !vv.node()) return Result::Continue; return res(ctx, std::move(vv)); @@ -687,7 +741,7 @@ void PathExpr::accept(ExprVisitor& v) auto PathExpr::toString() const -> std::string { - return "(. "s + left_->toString() + " "s + right_->toString() + ")"s; + return fmt::format("(. {} {})", left_->toString(), right_->toString()); } UnpackExpr::UnpackExpr(ExprPtr sub) @@ -699,10 +753,10 @@ auto UnpackExpr::type() const -> Type return Type::VALUE; } -auto UnpackExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto UnpackExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { auto anyval = false; - auto r = sub_->eval(ctx, val, LambdaResultFn([&res, &anyval](Context ctx, Value v) { + auto r = sub_->eval(ctx, val, LambdaResultFn([&res, &anyval](Context ctx, Value&& v) -> tl::expected { if (v.isa(ValueType::TransientObject)) { const auto& obj = v.as(); auto r = Result::Continue; @@ -715,11 +769,14 @@ auto UnpackExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Re return Result::Stop; } else { anyval = true; - if (res(ctx, std::move(v)) == Result::Stop) + auto r = res(ctx, std::move(v)); + TRY_EXPECTED(r); + if (*r == Result::Stop) return Result::Stop; } return Result::Continue; })); + TRY_EXPECTED(r); if (!anyval) r = res(ctx, Value::null()); @@ -738,7 +795,7 @@ void UnpackExpr::accept(ExprVisitor& v) auto UnpackExpr::toString() const -> std::string { - return "(... "s + sub_->toString() + ")"s; + return fmt::format("(... {})", sub_->toString()); } UnaryWordOpExpr::UnaryWordOpExpr(std::string ident, ExprPtr left) @@ -751,19 +808,21 @@ auto UnaryWordOpExpr::type() const -> Type return Type::VALUE; } -auto UnaryWordOpExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto UnaryWordOpExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { - return left_->eval(ctx, val, LambdaResultFn([this, &res](const Context& ctx, Value val) { + return left_->eval(ctx, val, LambdaResultFn([this, &res](const Context& ctx, Value&& val) -> tl::expected { if (val.isa(ValueType::Undef)) return res(ctx, std::move(val)); if (val.isa(ValueType::TransientObject)) { const auto& obj = val.as(); - return res(ctx, obj.meta->unaryOp(ident_, obj)); + auto v = obj.meta->unaryOp(ident_, obj); + TRY_EXPECTED(v); + return res(ctx, std::move(v.value())); } - raise(fmt::format("Invalid operator '{}' for value of type {}", - ident_, valueType2String(val.type))); + return tl::unexpected(Error::InvalidOperator, + fmt::format("Invalid operator '{}' for value of type {}", ident_, valueType2String(val.type))); })); } @@ -779,7 +838,7 @@ auto UnaryWordOpExpr::clone() const -> ExprPtr auto UnaryWordOpExpr::toString() const -> std::string { - return "("s + ident_ + " "s + left_->toString() + ")"s; + return fmt::format("({} {})", ident_, left_->toString()); } BinaryWordOpExpr::BinaryWordOpExpr(std::string ident, ExprPtr left, ExprPtr right) @@ -793,25 +852,30 @@ auto BinaryWordOpExpr::type() const -> Type return Type::VALUE; } -auto BinaryWordOpExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto BinaryWordOpExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { return left_->eval(ctx, val, LambdaResultFn([this, &res, &val](const Context& ctx, const Value& lval) { - return right_->eval(ctx, val, LambdaResultFn([this, &res, &lval](const Context& ctx, const Value& rval) { + return right_->eval(ctx, val, LambdaResultFn([this, &res, &lval](const Context& ctx, const Value& rval) -> tl::expected { if (lval.isa(ValueType::Undef) || rval.isa(ValueType::Undef)) return res(ctx, Value::undef()); if (lval.isa(ValueType::TransientObject)) { const auto& obj = lval.as(); - return res(ctx, obj.meta->binaryOp(ident_, obj, rval)); + auto v = obj.meta->binaryOp(ident_, obj, rval); + TRY_EXPECTED(v); + return res(ctx, std::move(v.value())); } if (rval.isa(ValueType::TransientObject)) { const auto& obj = rval.as(); - return res(ctx, obj.meta->binaryOp(ident_, lval, obj)); + auto v = obj.meta->binaryOp(ident_, lval, obj); + TRY_EXPECTED(v); + return res(ctx, std::move(v.value())); } - raise(fmt::format("Invalid operator '{}' for values of type {} and {}", - ident_, valueType2String(lval.type), valueType2String(rval.type))); + return tl::unexpected(Error::InvalidOperator, + fmt::format("Invalid operator '{}' for values of type {} and {}", + ident_, valueType2String(lval.type), valueType2String(rval.type))); })); })); } @@ -828,7 +892,7 @@ auto BinaryWordOpExpr::clone() const -> ExprPtr auto BinaryWordOpExpr::toString() const -> std::string { - return "("s + ident_ + " "s + left_->toString() + " "s + right_->toString() + ")"s; + return fmt::format("({} {} {})", ident_, left_->toString(), right_->toString()); } AndExpr::AndExpr(ExprPtr left, ExprPtr right) @@ -844,19 +908,21 @@ auto AndExpr::type() const -> Type return Type::VALUE; } -auto AndExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto AndExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { /* Operator and behaves like in lua: - * 'a and b' returns a if 'not a?' else b is returned */ - return left_->eval(ctx, val, LambdaResultFn([this, &res, &val](const Context& ctx, Value lval) { + * 'a and b' returns a if 'not a?' else b is returned */ + return left_->eval(ctx, val, LambdaResultFn([this, &res, &val](const Context& ctx, Value&& lval) -> tl::expected { if (lval.isa(ValueType::Undef)) return res(ctx, lval); - if (auto v = UnaryOperatorDispatcher::dispatch(lval); v.isa(ValueType::Bool)) - if (!v.as()) + auto v = UnaryOperatorDispatcher::dispatch(lval); + TRY_EXPECTED(v); + if (v->isa(ValueType::Bool)) + if (!v->template as()) return res(ctx, std::move(lval)); - return right_->eval(ctx, val, LambdaResultFn([&res](const Context& ctx, Value rval) { + return right_->eval(ctx, val, LambdaResultFn([&res](const Context& ctx, Value&& rval) { return res(ctx, std::move(rval)); })); })); @@ -874,7 +940,7 @@ auto AndExpr::clone() const -> ExprPtr auto AndExpr::toString() const -> std::string { - return "(and "s + left_->toString() + " "s + right_->toString() + ")"s; + return fmt::format("(and {} {})", left_->toString(), right_->toString()); } OrExpr::OrExpr(ExprPtr left, ExprPtr right) @@ -890,21 +956,24 @@ auto OrExpr::type() const -> Type return Type::VALUE; } -auto OrExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> Result +auto OrExpr::ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected { /* Operator or behaves like in lua: * 'a or b' returns a if 'a?' else b is returned */ - return left_->eval(ctx, val, LambdaResultFn([this, &res, &val](Context ctx, Value lval) { + return left_->eval(ctx, val, LambdaResultFn([this, &res, &val](Context ctx, Value&& lval) -> tl::expected { if (lval.isa(ValueType::Undef)) return res(ctx, lval); ++leftEvaluations_; - if (auto v = UnaryOperatorDispatcher::dispatch(lval); v.isa(ValueType::Bool)) - if (v.as()) + + auto v = UnaryOperatorDispatcher::dispatch(lval); + TRY_EXPECTED(v); + if (v->isa(ValueType::Bool)) + if (v->template as()) return res(ctx, std::move(lval)); ++rightEvaluations_; - return right_->eval(ctx, val, LambdaResultFn([&](Context ctx, Value rval) { + return right_->eval(ctx, val, LambdaResultFn([&](Context ctx, Value&& rval) { return res(ctx, std::move(rval)); })); })); @@ -923,7 +992,7 @@ auto OrExpr::clone() const -> ExprPtr auto OrExpr::toString() const -> std::string { - return "(or "s + left_->toString() + " "s + right_->toString() + ")"s; + return fmt::format("(or {} {})", left_->toString(), right_->toString()); } void ExprVisitor::visit(Expr& e) diff --git a/src/expressions.h b/src/expressions.h index 6bf22e20..96b3ea2e 100644 --- a/src/expressions.h +++ b/src/expressions.h @@ -76,7 +76,7 @@ class WildcardExpr : public Expr WildcardExpr(); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -91,7 +91,7 @@ class AnyChildExpr : public Expr AnyChildExpr(); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -104,7 +104,8 @@ class FieldExpr : public Expr FieldExpr(std::string name, const Token& token); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; + auto ieval(Context ctx, Value&& val, const ResultFn& res) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -126,7 +127,7 @@ class MultiConstExpr : public Expr auto type() const -> Type override; auto constant() const -> bool override; - auto ieval(Context ctx, const Value&, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value&, const ResultFn& res) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -146,11 +147,13 @@ class ConstExpr : public Expr auto type() const -> Type override; auto constant() const -> bool override; - auto ieval(Context ctx, const Value&, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value&, const ResultFn& res) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; + auto value() const -> const Value&; + protected: const Value value_; }; @@ -161,7 +164,7 @@ class SubscriptExpr : public Expr SubscriptExpr(ExprPtr left, ExprPtr index); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -177,7 +180,8 @@ class SubExpr : public Expr SubExpr(ExprPtr left, ExprPtr sub); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected override; + auto ieval(Context ctx, Value&& val, const ResultFn& ores) -> tl::expected override; auto toString() const -> std::string override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; @@ -191,7 +195,7 @@ class AnyExpr : public Expr explicit AnyExpr(std::vector args); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -209,7 +213,7 @@ class EachExpr : public Expr explicit EachExpr(std::vector args); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -227,7 +231,8 @@ class CallExpression : public Expr CallExpression(std::string name, std::vector args); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; + auto ieval(Context ctx, Value&& val, const ResultFn& res) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -244,7 +249,8 @@ class PathExpr : public Expr PathExpr(ExprPtr left, ExprPtr right); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected override; + auto ieval(Context ctx, Value&& val, const ResultFn& ores) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -266,7 +272,7 @@ class UnpackExpr : public Expr explicit UnpackExpr(ExprPtr sub); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; auto clone() const -> ExprPtr override; void accept(ExprVisitor& v) override; auto toString() const -> std::string override; @@ -290,10 +296,13 @@ class UnaryExpr : public Expr return Type::VALUE; } - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override { - return sub_->eval(ctx, val, LambdaResultFn([&](Context ctx, Value vv) { - return res(ctx, UnaryOperatorDispatcher::dispatch(std::move(vv))); + return sub_->eval(ctx, val, LambdaResultFn([&](Context ctx, Value vv) -> tl::expected { + auto v = UnaryOperatorDispatcher::dispatch(std::move(vv)); + if (!v) + return tl::unexpected(std::move(v.error())); + return res(ctx, std::move(v.value())); })); } @@ -339,15 +348,17 @@ class BinaryExpr : public Expr return Type::VALUE; } - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override { return left_->eval(ctx, val, LambdaResultFn([this, &res, &val](Context ctx, Value lv) { leftTypes_.set(lv.type); - return right_->eval(ctx, val, LambdaResultFn([this, &res, &lv](Context ctx, Value rv) { + return right_->eval(ctx, val, LambdaResultFn([this, &res, &lv](Context ctx, Value rv) -> tl::expected { rightTypes_.set(rv.type); - return res(ctx, BinaryOperatorDispatcher::dispatch(std::move(lv), - std::move(rv))); + auto v = BinaryOperatorDispatcher::dispatch(std::move(lv), std::move(rv)); + if (!v) + return tl::unexpected(std::move(v.error())); + return res(ctx, std::move(v.value())); })); })); } @@ -415,22 +426,25 @@ class ComparisonExpr : public ComparisonExprBase public: using ComparisonExprBase::ComparisonExprBase; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override { return left_->eval(ctx, val, LambdaResultFn([this, &res, &val](Context ctx, Value lv) { leftTypes_.set(lv.type); - return right_->eval(ctx, val, LambdaResultFn([this, &res, &lv](Context ctx, Value rv) { + return right_->eval(ctx, val, LambdaResultFn([this, &res, &lv](Context ctx, Value rv) -> tl::expected { rightTypes_.set(rv.type); auto operatorResult = BinaryOperatorDispatcher::dispatch(std::move(lv), std::move(rv)); - if (operatorResult.isa(ValueType::Bool)) { - if (operatorResult.template as()) + if (!operatorResult) + return tl::unexpected(std::move(operatorResult.error())); + + if (operatorResult->isa(ValueType::Bool)) { + if (operatorResult->template as()) ++trueResults_; else ++falseResults_; } - return res(ctx, operatorResult); + return res(ctx, std::move(operatorResult.value())); })); })); } @@ -495,7 +509,7 @@ class UnaryWordOpExpr : public Expr UnaryWordOpExpr(std::string ident, ExprPtr left); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; void accept(ExprVisitor& v) override; auto clone() const -> ExprPtr override; auto toString() const -> std::string override; @@ -510,7 +524,7 @@ class BinaryWordOpExpr : public Expr BinaryWordOpExpr(std::string ident, ExprPtr left, ExprPtr right); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; void accept(ExprVisitor& v) override; auto clone() const -> ExprPtr override; auto toString() const -> std::string override; @@ -525,7 +539,7 @@ class AndExpr : public Expr AndExpr(ExprPtr left, ExprPtr right); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; void accept(ExprVisitor& v) override; auto clone() const -> ExprPtr override; auto toString() const -> std::string override; @@ -539,7 +553,7 @@ class OrExpr : public Expr OrExpr(ExprPtr left, ExprPtr right); auto type() const -> Type override; - auto ieval(Context ctx, const Value& val, const ResultFn& res) -> Result override; + auto ieval(Context ctx, const Value& val, const ResultFn& res) -> tl::expected override; void accept(ExprVisitor& v) override; auto clone() const -> ExprPtr override; auto toString() const -> std::string override; diff --git a/src/function.cpp b/src/function.cpp index 84dc938c..9a57fd41 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -8,40 +8,14 @@ #include "simfil/types.h" #include "simfil/overlay.h" #include "fmt/core.h" +#include "tl/expected.hpp" +#include "expected.h" #include -#include namespace simfil { -using namespace std::string_literals; - -auto ArgumentCountError::what() const noexcept -> const char* -{ - if (min < max) - msg = fmt::format("{}: Expected {} to {} arguments; got {}", - fn->ident().ident, min, max, have); - else - msg = fmt::format("{}: Expected {} arguments; got {}", - fn->ident().ident, min, have); - return msg.c_str(); -} - -auto ArgumentValueCountError::what() const noexcept -> const char* -{ - msg = fmt::format("{}: Argument {} must be a single value", - fn->ident().ident, index); - return msg.c_str(); -} - -auto ArgumentTypeError::what() const noexcept -> const char* -{ - msg = fmt::format("{}: Expected argument {} to be of type {}; got {}", - fn->ident().ident, index, want, have); - return msg.c_str(); -} - namespace { @@ -53,27 +27,32 @@ struct ArgParser Context ctx; std::size_t idx = 0; bool anyUndef = false; + std::optional error; - ArgParser(const std::string& functionName, Context ctx, Value val, const std::vector& args, size_t idx = 0) - : functionName(functionName) + ArgParser(std::string functionName, Context ctx, Value val, const std::vector& args, size_t idx = 0) + : functionName(std::move(functionName)) , args(args) , value(std::move(val)) , ctx(ctx) , idx(idx) { if (args.size() < idx) - raise(functionName + ": too few arguments"s); + error = Error(Error::InvalidArguments, fmt::format("too few arguments for function {}", this->functionName)); } auto arg(const char* name, ValueType type, Value& outValue) -> ArgParser& { - if (args.size() <= idx) - raise(functionName + ": missing argument "s + name); + if (args.size() <= idx) { + error = Error(Error::InvalidArguments, fmt::format("missing argument {} for function {}", name, this->functionName)); + return *this; + } auto subctx = ctx; - args[idx]->eval(subctx, value, LambdaResultFn([&, n = 0](Context, Value vv) mutable { - if (++n > 1) - raise(functionName + ": argument "s + name + " must return a single value"s); + auto res = args[idx]->eval(subctx, value, LambdaResultFn([&, n = 0](Context, Value&& vv) mutable -> tl::expected { + if (++n > 1) [[unlikely]] { + return tl::unexpected(Error::ExpectedSingleValue, + fmt::format("expeted single argument value for argument {} for function {}", name, functionName)); + } if (vv.isa(ValueType::Undef)) { anyUndef = true; @@ -81,14 +60,19 @@ struct ArgParser return Result::Continue; } - if (!vv.isa(type)) - raise(functionName + ": invalid value type for argument '"s + name + "'"s); + if (!vv.isa(type)) [[unlikely]] { + return tl::unexpected(Error::TypeMissmatch, + fmt::format("invalid type for argument {} for function {}", name, functionName)); + } outValue = std::move(vv); return Result::Continue; })); + if (!res) [[unlikely]] + error = std::move(res.error()); + ++idx; return *this; } @@ -101,9 +85,11 @@ struct ArgParser } auto subctx = ctx; - args[idx]->eval(subctx, value, LambdaResultFn([&, n = 0](Context, Value vv) mutable { - if (++n > 1) - raise(fmt::format("{}: argument {} must return a single value", functionName, name)); + auto res = args[idx]->eval(subctx, value, LambdaResultFn([&, n = 0](Context, Value&& vv) mutable -> tl::expected { + if (++n > 1) { + return tl::unexpected(Error::ExpectedSingleValue, + fmt::format("{}: argument {} must return a single value", functionName, name)); + } if (vv.isa(ValueType::Undef)) { anyUndef = true; @@ -112,19 +98,24 @@ struct ArgParser } if (!vv.isa(type)) - raise(fmt::format("{}: invalid value type for argument", functionName, name)); + return tl::unexpected(Error::TypeMissmatch, + fmt::format("{}: invalid value type for argument", functionName, name)); outValue = std::move(vv); return Result::Continue; })); + if (!res) [[unlikely]] + error = std::move(res.error()); ++idx; return *this; } [[nodiscard]] - auto ok() const -> bool + auto ok() const -> tl::expected { + if (error) [[unlikely]] + return tl::unexpected(*error); return !anyUndef; } }; @@ -135,7 +126,7 @@ auto boolify(const Value& v) -> bool * Undef if any argument is Undef. */ if (v.isa(ValueType::Undef)) return false; - return UnaryOperatorDispatcher::dispatch(v).as(); + return UnaryOperatorDispatcher::dispatch(v).value_or(Value::f()).as(); } } @@ -152,17 +143,18 @@ auto CountFn::ident() const -> const FnInfo& return info; } -auto CountFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto CountFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { if (args.empty()) - raise("count(...) expects one argument; got "s + std::to_string(args.size())); + return tl::unexpected(Error::InvalidArguments, + fmt::format("function 'count' expects one argument, got {}", args.size())); auto subctx = ctx; auto undef = false; /* At least one value is undef */ int64_t count = 0; for (const auto& arg : args) { - arg->eval(ctx, val, LambdaResultFn([&](Context, const Value& vv) { + auto evalRes = arg->eval(ctx, val, LambdaResultFn([&](Context, const Value& vv) { if (ctx.phase == Context::Phase::Compilation) { if (vv.isa(ValueType::Undef)) { undef = true; @@ -172,6 +164,7 @@ auto CountFn::eval(Context ctx, Value val, const std::vector& args, con count += boolify(vv) ? 1 : 0; return Result::Continue; })); + TRY_EXPECTED(evalRes); if (undef) break; @@ -195,16 +188,17 @@ auto TraceFn::ident() const -> const FnInfo& return info; } -auto TraceFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto TraceFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { Value name = Value::undef(); Value limit = Value::undef(); - (void)ArgParser("trace", ctx, val, args, 1) + auto ok = ArgParser("trace", ctx, val, args, 1) /* Skip arg 0 */ .opt("limit", ValueType::Int, limit, Value::make(static_cast(-1))) .opt("name", ValueType::String, name, Value::make(args[0]->toString())) .ok(); + TRY_EXPECTED(ok); /* Never run in compilation phase */ if (ctx.phase == Context::Phase::Compilation) @@ -255,10 +249,11 @@ auto RangeFn::ident() const -> const FnInfo& return info; } -auto RangeFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto RangeFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { if (args.size() != 2) - raise("range(begin, end) expects 2 arguments; got "s + std::to_string(args.size())); + return tl::unexpected(Error::InvalidArguments, + fmt::format("function 'range' expects 2 arguments, got {}", args.size())); Value begin = Value::undef(); Value end = Value::undef(); @@ -267,8 +262,8 @@ auto RangeFn::eval(Context ctx, Value val, const std::vector& args, con .arg("begin", ValueType::Int, begin) .arg("end", ValueType::Int, end) .ok(); - - if (!ok) + TRY_EXPECTED(ok); + if (!ok.value()) [[unlikely]] return res(ctx, Value::undef()); auto ibegin = begin.as(); @@ -289,13 +284,14 @@ auto ReFn::ident() const -> const FnInfo& return info; } -auto ReFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto ReFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { if (args.size() != 1) - raise("re(expr) expects 1 arguments; got "s + std::to_string(args.size())); + return tl::unexpected(Error::InvalidArguments, + fmt::format("'re' expects 1 argument, got {}", args.size())); auto subctx = ctx; - return args[0]->eval(subctx, val, LambdaResultFn([&](Context, Value vv) { + return args[0]->eval(subctx, val, LambdaResultFn([&](Context, Value&& vv) -> tl::expected { if (vv.isa(ValueType::Undef)) return res(ctx, Value::undef()); @@ -307,7 +303,8 @@ auto ReFn::eval(Context ctx, Value val, const std::vector& args, const if (const auto obj = vv.as(); obj.meta == &ReType::Type) return res(ctx, std::move(vv)); - raise("re: invalid value type for argument 'expr'"s); + return tl::unexpected(Error::TypeMissmatch, + fmt::format("invalid type for argument 'expr' for function 're'")); })); } @@ -325,15 +322,17 @@ auto ArrFn::ident() const -> const FnInfo& } -auto ArrFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto ArrFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { if (args.empty()) return res(ctx, Value::null()); for (const auto& arg : args) { - if (arg->eval(ctx, val, LambdaResultFn([&res](Context ctx, Value vv) { + auto r = arg->eval(ctx, val, LambdaResultFn([&res](Context ctx, Value &&vv) { return res(ctx, std::move(vv)); - })) == Result::Stop) + })); + TRY_EXPECTED(r); + if (*r == Result::Stop) return Result::Stop; } @@ -398,7 +397,7 @@ ContainerType split(std::string_view what, } } -auto SplitFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto SplitFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { Value str = Value::undef(); Value sep = Value::undef(); @@ -409,14 +408,17 @@ auto SplitFn::eval(Context ctx, Value val, const std::vector& args, con .arg("separator", ValueType::String, sep) .opt("keepEmpty", ValueType::Bool, keepEmpty, Value::t()) .ok(); + TRY_EXPECTED(ok); auto subctx = ctx; - if (!ok) + if (!ok.value()) [[unlikely]] return res(subctx, Value::undef()); auto items = split(str.as(), sep.as(), !keepEmpty.as()); for (auto&& item : items) { - if (res(subctx, Value::make(std::move(item))) == Result::Stop) + auto r = res(subctx, Value::make(std::move(item))); + TRY_EXPECTED(r); + if (*r == Result::Stop) break; } @@ -436,7 +438,7 @@ auto SelectFn::ident() const -> const FnInfo& return info; } -auto SelectFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto SelectFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { Value idx = Value::undef(); Value cnt = Value::undef(); @@ -446,8 +448,9 @@ auto SelectFn::eval(Context ctx, Value val, const std::vector& args, co .arg("index", ValueType::Int, idx) .opt("limit", ValueType::Int, cnt, Value::make(static_cast(1))) .ok(); + TRY_EXPECTED(ok); - if (!ok) + if (!ok.value()) [[unlikely]] return res(ctx, Value::undef()); auto iidx = idx.as(); @@ -455,7 +458,7 @@ auto SelectFn::eval(Context ctx, Value val, const std::vector& args, co if (icnt <= 0) icnt = std::numeric_limits::max(); - auto result = args[0]->eval(ctx, val, LambdaResultFn([&, n = -1](Context ctx, Value vv) mutable { + auto result = args[0]->eval(ctx, val, LambdaResultFn([&, n = -1](Context ctx, Value&& vv) mutable -> tl::expected { ++n; if (ctx.phase == Context::Phase::Compilation) if (vv.isa(ValueType::Undef)) @@ -483,22 +486,28 @@ auto SumFn::ident() const -> const FnInfo& return info; } -auto SumFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto SumFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { if (args.empty() || args.size() > 3) - raise("sum: Expected at least 1 argument; got "s + std::to_string(args.size())); + return tl::unexpected(Error::InvalidArguments, + fmt::format("'sum' expects at least 1 argument, got {}", args.size())); Value sum = Value::make(static_cast(0)); Expr* subexpr = args.size() >= 2 ? args[1].get() : nullptr; Expr* initval = args.size() == 3 ? args[2].get() : nullptr; - if (initval) - (void)initval->eval(ctx, val, LambdaResultFn([&](Context ctx, Value vv) { + if (initval) { + auto initRes = initval->eval(ctx, val, LambdaResultFn([&sum](Context, Value&& vv) { sum = std::move(vv); return Result::Continue; })); - (void)args[0]->eval(ctx, val, LambdaResultFn([&, n = 0](Context ctx, Value vv) mutable { + TRY_EXPECTED(initRes); + if (sum.isa(ValueType::Undef)) + return res(ctx, sum); + } + + auto argRes = args[0]->eval(ctx, val, LambdaResultFn([&, n = 0](Context ctx, Value&& vv) mutable -> tl::expected { if (subexpr) { auto ov = model_ptr::make(vv); ov->set(StringPool::OverlaySum, sum); @@ -506,21 +515,25 @@ auto SumFn::eval(Context ctx, Value val, const std::vector& args, const ov->set(StringPool::OverlayIndex, Value::make(static_cast(n))); n += 1; - subexpr->eval(ctx, Value::field(ov), LambdaResultFn([&ov, &sum](auto ctx, auto vv) { + auto subRes = subexpr->eval(ctx, Value::field(ov), LambdaResultFn([&ov, &sum](auto ctx, Value&& vv) { ov->set(StringPool::OverlaySum, vv); sum = vv; return Result::Continue; })); + TRY_EXPECTED(subRes); } else { if (sum.isa(ValueType::Null)) { sum = std::move(vv); } else { - sum = BinaryOperatorDispatcher::dispatch(sum, vv); + auto newSum = BinaryOperatorDispatcher::dispatch(sum, vv); + TRY_EXPECTED(newSum); + sum = std::move(newSum.value()); } } return Result::Continue; })); + TRY_EXPECTED(argRes); return res(ctx, sum); } @@ -538,20 +551,23 @@ auto KeysFn::ident() const -> const FnInfo& return info; } -auto KeysFn::eval(Context ctx, Value val, const std::vector& args, const ResultFn& res) const -> Result +auto KeysFn::eval(Context ctx, const Value& val, const std::vector& args, const ResultFn& res) const -> tl::expected { if (args.size() != 1) - raise("keys: Expected 1 argument; got "s + std::to_string(args.size())); + return tl::unexpected(Error::InvalidArguments, + fmt::format("'keys' expects 1 argument got {}", args.size())); - auto result = args[0]->eval(ctx, val, LambdaResultFn([&res](Context ctx, Value vv) { + auto result = args[0]->eval(ctx, val, LambdaResultFn([&res](Context ctx, const Value& vv) -> tl::expected { if (ctx.phase == Context::Phase::Compilation) if (vv.isa(ValueType::Undef)) - return res(ctx, std::move(vv)); + return res(ctx, vv); - if (vv.node) - for (auto&& fieldName : vv.node->fieldNames()) { + if (vv.nodePtr()) + for (auto&& fieldName : vv.node()->fieldNames()) { if (auto key = ctx.env->stringPool->resolve(fieldName)) { - if (res(ctx, Value::strref(*key)) == Result::Stop) + auto r = res(ctx, Value::strref(*key)); + TRY_EXPECTED(r); + if (*r == Result::Stop) return Result::Stop; } } diff --git a/src/levenshtein.h b/src/levenshtein.h deleted file mode 100644 index 0b1e54ca..00000000 --- a/src/levenshtein.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace simfil -{ - -inline auto levenshtein(std::string_view a, std::string_view b) -> int -{ - if (a.size() > b.size()) - return levenshtein(b, a); - - auto lo = a.size(); - auto hi = b.size(); - - std::vector dist; - dist.resize(lo + 1); - std::ranges::generate(dist.begin(), dist.end(), [i = 0]() mutable { return i++; }); - - for (auto j = 1; j <= hi; ++j) { - auto p = dist[0]++; - - for (auto i = 1; i <= lo; ++i) { - auto s = dist[i]; - if (a[i - 1] == b[j - 1]) - dist[i] = p; - else - dist[i] = std::min(std::min(dist[i - 1], dist[i]), p) + 1; - p = s; - } - } - - return dist[lo]; -} - -} diff --git a/src/model/json.cpp b/src/model/json.cpp index 9248fb1e..c140bc7f 100644 --- a/src/model/json.cpp +++ b/src/model/json.cpp @@ -5,11 +5,13 @@ #include +#include "../expected.h" + namespace simfil::json { using json = nlohmann::json; -static ModelNode::Ptr build(const json& j, ModelPool & model) +static auto build(const json& j, ModelPool & model) -> tl::expected { switch (j.type()) { case json::value_t::null: @@ -23,45 +25,64 @@ static ModelNode::Ptr build(const json& j, ModelPool & model) case json::value_t::number_integer: return model.newValue(j.get()); case json::value_t::string: - return model.newValue((StringId)model.strings()->emplace(j.get())); + if (auto stringId = model.strings()->emplace(j.get()); stringId) + return model.newValue((StringId)*stringId); + else + return tl::unexpected(stringId.error()); default: break; } if (j.is_object()) { auto object = model.newObject(j.size()); - for (auto&& [key, value] : j.items()) - object->addField(key, build(value, model)); + for (auto&& [key, value] : j.items()) { + auto child = build(value, model); + TRY_EXPECTED(child); + object->addField(key, *child); + } return object; } if (j.is_array()) { auto array = model.newArray(j.size()); - for (const auto& value : j) - array->append(build(value, model)); + for (const auto& value : j) { + auto child = build(value, model); + TRY_EXPECTED(child); + array->append(*child); + } return array; } return {}; } -void parse(std::istream& input, ModelPoolPtr const& model) +auto parse(std::istream& input, ModelPoolPtr const& model) -> tl::expected { - model->addRoot(build(json::parse(input), *model)); - model->validate(); + auto root = build(json::parse(input), *model); + if (!root) + return tl::unexpected(root.error()); + model->addRoot(*root); + return model->validate(); } -void parse(const std::string& input, ModelPoolPtr const& model) +auto parse(const std::string& input, ModelPoolPtr const& model) -> tl::expected { - model->addRoot(build(json::parse(input), *model)); - model->validate(); + auto root = build(json::parse(input), *model); + if (!root) + return tl::unexpected(root.error()); + model->addRoot(*root); + return model->validate(); } -ModelPoolPtr parse(const std::string& input) +auto parse(const std::string& input) -> tl::expected { auto model = std::make_shared(); - model->addRoot(build(json::parse(input), *model)); - model->validate(); + auto root = build(json::parse(input), *model); + if (!root) + return tl::unexpected(root.error()); + model->addRoot(*root); + if (auto res = model->validate(); !res) + return tl::unexpected(res.error()); return model; } diff --git a/src/model/model.cpp b/src/model/model.cpp index 55344e05..8e0cdf23 100644 --- a/src/model/model.cpp +++ b/src/model/model.cpp @@ -14,11 +14,14 @@ #include #include #include +#include + +#include "../expected.h" namespace simfil { -void Model::resolve(const ModelNode& n, const ResolveFn& cb) const +tl::expected Model::resolve(const ModelNode& n, const ResolveFn& cb) const { switch (n.addr_.column()) { case Null: @@ -37,8 +40,10 @@ void Model::resolve(const ModelNode& n, const ResolveFn& cb) const cb(ValueNode(n)); break; default: - raise(fmt::format("Bad column reference: col={}", (uint16_t)n.addr_.column())); + return tl::unexpected(Error::RuntimeError, + fmt::format("Bad column reference: col={}", (uint16_t)n.addr_.column())); } + return {}; } struct ModelPool::Impl @@ -124,31 +129,28 @@ std::vector ModelPool::checkForErrors() const std::function validateModelNode = [&](ModelNode::Ptr node) { - try { - if (node->type() == ValueType::Object) { - if (node->addr().column() == Objects) - if (!validateArrayIndex(node->addr().index(), "object", impl_->columns_.objectMemberArrays_)) - return; - for (auto const& [fieldName, fieldValue] : node->fields()) { - validatePooledString(fieldName); - validateModelNode(fieldValue); - } - } - else if (node->type() == ValueType::Array) { - if (node->addr().column() == Arrays) - if (!validateArrayIndex(node->addr().index(), "arrays", impl_->columns_.arrayMemberArrays_)) - return; - for (auto const& member : *node) - validateModelNode(member); - } - else if (node->addr().column() == PooledString) { - validatePooledString(static_cast(node->addr().index())); + if (node->type() == ValueType::Object) { + if (node->addr().column() == Objects) + if (!validateArrayIndex(node->addr().index(), "object", impl_->columns_.objectMemberArrays_)) + return; + for (auto const& [fieldName, fieldValue] : node->fields()) { + validatePooledString(fieldName); + validateModelNode(fieldValue); } - resolve(*node, Lambda([](auto&&) {})); } - catch (std::exception& e) { - errors.emplace_back(e.what()); + else if (node->type() == ValueType::Array) { + if (node->addr().column() == Arrays) + if (!validateArrayIndex(node->addr().index(), "arrays", impl_->columns_.arrayMemberArrays_)) + return; + for (auto const& member : *node) + validateModelNode(member); } + else if (node->addr().column() == PooledString) { + validatePooledString(static_cast(node->addr().index())); + } + + if (auto res = resolve(*node, Lambda([](auto&&) {})); !res) + errors.emplace_back(res.error().message); }; // Validate objects @@ -161,18 +163,19 @@ std::vector ModelPool::checkForErrors() const // Validate roots for (auto i = 0; i < numRoots(); ++i) - validateModelNode(root(i)); + if (auto node = root(i)) + validateModelNode(*node); return errors; } -void ModelPool::validate() const +auto ModelPool::validate() const -> tl::expected { auto errors = checkForErrors(); if (!errors.empty()) { - raise( - fmt::format("Model Error(s): {}", fmt::join(errors, ", "))); + return tl::unexpected(Error::RuntimeError, fmt::format("Model Error(s): {}", fmt::join(errors, ", "))); } + return {}; } void ModelPool::clear() @@ -192,17 +195,14 @@ void ModelPool::clear() clear_and_shrink(columns.arrayMemberArrays_); } -void ModelPool::resolve(ModelNode const& n, ResolveFn const& cb) const +tl::expected ModelPool::resolve(ModelNode const& n, ResolveFn const& cb) const { - auto get = [&n](auto const& vec) -> auto& { + auto checkBounds = [&n](auto const& vec) -> std::optional { auto idx = n.addr_.index(); if (idx >= vec.size()) - raise( - fmt::format( - "Bad node reference: col={}, i={}", - (uint16_t)n.addr_.column(), idx - )); - return vec[idx]; + return Error(Error::RuntimeError, fmt::format("bad node reference: col={}, i={}", + (uint16_t)n.addr_.column(), idx)); + return {}; }; switch (n.addr_.column()) { @@ -215,17 +215,26 @@ void ModelPool::resolve(ModelNode const& n, ResolveFn const& cb) const break; } case Int64: { - auto& val = get(impl_->columns_.i64_); + if (auto err = checkBounds(impl_->columns_.i64_)) + return tl::unexpected(*err); + auto idx = n.addr().index(); + auto& val = impl_->columns_.i64_[idx]; cb(ValueNode(val, shared_from_this())); break; } case Double: { - auto& val = get(impl_->columns_.double_); + if (auto err = checkBounds(impl_->columns_.double_)) + return tl::unexpected(*err); + auto idx = n.addr().index(); + auto& val = impl_->columns_.double_[idx]; cb(ValueNode(val, shared_from_this())); break; } case String: { - auto& val = get(impl_->columns_.strings_); + auto idx = n.addr().index(); + if (auto err = checkBounds(impl_->columns_.strings_)) + return tl::unexpected(*err); + auto& val = impl_->columns_.strings_[idx]; cb(ValueNode( // TODO: Make sure that the string view is not turned into a string here. std::string_view(impl_->columns_.stringData_).substr(val.offset_, val.length_), @@ -237,17 +246,19 @@ void ModelPool::resolve(ModelNode const& n, ResolveFn const& cb) const cb(ValueNode(str.value_or(std::string_view{}), shared_from_this())); break; } - default: Model::resolve(n, cb); + default: + return Model::resolve(n, cb); } + return {}; } size_t ModelPool::numRoots() const { return impl_->columns_.roots_.size(); } -ModelNode::Ptr ModelPool::root(size_t const& i) const { +tl::expected ModelPool::root(size_t const& i) const { if ((i < 0) || (i >= impl_->columns_.roots_.size())) - raise("Root index does not exist."); + return tl::unexpected(Error::RuntimeError, "Root index does not exist."); return ModelNode(shared_from_this(), impl_->columns_.roots_.at(i)); } @@ -339,23 +350,28 @@ std::shared_ptr ModelPool::strings() const return impl_->strings_; } -void ModelPool::setStrings(std::shared_ptr const& strings) +auto ModelPool::setStrings(std::shared_ptr const& strings) -> tl::expected { if (!strings) - raise("Attempt to call ModelPool::setStrings(nullptr)!"); + return tl::unexpected(Error::RuntimeError, "Attempt to call ModelPool::setStrings(nullptr)!"); auto oldStrings = impl_->strings_; impl_->strings_ = strings; if (!oldStrings || *strings == *oldStrings) - return; + return {}; // Translate object field IDs to the new dictionary. for (auto memberArray : impl_->columns_.objectMemberArrays_) { for (auto& member : memberArray) { - if (auto resolvedName = oldStrings->resolve(member.name_)) - member.name_ = strings->emplace(*resolvedName); + if (auto resolvedName = oldStrings->resolve(member.name_)) { + auto stringId = strings->emplace(*resolvedName); + TRY_EXPECTED(stringId); + member.name_ = *stringId; + } } } + + return {}; } std::optional ModelPool::lookupStringId(const simfil::StringId id) const @@ -371,19 +387,21 @@ Array::Storage& ModelPool::arrayMemberStorage() { return impl_->columns_.arrayMemberArrays_; } -void ModelPool::write(std::ostream& outputStream) { +tl::expected ModelPool::write(std::ostream& outputStream) { bitsery::Serializer s(outputStream); impl_->readWrite(s); + return {}; } -void ModelPool::read(std::istream& inputStream) { +tl::expected ModelPool::read(std::istream& inputStream) { bitsery::Deserializer s(inputStream); impl_->readWrite(s); if (s.adapter().error() != bitsery::ReaderError::NoError) { - raise(fmt::format( + return tl::unexpected(Error::EncodeDecodeError, fmt::format( "Failed to read ModelPool: Error {}", static_cast>(s.adapter().error()))); } + return {}; } #if defined(SIMFIL_WITH_MODEL_JSON) @@ -392,7 +410,8 @@ nlohmann::json ModelPool::toJson() const auto roots = nlohmann::json::array(); const auto n = numRoots(); for (auto i = 0u; i < n; ++i) { - roots.push_back(root(i)->toJson()); + if (auto node = root(i)) + roots.push_back(node.value()->toJson()); } return roots; diff --git a/src/model/nodes.cpp b/src/model/nodes.cpp index a30a6b23..458ff040 100644 --- a/src/model/nodes.cpp +++ b/src/model/nodes.cpp @@ -3,6 +3,11 @@ #include "simfil/value.h" #include "simfil/model/nodes.h" +#include "../expected.h" +#include "tl/expected.hpp" + +#include + namespace simfil { @@ -238,64 +243,78 @@ Array& Array::append(int64_t const& value) {storage_->push_back(members_, model( Array& Array::append(double const& value) {storage_->push_back(members_, model().newValue(value)->addr()); return *this;} Array& Array::append(std::string_view const& value) {storage_->push_back(members_, model().newValue(value)->addr()); return *this;} -Array& Array::extend(model_ptr const& other) { +tl::expected Array::extend(model_ptr const& other) { auto otherSize = other->size(); for (auto i = 0u; i < otherSize; ++i) { - storage_->push_back(members_, storage_->at(other->members_, i)); + auto value = storage_->at(other->members_, i); + TRY_EXPECTED(value); + storage_->push_back(members_, *value); } - return *this; + return {}; } /** Model Node impls for an object. */ -ModelNode::Ptr Object::get(std::string_view const& fieldName) const { +tl::expected Object::get(std::string_view const& fieldName) const { auto fieldId = model().strings()->emplace(fieldName); - return get(fieldId); + TRY_EXPECTED(fieldId); + auto field = get(*fieldId); + if (!field) + return tl::unexpected(Error::FieldNotFound, fmt::format("No such field {}", fieldName)); + return field; } -Object& Object::addBool(std::string_view const& name, bool value) { +tl::expected Object::addBool(std::string_view const& name, bool value) { auto fieldId = model().strings()->emplace(name); - storage_->emplace_back(members_, fieldId, model().newSmallValue(value)->addr()); - return *this; + TRY_EXPECTED(fieldId); + storage_->emplace_back(members_, *fieldId, model().newSmallValue(value)->addr()); + return {}; } -Object& Object::addField(std::string_view const& name, uint16_t value) { +tl::expected Object::addField(std::string_view const& name, uint16_t value) { auto fieldId = model().strings()->emplace(name); - storage_->emplace_back(members_, fieldId, model().newSmallValue(value)->addr()); - return *this; + TRY_EXPECTED(fieldId); + storage_->emplace_back(members_, *fieldId, model().newSmallValue(value)->addr()); + return {}; } -Object& Object::addField(std::string_view const& name, int16_t value) { +tl::expected Object::addField(std::string_view const& name, int16_t value) { auto fieldId = model().strings()->emplace(name); - storage_->emplace_back(members_, fieldId, model().newSmallValue(value)->addr()); - return *this; + TRY_EXPECTED(fieldId); + storage_->emplace_back(members_, *fieldId, model().newSmallValue(value)->addr()); + return {}; } -Object& Object::addField(std::string_view const& name, int64_t const& value) { +tl::expected Object::addField(std::string_view const& name, int64_t const& value) { auto fieldId = model().strings()->emplace(name); - storage_->emplace_back(members_, fieldId, model().newValue(value)->addr()); - return *this; + TRY_EXPECTED(fieldId); + storage_->emplace_back(members_, *fieldId, model().newValue(value)->addr()); + return {}; } -Object& Object::addField(std::string_view const& name, double const& value) { +tl::expected Object::addField(std::string_view const& name, double const& value) { auto fieldId = model().strings()->emplace(name); - storage_->emplace_back(members_, fieldId, model().newValue(value)->addr()); - return *this; + TRY_EXPECTED(fieldId); + storage_->emplace_back(members_, *fieldId, model().newValue(value)->addr()); + return {}; } -Object& Object::addField(std::string_view const& name, std::string_view const& value) { +tl::expected Object::addField(std::string_view const& name, std::string_view const& value) { auto fieldId = model().strings()->emplace(name); - storage_->emplace_back(members_, fieldId, model().newValue(value)->addr()); - return *this; + TRY_EXPECTED(fieldId); + storage_->emplace_back(members_, *fieldId, model().newValue(value)->addr()); + return {}; } -Object& Object::extend(model_ptr const& other) +tl::expected Object::extend(model_ptr const& other) { auto otherSize = other->size(); for (auto i = 0u; i < otherSize; ++i) { - storage_->push_back(members_, storage_->at(other->members_, i)); + auto value = storage_->at(other->members_, i); + TRY_EXPECTED(value); + storage_->push_back(members_, *value); } - return *this; + return {}; } } diff --git a/src/model/string-pool.cpp b/src/model/string-pool.cpp index 8c3a11db..a4c4f976 100644 --- a/src/model/string-pool.cpp +++ b/src/model/string-pool.cpp @@ -1,5 +1,6 @@ #include "simfil/model/string-pool.h" #include "simfil/exception-handler.h" +#include "simfil/error.h" #include #include @@ -89,7 +90,7 @@ StringPool::StringPool(const StringPool& other) cacheMisses_ = other.cacheMisses_.load(); } -StringId StringPool::emplace(std::string_view const& str) +auto StringPool::emplace(std::string_view const& str) -> tl::expected { { std::shared_lock lock(stringStoreMutex_); @@ -112,7 +113,7 @@ StringId StringPool::emplace(std::string_view const& str) auto& storedString = storedStrings_.emplace_back(str); StringId id = nextId_++; if (nextId_ < id) { - raise("StringPool id overflow!"); + return tl::unexpected(Error::StringPoolOverflow, "StringPool id overflow!"); } idForString_.emplace(storedString, id); stringForId_.emplace(id, storedString); @@ -177,7 +178,7 @@ void StringPool::addStaticKey(StringId id, const std::string& value) stringForId_.emplace(id, storedString); } -void StringPool::write(std::ostream& outputStream, const StringId offset) const // NOLINT +auto StringPool::write(std::ostream& outputStream, const StringId offset) const -> tl::expected // NOLINT { std::shared_lock stringStoreReadAccess(stringStoreMutex_); bitsery::Serializer s(outputStream); @@ -201,9 +202,11 @@ void StringPool::write(std::ostream& outputStream, const StringId offset) const s.text1b(it->second, std::numeric_limits::max()); } } + + return {}; } -void StringPool::read(std::istream& inputStream) +auto StringPool::read(std::istream& inputStream) -> tl::expected { std::unique_lock stringStoreWriteAccess_(stringStoreMutex_); bitsery::Deserializer s(inputStream); @@ -232,10 +235,13 @@ void StringPool::read(std::istream& inputStream) } if (s.adapter().error() != bitsery::ReaderError::NoError) { - raise(fmt::format( - "Failed to read StringPool: Error {}", - static_cast>(s.adapter().error()))); + return tl::unexpected( + Error::EncodeDecodeError, + fmt::format("Failed to read StringPool: Error {}", + static_cast>(s.adapter().error()))); } + + return {}; } bool StringPool::operator==(const StringPool &other) const { diff --git a/src/overlay.cpp b/src/overlay.cpp index 612ea8ec..45358272 100644 --- a/src/overlay.cpp +++ b/src/overlay.cpp @@ -3,9 +3,10 @@ namespace simfil { -void OverlayNodeStorage::resolve(ModelNode const& n, ResolveFn const& cb) const +tl::expected OverlayNodeStorage::resolve(ModelNode const& n, ResolveFn const& cb) const { cb(OverlayNode(n)); + return {}; } OverlayNode::OverlayNode(Value const& val) @@ -37,30 +38,30 @@ auto OverlayNode::set(StringId const& key, Value const& child) -> void { auto iter = model().overlayChildren_.find(key); if (iter != model().overlayChildren_.end()) { - if (iter->second.node) - return iter->second.node; + if (iter->second.nodePtr()) + return *iter->second.nodePtr(); return ValueNode(iter->second.getScalar()); } - return model().value_.node->get(key); + return model().value_.node()->get(key); } [[nodiscard]] ModelNode::Ptr OverlayNode::at(int64_t i) const { - return model().value_.node->at(i); + return model().value_.node()->at(i); } [[nodiscard]] StringId OverlayNode::keyAt(int64_t i) const { - return model().value_.node->keyAt(i); + return model().value_.node()->keyAt(i); } [[nodiscard]] uint32_t OverlayNode::size() const { - return model().value_.node->size(); + return model().value_.node()->size(); } [[nodiscard]] bool OverlayNode::iterate(IterCallback const& cb) const { - return model().value_.node->iterate(cb); + return model().value_.node()->iterate(cb); } } diff --git a/src/parser.cpp b/src/parser.cpp index b8cd8c36..ab9fd132 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -24,7 +24,7 @@ class NOOPExpr : public Expr return Type::FIELD; } - auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> Result override + auto ieval(Context ctx, const Value& val, const ResultFn& ores) -> tl::expected override { return Result::Stop; } @@ -237,14 +237,14 @@ auto Parser::parseList(Token::Type stop) -> expected, Error auto Parser::findPrefixParser(const Token& t) const -> const PrefixParselet* { if (auto iter = prefixParsers.find(t.type); iter != prefixParsers.end()) - return iter->second.get(); + return iter->second; return nullptr; } auto Parser::findInfixParser(const Token& t) const -> const InfixParselet* { if (auto iter = infixParsers.find(t.type); iter != infixParsers.end()) - return iter->second.get(); + return iter->second; return nullptr; } } diff --git a/src/simfil.cpp b/src/simfil.cpp index 481eb605..6f69da53 100644 --- a/src/simfil.cpp +++ b/src/simfil.cpp @@ -1,6 +1,7 @@ #include "simfil/simfil.h" #include "simfil/model/nodes.h" #include "simfil/model/string-pool.h" +#include "simfil/sourcelocation.h" #include "simfil/token.h" #include "simfil/operator.h" #include "simfil/value.h" @@ -14,6 +15,7 @@ #include "fmt/core.h" #include "expressions.h" +#include "expression-patterns.h" #include "completion.h" #include "expected.h" @@ -63,44 +65,6 @@ enum Precedence { LOGIC = 1, // and, or }; -/** - * - */ -template -static auto expect(const ExprPtr& e, Type... types) -> std::optional> -{ - const auto type2str = [](Expr::Type t) { - switch (t) { - case Expr::Type::FIELD: return "field"s; - case Expr::Type::PATH: return "path"s; - case Expr::Type::SUBEXPR: return "subexpression"s; - case Expr::Type::SUBSCRIPT: return "subscript"s; - case Expr::Type::VALUE: return "value"s; - } - return "error"s; - }; - - if (!e) - return unexpected(Error::InvalidExpression, "Expected expression"); - - if constexpr (sizeof...(types) >= 1) { - Expr::Type list[] = {types...}; - for (auto i = 0; i < sizeof...(types); ++i) { - if (e->type() == list[i]) - return {}; - } - - std::string typeNames; - for (auto i = 0; i < sizeof...(types); ++i) { - if (!typeNames.empty()) - typeNames += " or "; - typeNames += type2str(list[i]); - } - - return unexpected(Error::InvalidExpression, fmt::format("Expected {} got {}", typeNames, type2str(e->type()))); - } -} - /** * Returns if a word should be parsed as a symbol (string). * This is true for all UPPER_CASE words. @@ -120,14 +84,17 @@ static auto isSymbolWord(std::string_view sv) -> bool /** * RIIA Helper for calling function at destruction. */ +template struct scoped { - std::function f; + Fun f; + bool call = true; - explicit scoped(std::function f) : f(std::move(f)) {} - scoped(scoped&& s) noexcept : f(std::move(s.f)) { s.f = nullptr; } + explicit scoped(Fun f) : f(std::move(f)) {} + scoped(scoped&& s) noexcept : f(std::move(s.f)) { s.call = false; } scoped(const scoped& s) = delete; ~scoped() { - try { if (f) { f(); } } catch (...) {} + if (call) + f(); } }; @@ -157,9 +124,9 @@ static auto simplifyOrForward(Environment* env, expected expr) - std::deque values; auto stub = Context(env, Context::Phase::Compilation); - (void)(*expr)->eval(stub, Value::undef(), LambdaResultFn([&, n = 0](Context ctx, Value vv) mutable { + auto res = (*expr)->eval(stub, Value::undef(), LambdaResultFn([&, n = 0](Context ctx, Value&& vv) mutable { n += 1; - if ((n <= MultiConstExpr::Limit) && (!vv.isa(ValueType::Undef) || vv.node)) { + if ((n <= MultiConstExpr::Limit) && (!vv.isa(ValueType::Undef) || vv.nodePtr())) { values.push_back(std::move(vv)); return Result::Continue; } @@ -167,12 +134,13 @@ static auto simplifyOrForward(Environment* env, expected expr) - values.clear(); return Result::Stop; })); + TRY_EXPECTED(res); /* Warn about constant results */ if (!values.empty() && std::ranges::all_of(values.begin(), values.end(), [](const Value& v) { return v.isa(ValueType::Null); })) - env->warn("Expression is alway null"s, (*expr)->toString()); + env->warn("Expression is always null"s, (*expr)->toString()); if (!values.empty() && values[0].isa(ValueType::Bool) && std::ranges::all_of(values.begin(), values.end(), [&](const Value& v) { return v.isBool(values[0].as()); @@ -478,9 +446,6 @@ class SubscriptParser : public PrefixParselet, public InfixParselet auto parse(Parser& p, ExprPtr left, Token t) const -> expected override { - if (auto err = expect(left, Expr::Type::PATH, Expr::Type::VALUE, Expr::Type::SUBEXPR, Expr::Type::SUBSCRIPT)) - return *err; - auto _ = scopedNotInPath(p); auto body = p.parseTo(Token::RBRACK); if (!body) @@ -510,23 +475,16 @@ class SubSelectParser : public PrefixParselet, public InfixParselet /* Prefix sub-selects are transformed to a right side path expression, * with the current node on the left. As "standalone" sub-selects are not useful. */ auto body = p.parseTo(Token::RBRACE); - if (!body) - return unexpected(std::move(body.error())); - + TRY_EXPECTED(body); return simplifyOrForward(p.env, std::make_unique(std::make_unique("_"), std::move(*body))); } auto parse(Parser& p, ExprPtr left, Token t) const -> expected override { - if (auto err = expect(left, Expr::Type::PATH, Expr::Type::VALUE, Expr::Type::SUBEXPR, Expr::Type::SUBSCRIPT)) - return *err; - auto _ = scopedNotInPath(p); auto body = p.parseTo(Token::RBRACE); - if (!body) - return unexpected(std::move(body.error())); - + TRY_EXPECTED(body); return simplifyOrForward(p.env, std::make_unique(std::move(left), std::move(*body))); } @@ -637,7 +595,7 @@ class CompletionWordParser : public WordParser if (t.containsPoint(comp_->point)) { return std::make_unique(word.substr(0, comp_->point - t.begin), comp_, t); } - return std::make_unique(Value::make(std::move(word))); + return std::make_unique(Value::make(std::move(word))); } /* Constant */ else if (auto constant = p.env->findConstant(word)) { @@ -716,71 +674,114 @@ class CompletionPathParser : public PathParser Completion* comp_; }; +namespace +{ +// Static stateles parselets re-used by all parser instances +const ScalarParser intParser; +const ScalarParser floatParser; +const ScalarParser stringParser; +const RegExpParser regexpParser; +const UnaryOpParser negateParser; +const UnaryOpParser bitInvParser; +const UnaryOpParser notParser; +const UnaryOpParser lenParser; +const UnaryOpParser typeofParser; +const UnaryPostOpParser boolParser; +const BinaryOpParser addParser; +const BinaryOpParser subParser; +const BinaryOpParser mulParser; +const BinaryOpParser divParser; +const BinaryOpParser modParser; +const BinaryOpParser bitAndParser; +const BinaryOpParser bitOrParser; +const BinaryOpParser bitXorParser; +const BinaryOpParser shlParser; +const BinaryOpParser shrParser; +const BinaryOpParser eqParser; +const BinaryOpParser neqParser; +const BinaryOpParser ltParser; +const BinaryOpParser lteqParser; +const BinaryOpParser gtParser; +const BinaryOpParser gteqParser; +const AndOrParser andOrParser; +const CastParser castParser; +const ParenParser parenParser; +const SubSelectParser subSelectParser; +const SubscriptParser subscriptParser; +const WordParser wordParser; +const PathParser pathParser; +const UnpackOpParser unpackParser; +const WordOpParser wordOpParser; +const ConstParser trueParser{Value::t()}; +const ConstParser falseParser{Value::f()}; +const ConstParser nullParser{Value::null()}; +} + static auto setupParser(Parser& p) { /* Scalars */ - p.prefixParsers[Token::C_TRUE] = std::make_unique(Value::t()); - p.prefixParsers[Token::C_FALSE] = std::make_unique(Value::f()); - p.prefixParsers[Token::C_NULL] = std::make_unique(Value::null()); - p.prefixParsers[Token::INT] = std::make_unique>(); - p.prefixParsers[Token::FLOAT] = std::make_unique>(); - p.prefixParsers[Token::STRING] = std::make_unique>(); - p.prefixParsers[Token::REGEXP] = std::make_unique(); + p.prefixParsers[Token::C_TRUE] = &trueParser; + p.prefixParsers[Token::C_FALSE] = &falseParser; + p.prefixParsers[Token::C_NULL] = &nullParser; + p.prefixParsers[Token::INT] = &intParser; + p.prefixParsers[Token::FLOAT] = &floatParser; + p.prefixParsers[Token::STRING] = &stringParser; + p.prefixParsers[Token::REGEXP] = ®expParser; /* Unary Operators */ - p.prefixParsers[Token::OP_SUB] = std::make_unique>(); - p.prefixParsers[Token::OP_BITINV] = std::make_unique>(); - p.prefixParsers[Token::OP_NOT] = std::make_unique>(); - p.prefixParsers[Token::OP_LEN] = std::make_unique>(); - p.infixParsers[Token::OP_BOOL] = std::make_unique>(); - p.prefixParsers[Token::OP_TYPEOF] = std::make_unique>(); - p.infixParsers[Token::OP_UNPACK] = std::make_unique(); - p.infixParsers[Token::WORD] = std::make_unique(); + p.prefixParsers[Token::OP_SUB] = &negateParser; + p.prefixParsers[Token::OP_BITINV] = &bitInvParser; + p.prefixParsers[Token::OP_NOT] = ¬Parser; + p.prefixParsers[Token::OP_LEN] = &lenParser; + p.infixParsers[Token::OP_BOOL] = &boolParser; + p.prefixParsers[Token::OP_TYPEOF] = &typeofParser; + p.infixParsers[Token::OP_UNPACK] = &unpackParser; + p.infixParsers[Token::WORD] = &wordOpParser; /* Binary Operators */ - p.infixParsers[Token::OP_ADD] = std::make_unique>(); - p.infixParsers[Token::OP_SUB] = std::make_unique>(); - p.infixParsers[Token::OP_TIMES] = std::make_unique>(); - p.infixParsers[Token::OP_DIV] = std::make_unique>(); - p.infixParsers[Token::OP_MOD] = std::make_unique>(); + p.infixParsers[Token::OP_ADD] = &addParser; + p.infixParsers[Token::OP_SUB] = &subParser; + p.infixParsers[Token::OP_TIMES] = &mulParser; + p.infixParsers[Token::OP_DIV] = &divParser; + p.infixParsers[Token::OP_MOD] = &modParser; /* Bit Operators */ - p.infixParsers[Token::OP_BITAND] = std::make_unique>(); - p.infixParsers[Token::OP_BITOR] = std::make_unique>(); - p.infixParsers[Token::OP_BITXOR] = std::make_unique>(); - p.infixParsers[Token::OP_LSHIFT] = std::make_unique>(); - p.infixParsers[Token::OP_RSHIFT] = std::make_unique>(); + p.infixParsers[Token::OP_BITAND] = &bitAndParser; + p.infixParsers[Token::OP_BITOR] = &bitOrParser; + p.infixParsers[Token::OP_BITXOR] = &bitXorParser; + p.infixParsers[Token::OP_LSHIFT] = &shlParser; + p.infixParsers[Token::OP_RSHIFT] = &shrParser; /* Comparison/Test */ - p.infixParsers[Token::OP_EQ] = std::make_unique>(); - p.infixParsers[Token::OP_NOT_EQ] = std::make_unique>(); - p.infixParsers[Token::OP_LT] = std::make_unique>(); - p.infixParsers[Token::OP_LTEQ] = std::make_unique>(); - p.infixParsers[Token::OP_GT] = std::make_unique>(); - p.infixParsers[Token::OP_GTEQ] = std::make_unique>(); - p.infixParsers[Token::OP_AND] = std::make_unique(); - p.infixParsers[Token::OP_OR] = std::make_unique(); + p.infixParsers[Token::OP_EQ] = &eqParser; + p.infixParsers[Token::OP_NOT_EQ] = &neqParser; + p.infixParsers[Token::OP_LT] = <Parser; + p.infixParsers[Token::OP_LTEQ] = <eqParser; + p.infixParsers[Token::OP_GT] = >Parser; + p.infixParsers[Token::OP_GTEQ] = >eqParser; + p.infixParsers[Token::OP_AND] = &andOrParser; + p.infixParsers[Token::OP_OR] = &andOrParser; /* Cast */ - p.infixParsers[Token::OP_CAST] = std::make_unique(); + p.infixParsers[Token::OP_CAST] = &castParser; /* Subexpressions/Subscript */ - p.prefixParsers[Token::LPAREN] = std::make_unique(); /* (...) */ - p.prefixParsers[Token::LBRACE] = std::make_unique(); /* {...} */ - p.infixParsers[Token::LBRACE] = std::make_unique(); - p.prefixParsers[Token::LBRACK] = std::make_unique(); /* [...] */ - p.infixParsers[Token::LBRACK] = std::make_unique(); + p.prefixParsers[Token::LPAREN] = &parenParser; /* (...) */ + p.prefixParsers[Token::LBRACE] = &subSelectParser; /* {...} */ + p.infixParsers[Token::LBRACE] = &subSelectParser; + p.prefixParsers[Token::LBRACK] = &subscriptParser; /* [...] */ + p.infixParsers[Token::LBRACK] = &subscriptParser; /* Ident/Function */ - p.prefixParsers[Token::WORD] = std::make_unique(); - p.prefixParsers[Token::SELF] = std::make_unique(); + p.prefixParsers[Token::WORD] = &wordParser; + p.prefixParsers[Token::SELF] = &wordParser; /* Wildcards */ - p.prefixParsers[Token::WILDCARD] = std::make_unique(); - p.prefixParsers[Token::OP_TIMES] = std::make_unique(); + p.prefixParsers[Token::WILDCARD] = &wordParser; + p.prefixParsers[Token::OP_TIMES] = &wordParser; /* Paths */ - p.infixParsers[Token::DOT] = std::make_unique(); + p.infixParsers[Token::DOT] = &pathParser; } auto compile(Environment& env, std::string_view query, bool any, bool autoWildcard) -> expected @@ -832,19 +833,36 @@ auto complete(Environment& env, std::string_view query, size_t point, const Mode if (options.limit > 0) comp.limit = options.limit; - p.prefixParsers[Token::WORD] = std::make_unique(&comp); - p.infixParsers[Token::DOT] = std::make_unique(&comp); - p.infixParsers[Token::OP_AND] = std::make_unique(&comp); - p.infixParsers[Token::OP_OR] = std::make_unique(&comp); + CompletionWordParser wordCompletionParser(&comp); + CompletionPathParser pathCompletionParser(&comp); + CompletionAndOrParser andOrCompletionParser(&comp); + p.prefixParsers[Token::WORD] = &wordCompletionParser; + p.infixParsers[Token::DOT] = &pathCompletionParser; + p.infixParsers[Token::OP_AND] = &andOrCompletionParser; + p.infixParsers[Token::OP_OR] = &andOrCompletionParser; auto astResult = p.parse(); + if (!p.match(Token::Type::NIL)) + return unexpected(Error::ExpectedEOF, "Expected end-of-input; got "s + p.current().toString()); + TRY_EXPECTED(astResult); auto ast = std::move(*astResult); - /* Expand a single value to `** == ` */ - if (options.autoWildcard && ast && ast->constant()) { - ast = std::make_unique>( - std::make_unique(), std::move(ast)); + // Determine which hints to show. + auto showConstantWildcardHint = false; + auto showFieldWildcardHint = false; + auto showComparisonWildcardHint = false; + if (options.showWildcardHints) { + // Test the query for patterns and hint for converting it + // to a wildcard query by prepending `**.` to the query. + if (isSingleValueExpression(ast.get())) + showConstantWildcardHint = true; + + if (isSingleValueOrFieldExpression(ast.get())) + showFieldWildcardHint = true; + + if (isFieldComparison(ast.get())) + showComparisonWildcardHint = true; } Context ctx(&env); @@ -861,6 +879,25 @@ auto complete(Environment& env, std::string_view query, size_t point, const Mode return left.text < right.text; }); + // Show special hints for wildcard expansion. + if (showFieldWildcardHint) + candidates.emplace_back(fmt::format("**.{}", query), + SourceLocation(0, query.size()), + CompletionCandidate::Type::HINT, + fmt::format("Query field '{}' recursive", query)); + + if (showConstantWildcardHint) + candidates.emplace_back(fmt::format("** = {}", query), + SourceLocation(0, query.size()), + CompletionCandidate::Type::HINT, + fmt::format("Query fields matching '{}' recursive", query)); + + if (showComparisonWildcardHint) + candidates.emplace_back(fmt::format("**.{}", query), + SourceLocation(0, query.size()), + CompletionCandidate::Type::HINT, + "Expand to recursive query"); + return candidates; } @@ -873,17 +910,18 @@ auto eval(Environment& env, const AST& ast, const ModelNode& node, Diagnostics* auto mutableAST = ast.expr().clone(); - std::vector res; - mutableAST->eval(ctx, Value::field(node), LambdaResultFn([&res](Context, Value value) { - res.push_back(std::move(value)); + std::vector values; + auto res = mutableAST->eval(ctx, Value::field(node), LambdaResultFn([&values](Context, Value&& value) { + values.push_back(std::move(value)); return Result::Continue; })); + TRY_EXPECTED(res); if (diag) { diag->collect(*mutableAST); } - return res; + return values; } auto diagnostics(Environment& env, const AST& ast, const Diagnostics& diag) -> expected, Error> diff --git a/src/token.cpp b/src/token.cpp index 32bfe0ee..094a99da 100644 --- a/src/token.cpp +++ b/src/token.cpp @@ -435,7 +435,7 @@ std::optional scanSyntax(Scanner& s) {"/", Token::OP_DIV}, {"%", Token::OP_MOD}, {"<<",Token::OP_LSHIFT}, - {">>",Token::OP_LSHIFT}, + {">>",Token::OP_RSHIFT}, {"~", Token::OP_BITINV}, {"|", Token::OP_BITOR}, {"&", Token::OP_BITAND}, @@ -480,7 +480,7 @@ auto tokenize(std::string_view expr) -> expected, Error> } if (s.hasError()) - return unexpected(s.error()); + return unexpected(std::move(s.error())); } tokens.emplace_back(Token::NIL, expr.size(), expr.size()); diff --git a/src/types.cpp b/src/types.cpp index 696541a1..ca217100 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2,7 +2,7 @@ #include "simfil/model/nodes.h" #include "simfil/operator.h" -#include "fmt/core.h" +#include "fmt/format.h" namespace simfil { @@ -22,7 +22,7 @@ auto IRangeType::make(int64_t a, int64_t b) -> Value return {ValueType::TransientObject, std::move(obj)}; } -auto IRangeType::unaryOp(std::string_view op, const IRange& self) const -> Value +auto IRangeType::unaryOp(std::string_view op, const IRange& self) const -> tl::expected { if (op == OperatorTypeof::name()) return Value::make(ident); @@ -36,17 +36,17 @@ auto IRangeType::unaryOp(std::string_view op, const IRange& self) const -> Value if (op == OperatorLen::name()) return Value::make(static_cast(self.high() - self.low())); - raise(op); + return tl::unexpected(Error::InvalidOperands, fmt::format("Invalid operands for operator '{}'", op)); } -auto IRangeType::binaryOp(std::string_view op, const IRange& l, const Value& r) const -> Value +auto IRangeType::binaryOp(std::string_view op, const IRange& l, const Value& r) const -> tl::expected { /* Range ==/!= operator checks if the other operand is _in_ the range (for int/float) */ if (op == OperatorNeq::name()) { auto res = binaryOp(OperatorEq::name(), l, r); - if (res.isa(ValueType::Bool)) - return Value::make(!res.as()); - assert(0); + if (res && res->isa(ValueType::Bool)) + return Value::make(!res->template as()); + return res; } if (op == OperatorEq::name()) { @@ -62,18 +62,18 @@ auto IRangeType::binaryOp(std::string_view op, const IRange& l, const Value& r) return Value::f(); } - raise(op); + return tl::unexpected(Error::InvalidOperands, fmt::format("Invalid operands for operator '{}'", op)); } -auto IRangeType::binaryOp(std::string_view op, const Value& l, const IRange& r) const -> Value +auto IRangeType::binaryOp(std::string_view op, const Value& l, const IRange& r) const -> tl::expected { if (op == OperatorEq::name() || op == OperatorNeq::name()) return binaryOp(op, r, l); - raise(op); + return tl::unexpected(Error::InvalidOperands, fmt::format("Invalid operands for operator '{}'", op)); } -auto IRangeType::unpack(const IRange& self, std::function res) const -> void +auto IRangeType::unpack(const IRange& self, std::function res) const -> tl::expected { auto begin = self.begin; auto end = self.end; @@ -85,8 +85,9 @@ auto IRangeType::unpack(const IRange& self, std::function res) cons do { i += step; if (!res(Value(ValueType::Int, static_cast(i)))) - return; + return {}; } while (i != end); + return {}; } ReType ReType::Type; @@ -104,7 +105,7 @@ auto ReType::make(std::string_view expr) -> Value return Value(ValueType::TransientObject, std::move(obj)); } -auto ReType::unaryOp(std::string_view op, const Re& self) const -> Value +auto ReType::unaryOp(std::string_view op, const Re& self) const -> tl::expected { if (op == OperatorTypeof::name()) return Value::make(ident); @@ -115,15 +116,15 @@ auto ReType::unaryOp(std::string_view op, const Re& self) const -> Value if (op == OperatorAsString::name()) return Value::make(self.str); - raise(op); + return tl::unexpected(Error::InvalidOperands, fmt::format("Invalid operands for operator '{}'", op)); } -auto ReType::binaryOp(std::string_view op, const Re& l, const Value& r) const -> Value +auto ReType::binaryOp(std::string_view op, const Re& l, const Value& r) const -> tl::expected { return binaryOp(op, r, l); } -auto ReType::binaryOp(std::string_view op, const Value& l, const Re& r) const -> Value +auto ReType::binaryOp(std::string_view op, const Value& l, const Re& r) const -> tl::expected { if (l.isa(ValueType::Null)) return Value::null(); @@ -144,7 +145,7 @@ auto ReType::binaryOp(std::string_view op, const Value& l, const Re& r) const -> if (op == OperatorNeq::name()) return Value::t(); - raise(op); + return tl::unexpected(Error::InvalidOperands, fmt::format("Invalid operands for operator '{}'", op)); } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ea758562..53b8792b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,8 @@ project(test.simfil) -set(CMAKE_CXX_STANDARD 17) # Required for catch2 +set(CMAKE_CXX_STANDARD 20) # Required for catch2 +target_compile_features(Catch2 PRIVATE cxx_std_20) +target_compile_features(Catch2WithMain PRIVATE cxx_std_20) add_executable(test.simfil common.hpp @@ -11,7 +13,10 @@ add_executable(test.simfil completion.cpp complex.cpp performance.cpp - arena.cpp) + arena.cpp + error.cpp + value.cpp + operator.cpp) target_link_libraries(test.simfil PUBLIC diff --git a/test/arena.cpp b/test/arena.cpp index a9a7afe3..12af9abe 100644 --- a/test/arena.cpp +++ b/test/arena.cpp @@ -75,7 +75,8 @@ TEST_CASE("ArrayArena clear and shrink_to_fit", "[ArrayArena]") { arena.clear(); ArrayIndex array2 = arena.new_array(2); REQUIRE(array2 == 0); - REQUIRE_THROWS_AS(arena.at(array1, 0), std::out_of_range); + REQUIRE(!arena.at(array1, 0)); + REQUIRE(arena.at(array1, 0).error().type == Error::IndexOutOfRange); } SECTION("shrink_to_fit") { diff --git a/test/common.cpp b/test/common.cpp index 092db1ca..f2fc341d 100644 --- a/test/common.cpp +++ b/test/common.cpp @@ -5,10 +5,23 @@ static const PanicFn panicFn{}; +auto CompileError(std::string_view query, bool autoWildcard) -> Error +{ + Environment env(Environment::WithNewStringCache); + env.constants.try_emplace("a_number", simfil::Value::make((int64_t)123)); + env.functions["panic"] = &panicFn; + + auto ast = compile(env, query, false, autoWildcard); + REQUIRE(!ast.has_value()); + + return std::move(ast.error()); +} + auto Compile(std::string_view query, bool autoWildcard) -> ASTPtr { Environment env(Environment::WithNewStringCache); - env.constants.emplace("a_number", simfil::Value::make((int64_t)123)); + env.constants.try_emplace("a_number", simfil::Value::make((int64_t)123)); + env.functions["panic"] = &panicFn; auto ast = compile(env, query, false, autoWildcard); if (!ast) @@ -21,21 +34,27 @@ auto Compile(std::string_view query, bool autoWildcard) -> ASTPtr auto JoinedResult(std::string_view query, std::optional json) -> std::string { auto model = simfil::json::parse(std::string(json.value_or(TestModel))); - Environment env(model->strings()); + REQUIRE(model); + Environment env(model.value()->strings()); env.functions["panic"] = &panicFn; auto ast = compile(env, query, false); - if (!ast) - INFO(ast.error().message); - REQUIRE(ast.has_value()); + if (!ast) { + INFO("ERROR: " << ast.error().message); + return fmt::format("ERROR: {}", ast.error().message); + } INFO("AST: " << (*ast)->expr().toString()); - auto res = eval(env, **ast, *model->root(0), nullptr); - if (!res) - INFO(res.error().message); - REQUIRE(res); + auto root = model.value()->root(0); + REQUIRE(root); + + auto res = eval(env, **ast, **root, nullptr); + if (!res) { + INFO("ERROR: " << res.error().message); + return fmt::format("ERROR: {}", res.error().message); + } std::string vals; for (const auto& vv : *res) { @@ -46,19 +65,27 @@ auto JoinedResult(std::string_view query, std::optional json) -> st return vals; } -auto CompleteQuery(std::string_view query, size_t point, std::optional json) -> std::vector +auto CompleteQuery(std::string_view query, size_t point, std::optional json, const CompletionOptions* options) -> std::vector { auto model = simfil::json::parse(json.value_or(TestModel)); - Environment env(model->strings()); + REQUIRE(model); + Environment env(model.value()->strings()); CompletionOptions opts; - return complete(env, query, point, *model->root(0), opts).value_or(std::vector()); + opts.showWildcardHints = true; + if (options) + opts = *options; + + auto root = model.value()->root(0); + REQUIRE(root); + return complete(env, query, point, **root, opts).value_or(std::vector()); } auto GetDiagnosticMessages(std::string_view query) -> std::vector { auto model = simfil::json::parse(TestModel); - Environment env(model->strings()); + REQUIRE(model); + Environment env(model.value()->strings()); env.functions["panic"] = &panicFn; @@ -70,7 +97,9 @@ auto GetDiagnosticMessages(std::string_view query) -> std::vectorexpr().toString()); Diagnostics diag; - auto res = eval(env, **ast, *model->root(0), &diag); + auto root = model.value()->root(0); + REQUIRE(root); + auto res = eval(env, **ast, **root, &diag); if (!res) INFO(res.error().message); REQUIRE(res); diff --git a/test/common.hpp b/test/common.hpp index d2c6ac80..3291a10f 100644 --- a/test/common.hpp +++ b/test/common.hpp @@ -63,23 +63,24 @@ class PanicFn : public simfil::Function { static const FnInfo info{ "panic", - "Thrown an exception", + "Raise an error", "panic()" }; return info; } - auto eval(Context ctx, Value, const std::vector&, const ResultFn& res) const -> Result override + auto eval(Context ctx, const Value&, const std::vector&, const ResultFn& res) const -> tl::expected override { if (ctx.phase != Context::Phase::Compilation) - throw std::runtime_error("Panic!"); + return tl::unexpected(Error::RuntimeError, "Panic!"); return res(ctx, Value::undef()); } }; auto Compile(std::string_view query, bool autoWildcard = false) -> ASTPtr; +auto CompileError(std::string_view query, bool autoWildcard = false) -> Error; auto JoinedResult(std::string_view query, std::optional json = {}) -> std::string; -auto CompleteQuery(std::string_view query, size_t point, std::optional json = {}) -> std::vector; +auto CompleteQuery(std::string_view query, size_t point, std::optional json = {}, const CompletionOptions* = nullptr) -> std::vector; auto GetDiagnosticMessages(std::string_view query) -> std::vector; diff --git a/test/completion.cpp b/test/completion.cpp index 013d777a..d45762e9 100644 --- a/test/completion.cpp +++ b/test/completion.cpp @@ -1,8 +1,11 @@ +#include "src/completion.h" #include #include #include #include "common.hpp" +#include "simfil/environment.h" +#include "src/expected.h" const auto model = R"json( { @@ -17,36 +20,36 @@ const auto model = R"json( "this needs escaping": 6, "sub": { "child": 7 - } + }, + "with a space": 1 } )json"; using Type = simfil::CompletionCandidate::Type; -auto GetCompletion(std::string_view query, std::optional point) +auto GetCompletion(std::string_view query, std::optional point, const CompletionOptions* options = nullptr) { - return CompleteQuery(query, point.value_or(query.size()), model); + return CompleteQuery(query, point.value_or(query.size()), model, options); } -auto FindCompletion(std::string_view query, std::optional point, std::string_view what, std::optional type, size_t count) +auto EXPECT_COMPLETION(std::string_view query, std::optional point, std::string_view what, std::optional type = {}, size_t count = 0) { - auto comp = GetCompletion(query, point); - if (count != 0) - REQUIRE(comp.size() == count); - else - REQUIRE(comp.size() > 0); + INFO("Query: " << query); + INFO("Expected completion: " << what); + auto found = false; + auto comp = GetCompletion(query, point); for (const auto& item : comp) { - INFO(item.text); - if (item.text == what && (!type || item.type == *type)) - return true; + INFO(" Item: " << item.text); + if (item.text == what && (!type || item.type == *type)) { + found = true; + } } - return false; -} -auto EXPECT_COMPLETION(std::string_view query, std::optional point, std::string_view what, std::optional type = {}, size_t count = 0) -{ - REQUIRE(FindCompletion(query, point, what, type, count) == true); + REQUIRE(found); + if (count > 0) { + REQUIRE(comp.size() == count); + } } TEST_CASE("CompleteField", "[completion.field.incompleteQuery]") { @@ -77,18 +80,76 @@ TEST_CASE("CompleteString", "[completion.string-const]") { EXPECT_COMPLETION("1 > C", {}, "CONSTANT_1"); } -TEST_CASE("CompleteFieldOrString") { +TEST_CASE("Complete Function", "[completion.function]") { + EXPECT_COMPLETION("cou", {}, "count"); + EXPECT_COMPLETION("su", {}, "sum"); +} + +TEST_CASE("Completion Limit", "[completion.option-limit]") { + CompletionOptions opts; + opts.limit = 3; + + auto comp = GetCompletion("", {}, &opts); + REQUIRE(comp.size() <= opts.limit); +} + +TEST_CASE("Complete in Expression", "[completion.complete-mid-expression]") { + EXPECT_COMPLETION("(field + oth)", 12, "other"); + EXPECT_COMPLETION("count(fie", 9, "field"); + EXPECT_COMPLETION("sub.ch > 123", 6, "child"); +} + +TEST_CASE("Complete in unclosed expression", "[completion.complete-in-unclosed-expr]") { + EXPECT_COMPLETION("(field + oth", {}, "other"); + EXPECT_COMPLETION("_{field + oth", {}, "other"); + EXPECT_COMPLETION("*[field + oth", {}, "other"); +} + +TEST_CASE("Complete SmartCas", "[completion.smart-case]") { // Complete both the field and the constants - EXPECT_COMPLETION("cons", {}, "constant", Type::FIELD); + EXPECT_COMPLETION("cons", {}, "constant", Type::FIELD, 4); EXPECT_COMPLETION("cons", {}, "CONSTANT_1", Type::CONSTANT); // Do not complete the field - EXPECT_COMPLETION("CONS", {}, "CONSTANT_1", Type::CONSTANT, 2); - EXPECT_COMPLETION("CONS", {}, "CONSTANT_2", Type::CONSTANT, 2); + EXPECT_COMPLETION("CONS", {}, "CONSTANT_1", Type::CONSTANT, 4); // 3 entries bc. of `** =` + EXPECT_COMPLETION("CONS", {}, "CONSTANT_2", Type::CONSTANT); } -TEST_CASE("CompleteSorted") { - auto comp = GetCompletion("f", {}); +TEST_CASE("Complete Field with Special Characters", "[copletion.escape-field]") { + EXPECT_COMPLETION("with", {}, "[\"with a space\"]", Type::FIELD); +} + +TEST_CASE("Complete And/Or", "[copletion.and-or]") { + EXPECT_COMPLETION("true and f", {}, "field"); + EXPECT_COMPLETION("f and true", 1, "field"); + EXPECT_COMPLETION("false and f", {}, "field"); + EXPECT_COMPLETION("f and false", 1, "field"); + EXPECT_COMPLETION("true or f", {}, "field"); + EXPECT_COMPLETION("f or true", 1, "field"); + EXPECT_COMPLETION("false or f", {}, "field"); + EXPECT_COMPLETION("f or false", 1, "field"); +} + +TEST_CASE("Complete Wildcard Hint", "[completion.generate-eq-value-hint]") { + EXPECT_COMPLETION("A_CONST", {}, "** = A_CONST", Type::HINT); + EXPECT_COMPLETION("A_CONST", {}, "**.A_CONST", Type::HINT); + EXPECT_COMPLETION("field", {}, "**.field", Type::HINT); +} + +TEST_CASE("Complete Wildcard Comparison", "[completion.generate-compare-recursive-hint]") { + EXPECT_COMPLETION("field == 123", {}, "**.field == 123", Type::HINT); + EXPECT_COMPLETION("name != \"Mulldrifter\"", {}, "**.name != \"Mulldrifter\"", Type::HINT); + EXPECT_COMPLETION("count < 10", {}, "**.count < 10", Type::HINT); + EXPECT_COMPLETION("value >= 5.0", {}, "**.value >= 5.0", Type::HINT); + EXPECT_COMPLETION("A_FIELD == \"Value\"", {}, "**.A_FIELD == \"Value\"", Type::HINT); + EXPECT_COMPLETION("A_CONST != 42", {}, "**.A_CONST != 42", Type::HINT); +} + +TEST_CASE("Sort Completion", "[completion.sorted]") { + CompletionOptions opts; + opts.showWildcardHints = false; + + auto comp = GetCompletion("f", {}, &opts); REQUIRE(std::is_sorted(comp.begin(), comp.end(), [](const auto& l, const auto& r) { return l.text < r.text; })); diff --git a/test/complex.cpp b/test/complex.cpp index 0362cdd3..bffd18b9 100644 --- a/test/complex.cpp +++ b/test/complex.cpp @@ -76,7 +76,7 @@ TEST_CASE("Regular Expression", "[complex.regexp]") { } TEST_CASE("Runtime Error", "[complex.runtime-error]") { - REQUIRE_THROWS(JoinedResult("1 / (nonexisting as int)", invoice)); /* Division by zero */ + REQUIRE_RESULT("1 / (nonexisting as int)", "ERROR: Division by zero"); } TEST_CASE("Multimap JSON", "[multimap.serialization]") { @@ -104,17 +104,18 @@ TEST_CASE("Multimap JSON", "[multimap.serialization]") { TEST_CASE("Serialization", "[complex.serialization]") { auto model = json::parse(invoice); + REQUIRE(model); SECTION("Test Model write/read") { std::stringstream stream; - model->write(stream); + model.value()->write(stream); auto recoveredModel = std::make_shared(); recoveredModel->read(stream); - CHECK_THROWS(recoveredModel->validate()); - recoveredModel->setStrings(model->strings()); - recoveredModel->validate(); + REQUIRE(!recoveredModel->validate()); + recoveredModel->setStrings(model.value()->strings()); + REQUIRE(recoveredModel->validate()); std::function require_equals = [&](auto l, auto r) { @@ -134,22 +135,24 @@ TEST_CASE("Serialization", "[complex.serialization]") { } }; - REQUIRE(model->numRoots() == recoveredModel->numRoots()); - require_equals(model->root(0), recoveredModel->root(0)); + REQUIRE(model.value()->numRoots() == recoveredModel->numRoots()); + REQUIRE(model.value()->root(0)); + REQUIRE(recoveredModel->root(0)); + require_equals(*model.value()->root(0), *recoveredModel->root(0)); } SECTION("Test Fields write/read") { std::stringstream stream; - model->strings()->write(stream); + REQUIRE(model.value()->strings()->write(stream)); const auto recoveredFields = std::make_shared(); - recoveredFields->read(stream); + REQUIRE(recoveredFields->read(stream)); - REQUIRE(model->strings()->size() == recoveredFields->size()); - REQUIRE(model->strings()->highest() == recoveredFields->highest()); - REQUIRE(model->strings()->bytes() == recoveredFields->bytes()); + REQUIRE(model.value()->strings()->size() == recoveredFields->size()); + REQUIRE(model.value()->strings()->highest() == recoveredFields->highest()); + REQUIRE(model.value()->strings()->bytes() == recoveredFields->bytes()); for (StringId sId = 0; sId <= recoveredFields->highest(); ++sId) - REQUIRE(model->strings()->resolve(sId) == recoveredFields->resolve(StringId(sId))); + REQUIRE(model.value()->strings()->resolve(sId) == recoveredFields->resolve(StringId(sId))); } } diff --git a/test/diagnostics.cpp b/test/diagnostics.cpp index 9638391c..ec2e2580 100644 --- a/test/diagnostics.cpp +++ b/test/diagnostics.cpp @@ -1,6 +1,7 @@ #include #include +#include "catch2/catch_test_macros.hpp" #include "common.hpp" auto FindMessage(std::string_view query, std::string_view needle) { @@ -27,7 +28,7 @@ TEST_CASE("UnknownField", "[diag.unknown-field]") { } TEST_CASE("ComparatorTypeMissmatch", "[diag.comparator-type-missmatch]") { - EXPECT_DIAGNOSTIC_MESSAGE_CONTAINING("*['string'] > 123", "All values compared to"); + EXPECT_DIAGNOSTIC_MESSAGE_CONTAINING("['string'] > 123", "All values compared to true. Left hand types are string"); } TEST_CASE("AnyUnknownField", "[diag.any-suppress-if-any]") { @@ -40,13 +41,16 @@ TEST_CASE("OrShortCircuit", "[diag.suppress-short-circuitted-or]") { TEST_CASE("DiagnosticsSerialization", "[diag.serialization]") { auto model = simfil::json::parse(TestModel); - Environment env(model->strings()); + REQUIRE(model); + Environment env(model.value()->strings()); // Create two diagnostic messages. Diagnostics originalDiag; auto ast = compile(env, "**.number == \"string\" or **.number == \"string\""); REQUIRE(ast.has_value()); - eval(env, **ast, *model->root(0), &originalDiag); + auto root = model.value()->root(0); + REQUIRE(root); + eval(env, **ast, **root, &originalDiag); std::stringstream stream; auto writeResult = originalDiag.write(stream); diff --git a/test/error.cpp b/test/error.cpp new file mode 100644 index 00000000..43c464c4 --- /dev/null +++ b/test/error.cpp @@ -0,0 +1,57 @@ +#include + +#include "simfil/error.h" +#include "simfil/token.h" + + +using namespace simfil; + +TEST_CASE("Error constructors", "[error]") { + SECTION("Constructor with type only") { + Error error(Error::Type::ParserError); + REQUIRE(error.type == Error::Type::ParserError); + REQUIRE(error.message.empty()); + REQUIRE(error.location.offset == 0); + REQUIRE(error.location.size == 0); + } + + SECTION("Constructor with type and message") { + Error error(Error::Type::RuntimeError, "Test error message"); + REQUIRE(error.type == Error::Type::RuntimeError); + REQUIRE(error.message == "Test error message"); + REQUIRE(error.location.offset == 0); + REQUIRE(error.location.size == 0); + } + + SECTION("Constructor with type, message and source location") { + SourceLocation loc(10, 5); + Error error(Error::Type::InvalidExpression, "Invalid expr", loc); + REQUIRE(error.type == Error::Type::InvalidExpression); + REQUIRE(error.message == "Invalid expr"); + REQUIRE(error.location.offset == 10); + REQUIRE(error.location.size == 5); + } + + SECTION("Constructor with type, message and token") { + Token token(Token::Type::WORD, 20, 25); + Error error(Error::Type::UnknownFunction, "MyFunction", token); + REQUIRE(error.type == Error::Type::UnknownFunction); + REQUIRE(error.message == "MyFunction"); + REQUIRE(error.location.offset == 20); + REQUIRE(error.location.size == 5); + } +} + +TEST_CASE("Error equality operator", "[error]") { + SECTION("Equal errors") { + Error a(Error::Type::DivisionByZero, "Division by zero"); + Error b(Error::Type::DivisionByZero, "Division by zero"); + REQUIRE(a == b); + } + + SECTION("Different types") { + Error a(Error::Type::DivisionByZero, "Error"); + Error b(Error::Type::RuntimeError, "Error"); + REQUIRE(!(a == b)); + } +} diff --git a/test/operator.cpp b/test/operator.cpp new file mode 100644 index 00000000..7e78ed03 --- /dev/null +++ b/test/operator.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include + +#include "simfil/operator.h" +#include "simfil/model/model.h" +#include "simfil/value.h" + +using namespace simfil; +using namespace std::string_literals; + +#define REQUIRE_INVALID_OPERANDS(v) \ + static_assert(std::is_same_v) + +TEST_CASE("Unary operators", "[operator.unary]") { + SECTION("OperatorNegate") { + OperatorNegate op; + REQUIRE(op.name() == std::string("-")); + + REQUIRE(op(int64_t(5)) == int64_t(-5)); + REQUIRE(op(int64_t(-5)) == int64_t(5)); + REQUIRE(op(0.0) == Catch::Approx(-0.0)); + REQUIRE(op(3.14) == Catch::Approx(-3.14)); + REQUIRE(op(-3.14) == Catch::Approx(3.14)); + + REQUIRE_INVALID_OPERANDS(op("string"s)); + REQUIRE_INVALID_OPERANDS(op(true)); + } + + SECTION("OperatorBool") { + OperatorBool op; + REQUIRE(op.name() == std::string("?")); + + REQUIRE(op(NullType{}) == false); + REQUIRE(op(false) == false); + REQUIRE(op(true) == true); + REQUIRE(op(int64_t(0)) == true); + REQUIRE(op(int64_t(1)) == true); + REQUIRE(op(0.0) == true); + REQUIRE(op(1.0) == true); + REQUIRE(op("") == true); + REQUIRE(op("hello") == true); + } + + SECTION("OperatorNot") { + OperatorNot op; + REQUIRE(op.name() == std::string("not")); + + REQUIRE(op(NullType{}) == true); + REQUIRE(op(false) == true); + REQUIRE(op(true) == false); + REQUIRE(op(int64_t(0)) == false); + REQUIRE(op(int64_t(1)) == false); + REQUIRE(op(0.0) == false); + REQUIRE(op(1.0) == false); + REQUIRE(op("") == false); + REQUIRE(op("hello") == false); + } + + SECTION("OperatorBitInv") { + OperatorBitInv op; + REQUIRE(op.name() == std::string("~")); + + REQUIRE(op(uint64_t(0)) == ~uint64_t(0)); + REQUIRE(op(uint64_t(0xFF)) == ~uint64_t(0xFF)); + REQUIRE(op(int64_t(0)) == int64_t(~uint64_t(0))); + REQUIRE(op(int64_t(-1)) == int64_t(0)); + + REQUIRE_INVALID_OPERANDS(op("string"s)); + REQUIRE_INVALID_OPERANDS(op(3.14)); + REQUIRE_INVALID_OPERANDS(op(true)); + } + + SECTION("OperatorLen") { + OperatorLen op; + REQUIRE(op.name() == std::string("#")); + + REQUIRE(op(""s) == int64_t(0)); + REQUIRE(op("hello"s) == int64_t(5)); + REQUIRE(op("hello world"s) == int64_t(11)); + + REQUIRE_INVALID_OPERANDS(op(int64_t(123))); + REQUIRE_INVALID_OPERANDS(op(3.14)); + REQUIRE_INVALID_OPERANDS(op(true)); + } + + SECTION("OperatorTypeof") { + OperatorTypeof op; + REQUIRE(op.name() == std::string("typeof")); + + REQUIRE(op(NullType{}) == "null"); + REQUIRE(op(true) == "bool"); + REQUIRE(op(false) == "bool"); + REQUIRE(op(int64_t(42)) == "int"); + REQUIRE(op(3.14) == "float"); + REQUIRE(op("hello"s) == "string"); + } +} + +TEST_CASE("Type conversion operators", "[operator.conversion]") { + SECTION("OperatorAsInt") { + OperatorAsInt op; + REQUIRE(op.name() == std::string("int")); + + REQUIRE(op(true) == int64_t(1)); + REQUIRE(op(false) == int64_t(0)); + REQUIRE(op(int64_t(42)) == int64_t(42)); + REQUIRE(op(3.14) == int64_t(3)); + REQUIRE(op(-3.99) == int64_t(-3)); + REQUIRE(op("123"s) == int64_t(123)); + REQUIRE(op("-456"s) == int64_t(-456)); + REQUIRE(op("not a number"s) == int64_t(0)); + REQUIRE(op(""s) == int64_t(0)); + REQUIRE(op(NullType{}) == int64_t(0)); + } + + SECTION("OperatorAsFloat") { + OperatorAsFloat op; + REQUIRE(op.name() == std::string("float")); + + REQUIRE(op(true) == 1.0); + REQUIRE(op(false) == 0.0); + REQUIRE(op(int64_t(42)) == 42.0); + REQUIRE(op(3.14) == 3.14); + REQUIRE(op("123.45"s) == 123.45); + REQUIRE(op("-67.89"s) == -67.89); + REQUIRE(op("not a number"s) == 0.0); + REQUIRE(op(""s) == 0.0); + REQUIRE(op(NullType{}) == 0.0); + } +} + +TEST_CASE("Binary arithmetic operators", "[operator.binary.arithmetic]") { + SECTION("OperatorAdd") { + OperatorAdd op; + REQUIRE(op.name() == std::string("+")); + + REQUIRE(op(int64_t(5), int64_t(3)) == int64_t(8)); + REQUIRE(op(int64_t(-5), int64_t(3)) == int64_t(-2)); + REQUIRE(op(3.14, 2.86) == Catch::Approx(6.0)); + REQUIRE(op(-1.5, 2.5) == Catch::Approx(1.0)); + REQUIRE(op(int64_t(5), 3.5) == Catch::Approx(8.5)); + REQUIRE(op(3.5, int64_t(5)) == Catch::Approx(8.5)); + REQUIRE(op("hello"s, " world"s) == "hello world"); + + REQUIRE_INVALID_OPERANDS(op(true, int64_t(123))); + } + + SECTION("OperatorSub") { + OperatorSub op; + REQUIRE(op.name() == std::string("-")); + + REQUIRE(op(int64_t(5), int64_t(3)) == int64_t(2)); + REQUIRE(op(int64_t(3), int64_t(5)) == int64_t(-2)); + REQUIRE(op(5.5, 2.5) == Catch::Approx(3.0)); + REQUIRE(op(2.5, 5.5) == Catch::Approx(-3.0)); + REQUIRE(op(int64_t(10), 3.5) == Catch::Approx(6.5)); + REQUIRE(op(3.5, int64_t(10)) == Catch::Approx(-6.5)); + + REQUIRE_INVALID_OPERANDS(op("test"s, "test"s)); + } + + SECTION("OperatorMul") { + OperatorMul op; + REQUIRE(op.name() == std::string("*")); + + REQUIRE(op(int64_t(5), int64_t(3)) == int64_t(15)); + REQUIRE(op(int64_t(-5), int64_t(3)) == int64_t(-15)); + REQUIRE(op(2.5, 4.0) == Catch::Approx(10.0)); + REQUIRE(op(-2.5, 4.0) == Catch::Approx(-10.0)); + REQUIRE(op(int64_t(5), 2.5) == Catch::Approx(12.5)); + REQUIRE(op(2.5, int64_t(5)) == Catch::Approx(12.5)); + + REQUIRE_INVALID_OPERANDS(op("test"s, "test"s)); + REQUIRE_INVALID_OPERANDS(op(true, 123)); + } + + SECTION("OperatorDiv") { + OperatorDiv op; + REQUIRE(op.name() == std::string("/")); + + REQUIRE(op(int64_t(15), int64_t(3)) == int64_t(5)); + REQUIRE(op(int64_t(16), int64_t(3)) == int64_t(5)); + REQUIRE(op(10.0, 4.0) == Catch::Approx(2.5)); + REQUIRE(op(-10.0, 4.0) == Catch::Approx(-2.5)); + REQUIRE(op(int64_t(10), 4.0) == Catch::Approx(2.5)); + REQUIRE(op(10.0, int64_t(4)) == Catch::Approx(2.5)); + } + + SECTION("OperatorMod") { + OperatorMod op; + REQUIRE(op.name() == std::string("%")); + + REQUIRE(op(int64_t(17), int64_t(5)) == int64_t(2)); + REQUIRE(op(int64_t(-17), int64_t(5)) == int64_t(-2)); + REQUIRE(op(int64_t(17), int64_t(-5)) == int64_t(2)); + + REQUIRE_INVALID_OPERANDS(op(3.14, 2.0)); + REQUIRE_INVALID_OPERANDS(op("test"s, "test"s)); + } +} + +TEST_CASE("Binary comparison operators", "[operator.binary.comparison]") { + SECTION("OperatorEq") { + OperatorEq op; + REQUIRE(op.name() == std::string("==")); + + REQUIRE(op(int64_t(5), int64_t(5)) == true); + REQUIRE(op(int64_t(5), int64_t(6)) == false); + REQUIRE(op(3.14, 3.14) == true); + REQUIRE(op(3.14, 3.15) == false); + REQUIRE(op("hello"s, "hello"s) == true); + REQUIRE(op("hello"s, "world"s) == false); + REQUIRE(op(true, true) == true); + REQUIRE(op(false, false) == true); + REQUIRE(op(true, false) == false); + REQUIRE(op(NullType{}, NullType{}) == true); + REQUIRE(op(NullType{}, int64_t(0)) == false); + REQUIRE(op(int64_t(5), 5.0) == true); + REQUIRE(op(5.0, int64_t(5)) == true); + REQUIRE(op(int64_t(5), 5.1) == false); + } +} diff --git a/test/performance.cpp b/test/performance.cpp index cc1b2a8a..c1e8de01 100644 --- a/test/performance.cpp +++ b/test/performance.cpp @@ -119,7 +119,9 @@ static auto result(const ModelPoolPtr& model, std::string_view query) REQUIRE(ast.has_value()); INFO("AST: " << (*ast)->expr().toString()); - return eval(env, **ast, *model->root(0), nullptr); + auto root = model->root(0); + REQUIRE(root); + return eval(env, **ast, **root, nullptr); } static auto joined_result(const ModelPoolPtr& model, std::string_view query) @@ -153,6 +155,10 @@ TEST_CASE("Big model query performance", "[perf.big-model-benchmark]") { BENCHMARK("Query field id") { return result(model, "count(*.id == 25)"); }; + + BENCHMARK("Query field keys recursive") { + return result(model, "count(keys(**))"); + }; } #define REQUIRE_RESULT(query, result) \ @@ -167,3 +173,18 @@ TEST_CASE("Big model queries", "[perf.big-model-queries]") { REQUIRE_RESULT("count(*.id == 250)", "1"); CALLGRIND_STOP_INSTRUMENTATION; } + +TEST_CASE("Slow Compile Time Query", "[perf.slow-compile-time-query]") { + if (RUNNING_ON_VALGRIND) { + SKIP("Skipping benchmarks when running under valgrind"); + } + + const auto model = generate_model(1); + + BENCHMARK("Find Primes") { + auto value = result(model, "count(range(1,1000)...{count((_ % range(1,_)...) == 0) == 2})"); + REQUIRE(value); + REQUIRE(value->front().template as() == 168); + return value; + }; +} diff --git a/test/simfil.cpp b/test/simfil.cpp index da30e481..0b5c2305 100644 --- a/test/simfil.cpp +++ b/test/simfil.cpp @@ -19,10 +19,33 @@ static constexpr auto StaticTestKey = StringPool::NextStaticId; REQUIRE(JoinedResult((query)) == (result)) #define REQUIRE_AST(input, output) \ - REQUIRE(Compile(input, false)->expr().toString() == (output)); + REQUIRE(Compile(input, false)->expr().toString() == (output)) #define REQUIRE_AST_AUTOWILDCARD(input, output) \ - REQUIRE(Compile(input, true)->expr().toString() == (output)); + REQUIRE(Compile(input, true)->expr().toString() == (output)) + +#define REQUIRE_ERROR(input) \ + REQUIRE(CompileError(input, false).message != "") + +#define REQUIRE_PANIC(input) \ + REQUIRE_RESULT((input), "ERROR: Panic!") + + +TEST_CASE("Int", "[ast.integer]") { + REQUIRE_AST("1", "1"); + REQUIRE_AST("123", "123"); + REQUIRE_AST("-1", "-1"); + REQUIRE_AST("0xff", "255"); + REQUIRE_AST("-0xff", "-255"); +} + +TEST_CASE("Float", "[ast.float]") { + REQUIRE_AST("1.0", "1.000000"); + REQUIRE_AST("1.5", "1.500000"); + REQUIRE_AST("-1.0", "-1.000000"); + REQUIRE_AST("1e2", "100.000000"); + REQUIRE_AST("1e-2", "0.010000"); +} TEST_CASE("Path", "[ast.path]") { REQUIRE_AST("a", "a"); @@ -43,36 +66,32 @@ TEST_CASE("Wildcard", "[ast.wildcard]") { TEST_CASE("OperatorConst", "[ast.operator]") { /* Arithmetic */ - REQUIRE_AST("-1", "-1"); REQUIRE_AST("1+2", "3"); - REQUIRE_AST("1-2", "-1"); + REQUIRE_AST("1.5+2", "3.500000"); REQUIRE_AST("2*2", "4"); + REQUIRE_AST("2.5*2", "5.000000"); REQUIRE_AST("8/2", "4"); + REQUIRE_AST("3/2.0", "1.500000"); REQUIRE_AST("-a", "(- a)"); REQUIRE_AST("a+2", "(+ a 2)"); REQUIRE_AST("2+a", "(+ 2 a)"); REQUIRE_AST("a+b", "(+ a b)"); - REQUIRE_AST("1+null", "null"); - REQUIRE_AST("null+1", "null"); - REQUIRE_AST("1*null", "null"); - REQUIRE_AST("null*1", "null"); - REQUIRE_AST("1-null", "null"); - REQUIRE_AST("null-1", "null"); - REQUIRE_AST("1/null", "null"); - REQUIRE_AST("null/1", "null"); + REQUIRE_PANIC("1+panic()"); + REQUIRE_PANIC("panic()+1"); - auto GetError = [&](std::string_view query) { + auto GetError = [&](std::string_view query) -> std::string { Environment env(Environment::WithNewStringCache); auto ast = compile(env, query); REQUIRE(!ast); if (!ast) - throw ast.error(); + return ast.error().message; + return "Ok"; }; /* Division by zero */ - CHECK_THROWS(GetError("1/0")); - CHECK_THROWS(GetError("1%0")); + REQUIRE(GetError("1/0") == "Division by zero"); + REQUIRE(GetError("1%0") == "Division by zero"); /* String */ REQUIRE_AST("'a'+null", "\"anull\""); @@ -83,11 +102,20 @@ TEST_CASE("OperatorConst", "[ast.operator]") { REQUIRE_AST("1!=1", "false"); REQUIRE_AST("2>1", "true"); + REQUIRE_AST("1>=1", "true"); + REQUIRE_AST("1.0>=1", "true"); + REQUIRE_AST("1>=1.0", "true"); REQUIRE_AST("1<2", "true"); REQUIRE_AST("2<1", "false"); REQUIRE_AST("2<=2", "true"); REQUIRE_AST("2<=1", "false"); REQUIRE_AST("1<=1.1", "true"); + REQUIRE_AST("1.0<=1", "true"); + REQUIRE_AST("1.0<=1.0", "true"); + REQUIRE_AST("'a'<'b'", "true"); + REQUIRE_AST("'a'<='b'", "true"); + REQUIRE_AST("'b'>'a'", "true"); + REQUIRE_AST("'b'>='b'", "true"); /* Null behaviour */ REQUIRE_AST("1null", "false"); REQUIRE_AST("null==null", "true"); + REQUIRE_AST("1+null", "null"); + REQUIRE_AST("null+1", "null"); + REQUIRE_AST("1*null", "null"); + REQUIRE_AST("null*1", "null"); + REQUIRE_AST("1-null", "null"); + REQUIRE_AST("null-1", "null"); + REQUIRE_AST("1/null", "null"); + REQUIRE_AST("null/1", "null"); + REQUIRE_AST("null%null", "null"); + REQUIRE_AST("null/null", "null"); /* Typeof */ REQUIRE_AST("typeof 'abc'", "\"string\""); @@ -112,6 +150,9 @@ TEST_CASE("OperatorConst", "[ast.operator]") { REQUIRE_AST("2*(1+1)", "4"); /* Casts */ + REQUIRE_AST("1 as float", "1.000000"); + REQUIRE_AST("true as float", "1.000000"); + REQUIRE_AST("1.5 as int", "1"); REQUIRE_AST("'123' as int", "123"); REQUIRE_AST("'123' as float", "123.000000"); REQUIRE_AST("'123' as bool", "true"); @@ -123,6 +164,12 @@ TEST_CASE("OperatorConst", "[ast.operator]") { REQUIRE_AST("null as string", "\"null\""); REQUIRE_AST("range(1,3) as string", "\"1..3\""); + /* Bool Cast */ + REQUIRE_AST("123?", "true"); + REQUIRE_AST("0.0?", "true"); + REQUIRE_AST("false?", "false"); + REQUIRE_AST("null?", "false"); + /* Unpack */ REQUIRE_AST("null ...", "null"); REQUIRE_AST("1 ...", "1"); @@ -148,6 +195,24 @@ TEST_CASE("OperatorConst", "[ast.operator]") { REQUIRE_AST("a{1}", "(sub a 1)"); REQUIRE_AST("1{a}", "(sub 1 a)"); REQUIRE_AST("a{a}", "(sub a a)"); + + /* Length */ + REQUIRE_AST("#'abc'", "3"); + + /* Bit */ + REQUIRE_AST("~0xf0", "-241"); + REQUIRE_AST("0x80 >> 2", "32"); + REQUIRE_AST("32 << 2", "128"); + REQUIRE_AST("0xf0 & 0x80", "128"); + REQUIRE_AST("0xf0 | 0x01", "241"); +} + +TEST_CASE("OperatorLength", "[ast.operator-length]") { + REQUIRE_ERROR("#0"); + REQUIRE_ERROR("#0.0"); + REQUIRE_ERROR("#true"); + REQUIRE_AST("#null", "null"); + REQUIRE_AST("#'abc'", "3"); } TEST_CASE("CompareIncompatibleTypes", "[ast.compare-incompatible]") { @@ -188,8 +253,9 @@ TEST_CASE("CompareIncompatibleTypesFields", "[ast.compare-incompatible-types-fie )json"; const auto model = simfil::json::parse(doc); + REQUIRE(model); auto test = [&model](auto query) { - Environment env(model->strings()); + Environment env(model.value()->strings()); auto ast = compile(env, query, false); if (!ast) @@ -199,7 +265,10 @@ TEST_CASE("CompareIncompatibleTypesFields", "[ast.compare-incompatible-types-fie INFO("AST: " << (*ast)->expr().toString()); - return eval(env, **ast, *model->root(0), nullptr).value().front().template as(); + auto root = model.value()->root(0); + REQUIRE(root); + + return eval(env, **ast, **root, nullptr).value().front().template as(); }; /* Test some field with different value types for different objects */ @@ -218,16 +287,49 @@ TEST_CASE("CompareIncompatibleTypesFields", "[ast.compare-incompatible-types-fie REQUIRE(test("all(*.another=1)") == false); } +TEST_CASE("OperatorNegate", "[ast.operator-negate]") { + REQUIRE_ERROR("-('abc')"); + REQUIRE_ERROR("-(true)"); + REQUIRE_PANIC("-panic()"); + REQUIRE_AST("-(1)", "-1"); + REQUIRE_AST("-(1.1)", "-1.100000"); + REQUIRE_AST("-(null)", "null"); +} + +TEST_CASE("OperatorSubstract", "[ast.operator-substract]") { + REQUIRE_AST("1-2", "-1"); + REQUIRE_AST("1-2.0", "-1.000000"); + REQUIRE_AST("1-null", "null"); + REQUIRE_AST("1.0-2", "-1.000000"); + REQUIRE_AST("1.0-2.0", "-1.000000"); + REQUIRE_AST("1.0-null", "null"); + REQUIRE_ERROR("1-true"); + REQUIRE_ERROR("true-1"); + REQUIRE_ERROR("true-false"); +} + +TEST_CASE("OperatorNot", "[ast.operator-not]") { + REQUIRE_AST("not true", "false"); + REQUIRE_AST("not false", "true"); + REQUIRE_AST("not 'abc'", "false"); + REQUIRE_AST("not 1", "false"); + REQUIRE_AST("not 1.0", "false"); + REQUIRE_AST("not null", "true"); +} + TEST_CASE("OperatorAndOr", "[ast.operator-and-or]") { /* Or */ REQUIRE_AST("null or 1", "1"); + REQUIRE_AST("false or 1", "1"); REQUIRE_AST("1 or null", "1"); REQUIRE_AST("1 or 2", "1"); REQUIRE_AST("a or b", "(or a b)"); /* And */ REQUIRE_AST("null and 1", "null"); + REQUIRE_AST("false and 1", "false"); REQUIRE_AST("1 and null", "null"); + REQUIRE_AST("true and 2", "2"); REQUIRE_AST("1 and 2", "2"); REQUIRE_AST("a and b", "(and a b)"); } @@ -259,7 +361,9 @@ TEST_CASE("ModeSetter", "[ast.mode-setter]") { } TEST_CASE("UtilityFns", "[ast.functions]") { - REQUIRE_AST("range(a,b)", "(range a b)"); /* Can not optimize */ + REQUIRE_PANIC("range(panic(), 5)"); + REQUIRE_PANIC("range(1, panic())"); + REQUIRE_AST("range(a,b)", "(range a b)"); /* Ca not optimize */ REQUIRE_AST("range(1,5)", "1..5"); REQUIRE_AST("range(1,5)==0", "false"); REQUIRE_AST("range(1,5)==3", "true"); @@ -281,6 +385,10 @@ TEST_CASE("UtilityFns", "[ast.functions]") { REQUIRE_AST("Trace(1)", "(Trace 1)"); } +TEST_CASE("PanicFunction", "[eval.panic-function]") { + REQUIRE_RESULT("panic()", "ERROR: Panic!"); +} + TEST_CASE("OperatorOrShortCircuit", "[eval.operator-or-short-circuit]") { REQUIRE_RESULT("true or panic()", "true"); } @@ -355,43 +463,64 @@ TEST_CASE("Single Values", "[yaml.single-values]") { REQUIRE_RESULT("sub.sub.a", "sub sub a"); } -TEST_CASE("Model Functions", "[yaml.mode-functions]") { - SECTION("Test any(... ) with values generated by arr(...)") { +TEST_CASE("Model Functions", "[yaml.model-functions]") { + SECTION("Test any(...)") { REQUIRE_RESULT("any(arr(null, null))", "false"); REQUIRE_RESULT("any(arr(true, null))", "true"); REQUIRE_RESULT("any(arr(null, true))", "true"); REQUIRE_RESULT("any(arr(true, true))", "true"); } - SECTION("Test each(... ) with values generated by arr(...)") { + SECTION("Test each(...)") { REQUIRE_RESULT("each(arr(null, null))", "false"); REQUIRE_RESULT("each(arr(true, null))", "false"); REQUIRE_RESULT("each(arr(null, true))", "false"); REQUIRE_RESULT("each(arr(true, true))", "true"); } - SECTION("Test arr(... )") { + SECTION("Test arr(...)") { REQUIRE_RESULT("arr(2,3,5,7,'ok')", "2|3|5|7|ok"); + + REQUIRE_PANIC("arr(0,panic(),2)"); } - SECTION("Test split(... )") { + SECTION("Test split(...)") { REQUIRE_RESULT("split('hello.this.is.a.test.', '.')", "hello|this|is|a|test|"); REQUIRE_RESULT("split('hello.this.is.a.test.', '.', false)", "hello|this|is|a|test"); + + REQUIRE_PANIC("split(panic(), '.')"); + REQUIRE_PANIC("split('a.b.c', panic())"); } - SECTION("Test select(... )") { + SECTION("Test select(...)") { REQUIRE_RESULT("select(split('a.b.c.d', '.'), a)", "b"); REQUIRE_RESULT("select(split('a.b.c.d', '.'), 0)", "a"); REQUIRE_RESULT("select(split('a.b.c.d', '.'), 1, 2)", "b|c"); REQUIRE_RESULT("select(split('a.b.c.d', '.'), 1, 0)", "b|c|d"); + + REQUIRE_PANIC("select(panic(), 0)"); + REQUIRE_PANIC("select(0, panic())"); } - SECTION("Test sum(... )") { + SECTION("Test sum(...)") { REQUIRE_RESULT("sum(range(1, 10)...)", "55"); REQUIRE_RESULT("sum(range(1, 10)..., $sum + $val)", "55"); REQUIRE_RESULT("sum(range(1, 10)..., $sum + $val, 10)", "65"); REQUIRE_RESULT("sum(range(1, 10)..., $sum * $val, 1)", "3628800"); + + REQUIRE_PANIC("sum(panic())"); + REQUIRE_PANIC("sum(range(1, 10)..., panic())"); + REQUIRE_PANIC("sum(range(1, 10)..., 0, panic())"); } - SECTION("Count non-false values generated by arr(...)") { + SECTION("Count non-false values of arr(...)") { REQUIRE_RESULT("count(arr(null, null))", "0"); REQUIRE_RESULT("count(arr(true, null))", "1"); REQUIRE_RESULT("count(arr(null, true))", "1"); REQUIRE_RESULT("count(arr(true, true))", "2"); + + REQUIRE_PANIC("count(panic())"); + } + SECTION("Count model keys") { + REQUIRE_RESULT("count(keys(**))", "50"); + + REQUIRE_PANIC("keys(panic())"); + REQUIRE_PANIC("keys(**.{panic()})"); + REQUIRE_PANIC("panic(keys(**))"); } } @@ -431,25 +560,25 @@ TEST_CASE("Model Pool Validation", "[model.validation]") { // Recognize dangling object member pointer pool->clear(); pool->newObject()->addField("good", ModelNode::Ptr::make(pool, ModelNodeAddress{ModelPool::Objects, 666})); - REQUIRE_THROWS(pool->validate()); + REQUIRE(!pool->validate()); // Recognize dangling array member pointer pool->clear(); pool->newObject()->addField("good", ModelNode::Ptr::make(pool, ModelNodeAddress{ModelPool::Arrays, 666})); - REQUIRE_THROWS(pool->validate()); + REQUIRE(!pool->validate()); // Recognize dangling root pool->clear(); pool->addRoot(ModelNode::Ptr::make(pool, ModelNodeAddress{ModelPool::Objects, 666})); - REQUIRE_THROWS(pool->validate()); + REQUIRE(!pool->validate()); // An empty model should be valid pool->clear(); - REQUIRE_NOTHROW(pool->validate()); + REQUIRE(pool->validate()); // An empty object should also be valid pool->newObject()->addField("good", pool->newObject()); - REQUIRE_NOTHROW(pool->validate()); + REQUIRE(pool->validate()); } TEST_CASE("Procedural Object Node", "[model.procedural]") { @@ -487,20 +616,36 @@ TEST_CASE("Object/Array Extend", "[model.extend]") { testObjectB->addField("height", (int64_t)220); testObjectB->addField("age", (int64_t)55); - REQUIRE(testObjectA->size() == 2); - REQUIRE(testObjectB->size() == 2); - REQUIRE(Value(testObjectA->get("name")->value()).toString() == "hans"); - REQUIRE(Value(testObjectA->get("occupation")->value()).toString() == "baker"); - REQUIRE(!testObjectA->get("height")); - REQUIRE(!testObjectA->get("age")); - testObjectA->extend(testObjectB); - - REQUIRE(testObjectA->size() == 4); - REQUIRE(testObjectB->size() == 2); - REQUIRE(Value(testObjectA->get("name")->value()).toString() == "hans"); - REQUIRE(Value(testObjectA->get("occupation")->value()).toString() == "baker"); - REQUIRE(Value(testObjectA->get("height")->value()).as() == 220ll); - REQUIRE(Value(testObjectA->get("age")->value()).as() == 55ll); + { + REQUIRE(testObjectA->size() == 2); + REQUIRE(testObjectB->size() == 2); + auto nameValue = testObjectA->get("name"); + REQUIRE(nameValue); + REQUIRE(Value(nameValue.value()->value()).toString() == "hans"); + auto occupationValue = testObjectA->get("occupation"); + REQUIRE(occupationValue); + REQUIRE(Value(occupationValue.value()->value()).toString() == "baker"); + REQUIRE(!testObjectA->get("height")); + REQUIRE(!testObjectA->get("age")); + testObjectA->extend(testObjectB); + } + + { + REQUIRE(testObjectA->size() == 4); + REQUIRE(testObjectB->size() == 2); + auto nameValue = testObjectA->get("name"); + REQUIRE(nameValue); + REQUIRE(Value(nameValue.value()->value()).toString() == "hans"); + auto occupationValue = testObjectA->get("occupation"); + REQUIRE(occupationValue); + REQUIRE(Value(occupationValue.value()->value()).toString() == "baker"); + auto heightValue = testObjectA->get("height"); + REQUIRE(heightValue); + REQUIRE(Value(heightValue.value()->value()).as() == 220ll); + auto ageValue = testObjectA->get("age"); + REQUIRE(ageValue); + REQUIRE(Value(ageValue.value()->value()).as() == 55ll); + } } SECTION("Extend array") diff --git a/test/token.cpp b/test/token.cpp index 19a294cf..c68b3733 100644 --- a/test/token.cpp +++ b/test/token.cpp @@ -130,6 +130,7 @@ TEST_CASE("Tokenize symbols", "[token.symbol]") { REQUIRE(getFirstType("<") == Token::Type::OP_LT); REQUIRE(getFirstType("< <") == Token::Type::OP_LT); REQUIRE(getFirstType("<<") == Token::Type::OP_LSHIFT); + REQUIRE(getFirstType(">>") == Token::Type::OP_RSHIFT); REQUIRE(getFirstType("typeof") == Token::Type::OP_TYPEOF); } @@ -157,3 +158,34 @@ TEST_CASE("Token location", "[token.location]") { REQUIRE(tokens[3].begin == pos); REQUIRE(tokens[3].end == pos+3); pos+=4; /* and */ REQUIRE(tokens[4].begin == pos); REQUIRE(tokens[4].end == pos+4); pos+=4; /* true */ } + +TEST_CASE("Token to string", "[token.to-string]") { + auto tokens = tokenize("1 1.5 'Æthervial' re'.* Familiar' abc ()[]{},:._ * ** " + "null true false + - * / % << >> & | ^ ~ not and or " + "== != < <= > >= ? # typeof as ..."); + REQUIRE(tokens); + + auto i = 0; + #define REQUIRE_STR(expr) do { REQUIRE((*tokens)[i].toString() == (expr)); ++i; } while (false) + + REQUIRE_STR("1"); + REQUIRE_STR("1.500000"); + REQUIRE_STR("'Æthervial'"); + REQUIRE_STR("re'.* Familiar'"); + REQUIRE_STR("abc"); + REQUIRE_STR("("); REQUIRE_STR(")"); + REQUIRE_STR("["); REQUIRE_STR("]"); + REQUIRE_STR("{"); REQUIRE_STR("}"); + REQUIRE_STR(","); REQUIRE_STR(":"); + REQUIRE_STR("."); REQUIRE_STR("_"); + REQUIRE_STR("*"); REQUIRE_STR("**"); + REQUIRE_STR("null"); REQUIRE_STR("true"); REQUIRE_STR("false"); + REQUIRE_STR("+"); REQUIRE_STR("-"); REQUIRE_STR("*"); REQUIRE_STR("/"); + REQUIRE_STR("%"); + REQUIRE_STR("<<"); REQUIRE_STR(">>"); REQUIRE_STR("&"); REQUIRE_STR("|"); REQUIRE_STR("^"); REQUIRE_STR("~"); + REQUIRE_STR("not"); REQUIRE_STR("and"); REQUIRE_STR("or"); + REQUIRE_STR("=="); REQUIRE_STR("!="); REQUIRE_STR("<"); + REQUIRE_STR("<="); REQUIRE_STR(">"); REQUIRE_STR(">="); + REQUIRE_STR("?"); REQUIRE_STR("#"); REQUIRE_STR("typeof"); + REQUIRE_STR("as"); REQUIRE_STR("..."); +} diff --git a/test/value.cpp b/test/value.cpp new file mode 100644 index 00000000..bc4b0885 --- /dev/null +++ b/test/value.cpp @@ -0,0 +1,402 @@ +#include +#include +#include + +#include "simfil/value.h" +#include "simfil/model/model.h" +#include "simfil/transient.h" + +using namespace simfil; +using namespace std::string_literals; + +TEST_CASE("Value Constructors", "[value.value-constructor]") { + SECTION("Boolean") { + auto trueVal = Value::t(); + REQUIRE(trueVal.isa(ValueType::Bool)); + REQUIRE(trueVal.asBool() == true); + + auto falseVal = Value::f(); + REQUIRE(falseVal.isa(ValueType::Bool)); + REQUIRE(falseVal.asBool() == false); + } + + SECTION("Null") { + auto nullVal = Value::null(); + REQUIRE(nullVal.isa(ValueType::Null)); + } + + SECTION("Undef") { + auto undefVal = Value::undef(); + REQUIRE(undefVal.isa(ValueType::Undef)); + } + + SECTION("Model") { + auto modelVal = Value::model(); + REQUIRE(modelVal.isa(ValueType::Object)); + } + + SECTION("StrRef") { + auto strVal = Value::strref("test"); + REQUIRE(strVal.isa(ValueType::String)); + REQUIRE(strVal.as() == "test"); + } + + SECTION("Make Bool") { + auto val = Value::make(true); + REQUIRE(val.isa(ValueType::Bool)); + REQUIRE(val.asBool() == true); + + auto val2 = Value::make(false); + REQUIRE(val2.isa(ValueType::Bool)); + REQUIRE(val2.asBool() == false); + } + + SECTION("Make int64_t") { + auto val = Value::make(int64_t(42)); + REQUIRE(val.isa(ValueType::Int)); + REQUIRE(val.as() == 42); + } + + SECTION("Make double") { + auto val = Value::make(3.14); + REQUIRE(val.isa(ValueType::Float)); + REQUIRE(val.as() == Catch::Approx(3.14)); + } + + SECTION("Make string") { + auto val = Value::make("hello"s); + REQUIRE(val.isa(ValueType::String)); + REQUIRE(val.as() == "hello"); + } + + SECTION("Make string_view") { + std::string_view sv = "world"; + auto val = Value::make(sv); + REQUIRE(val.isa(ValueType::String)); + REQUIRE(val.as() == "world"); + } + + SECTION("Type constructor") { + Value val(ValueType::Null); + REQUIRE(val.isa(ValueType::Null)); + } + + SECTION("Type and value constructor") { + Value val(ValueType::Int, int64_t(123)); + REQUIRE(val.isa(ValueType::Int)); + REQUIRE(val.as() == 123); + } + + SECTION("ScalarValueType constructor") { + ScalarValueType scalar = (int64_t)123; + Value val(std::move(scalar)); + REQUIRE(val.isa(ValueType::Int)); + REQUIRE(val.as() == 123); + + ScalarValueType scalar2 = "Brainstorm"s; + Value val2(std::move(scalar2)); + REQUIRE(val2.isa(ValueType::String)); + REQUIRE(val2.as() == "Brainstorm"); + } + + SECTION("Copy & Move constructor") { + Value original = Value::make(123); + + Value copied = original; + REQUIRE(copied.isa(ValueType::Int)); + REQUIRE(copied.as() == 123); + REQUIRE(original.isa(ValueType::Int)); + REQUIRE(original.as() == 123); + + Value moved = std::move(original); + REQUIRE(moved.isa(ValueType::Int)); + REQUIRE(moved.as() == 123); + } +} + +TEST_CASE("Value field() methods", "[value.field]") { + auto model = std::make_shared(); + + SECTION("field() from scalar ModelNode") { + auto intNode = model->newValue(int64_t(42)); + auto val = Value::field(*intNode); + REQUIRE(val.isa(ValueType::Int)); + REQUIRE(val.as() == 42); + + auto strNode = model->newValue("test"s); + auto val2 = Value::field(*strNode); + REQUIRE(val2.isa(ValueType::String)); + REQUIRE(val2.as() == "test"); + } + + SECTION("field() from object ModelNode") { + auto objNode = model->newObject(1); + objNode->addField("key", model->newValue(int64_t(123))); + + auto val = Value::field(*objNode); + REQUIRE(val.isa(ValueType::Object)); + REQUIRE(val.node() != nullptr); + } + + SECTION("field() from array ModelNode") { + auto arrNode = model->newArray(2); + arrNode->append(model->newValue(int64_t(1))); + arrNode->append(model->newValue(int64_t(2))); + + auto val = Value::field(*arrNode); + REQUIRE(val.isa(ValueType::Array)); + REQUIRE(val.node() != nullptr); + } + + SECTION("field() from model_ptr") { + auto nodePtr = model->newValue((int64_t)88); + auto val = Value::field(nodePtr); + REQUIRE(val.isa(ValueType::Int)); + REQUIRE(val.as() == 88); + } +} + +TEST_CASE("Value As", "[value.as]") { + SECTION("as()") { + auto val = Value::make(int64_t(42)); + REQUIRE(val.as() == 42); + } + + SECTION("as()") { + auto val = Value::make(3.14); + REQUIRE(val.as() == Catch::Approx(3.14)); + } + + SECTION("as() from string") { + auto val = Value::make("hello"s); + REQUIRE(val.as() == "hello"); + } + + SECTION("as() from string_view") { + auto val = Value::strref("world"); + REQUIRE(val.as() == "world"); + } + + SECTION("as()") { + auto model = std::make_shared(); + auto objNode = model->newObject(0); + auto val = Value::field(objNode); + auto ptr = val.as(); + REQUIRE(!!ptr); + } + + SECTION("as()") { + auto model = std::make_shared(); + auto arrNode = model->newArray(0); + auto val = Value::field(arrNode); + auto ptr = val.as(); + REQUIRE(!!ptr); + } +} + +TEST_CASE("Value visit() method", "[value.visit]") { + SECTION("Visit UndefinedType") { + auto val = Value::undef(); + auto result = val.visit([](const auto& v) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return "undefined"; + } + return "other"; + }); + REQUIRE(result == "undefined") +; + } + + SECTION("Visit NullType") { + auto val = Value::null(); + auto result = val.visit([](const auto& v) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return "null"; + } + return "other"; + }); + REQUIRE(result == "null"); + } + + SECTION("Visit bool") { + auto val = Value::t() +; + auto result = val.visit([](const auto& v) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return v; + } + return false; + }); + REQUIRE(result == true); + } + + SECTION("Visit int64_t") { + auto val = Value::make(int64_t(42)); + auto result = val.visit([](const auto& v) -> int64_t { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return v; + } + return 0; + }); + REQUIRE(result == 42); + } + + SECTION("Visit double") { + auto val = Value::make(3.14); + auto result = val.visit([](const auto& v) -> double { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return v; + } + return 0.0; + }); + REQUIRE(result == Catch::Approx(3.14)); + } + + SECTION("Visit string") { + auto val = Value::make("Preordain"s); + auto result = val.visit([](const auto& v) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return v; + } + return ""; + }); + REQUIRE(result == "Preordain"); + } + + SECTION("Visit ModelNode with valid pointer") { + auto model = std::make_shared(); + auto objNode = model->newObject(0); + auto val = Value::field(objNode); + + auto result = val.visit([](const auto& v) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + return false; + } + return false; + }); + REQUIRE(result); + } + + SECTION("Visit ModelNode with null pointer") { + Value val(ValueType::Object); + auto result = val.visit([](const auto& v) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return true; + } + return false; + }); + REQUIRE(result); + } +} + +TEST_CASE("Value toString() method", "[value.toString]") { + REQUIRE(Value::undef().toString() == "undef"); + REQUIRE(Value::null().toString() == "null"); + REQUIRE(Value::t().toString() == "true"); + REQUIRE(Value::f().toString() == "false"); + REQUIRE(Value::make(int64_t(123)).toString() == "123"); + REQUIRE(Value::make(int64_t(-123)).toString() == "-123"); + REQUIRE(Value::make(double(3.14)).toString().find("3.14") == 0); + REQUIRE(Value::make("Ponder"s).toString() == "Ponder"); +} + +TEST_CASE("Value utility methods", "[value.utilities]") { + SECTION("getScalar() method") { + auto intVal = Value::make(int64_t(123)); + auto scalar = intVal.getScalar(); + REQUIRE(std::holds_alternative(scalar)); + REQUIRE(std::get(scalar) == 123); + + auto strVal = Value::make("test"s); + auto scalar2 = strVal.getScalar(); + REQUIRE(std::holds_alternative(scalar2)); + REQUIRE(std::get(scalar2) == "test"); + } + + SECTION("stringViewValue() method") { + auto strVal = Value::strref("Snap"); + auto svPtr = strVal.stringViewValue(); + REQUIRE(svPtr != nullptr); + REQUIRE(*svPtr == "Snap"); + + auto intVal = Value::make(123); + auto svPtr2 = intVal.stringViewValue(); + REQUIRE(svPtr2 == nullptr); // No string_view pointer + } + + SECTION("nodePtr() method") { + auto model = std::make_shared(); + auto objNode = model->newObject(0); + auto val = Value::field(objNode); + REQUIRE(val.nodePtr() != nullptr); + + REQUIRE(Value::make(123).nodePtr() == nullptr); + } + + SECTION("node() method") { + auto model = std::make_shared(); + auto objNode = model->newObject(0); + auto val = Value::field(objNode); + REQUIRE(val.node() != nullptr); + + REQUIRE(Value::make(42).node() == nullptr); + } +} + +TEST_CASE("valueType2String() function", "[value.type2string]") { + REQUIRE(valueType2String(ValueType::Undef) == "undef"s); + REQUIRE(valueType2String(ValueType::Null) == "null"s); + REQUIRE(valueType2String(ValueType::Bool) == "bool"s); + REQUIRE(valueType2String(ValueType::Int) == "int"s); + REQUIRE(valueType2String(ValueType::Float) == "float"s); + REQUIRE(valueType2String(ValueType::String) == "string"s); + REQUIRE(valueType2String(ValueType::TransientObject) == "transient"s); + REQUIRE(valueType2String(ValueType::Object) == "object"s); + REQUIRE(valueType2String(ValueType::Array) == "array"s); +} + +TEST_CASE("getNumeric() function", "[value.numeric]") { + SECTION("getNumeric from ith int") { + auto val = Value::make(int64_t(123)); + auto [ok, result] = getNumeric(val); + REQUIRE(ok); + REQUIRE(result == Catch::Approx(123.0)); + } + + SECTION("getNumeric from float") { + auto val = Value::make(3.14); + auto [ok, result] = getNumeric(val); + REQUIRE(ok); + REQUIRE(result == Catch::Approx(3.14)); + } + + SECTION("getNumeric from non-numeric") { + auto val = Value::make("Cloudpost"s); + auto [ok, result] = getNumeric(val); + REQUIRE_FALSE(ok); + } + + SECTION("getNumeric from double") { + ScalarValueType scalar = 1.5; + auto [ok, result] = getNumeric(scalar); + REQUIRE(ok); + REQUIRE(result == Catch::Approx(1.5f)); + } + + SECTION("getNumeric from int64_t") { + ScalarValueType scalar = int64_t(123); + auto [ok, result] = getNumeric(scalar); + REQUIRE(ok); + REQUIRE(result == 123); + } +}