From e76652a5d36d407397970cd1b737a91a10bde1af Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Fri, 14 Jul 2023 15:53:30 +0200 Subject: [PATCH] libexpr: add C bindings --- src/libexpr/eval.cc | 4 + src/libexpr/nix_api_expr.cc | 141 +++++++++ src/libexpr/nix_api_expr.h | 176 +++++++++++ src/libexpr/nix_api_expr_internal.h | 17 ++ src/libexpr/nix_api_external.cc | 198 +++++++++++++ src/libexpr/nix_api_external.h | 195 ++++++++++++ src/libexpr/nix_api_value.cc | 439 ++++++++++++++++++++++++++++ src/libexpr/nix_api_value.h | 355 ++++++++++++++++++++++ src/libexpr/search-path.cc | 2 +- src/libexpr/value.hh | 1 + 10 files changed, 1527 insertions(+), 1 deletion(-) create mode 100644 src/libexpr/nix_api_expr.cc create mode 100644 src/libexpr/nix_api_expr.h create mode 100644 src/libexpr/nix_api_expr_internal.h create mode 100644 src/libexpr/nix_api_external.cc create mode 100644 src/libexpr/nix_api_external.h create mode 100644 src/libexpr/nix_api_value.cc create mode 100644 src/libexpr/nix_api_value.h diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 5e2f71649..a93e531b6 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -897,6 +897,10 @@ void Value::mkStringMove(const char * s, const NixStringContext & context) copyContextToValue(*this, context); } +void Value::mkPath(std::string_view path) +{ + mkPath(makeImmutableString(path)); +} void Value::mkPath(const SourcePath & path) { diff --git a/src/libexpr/nix_api_expr.cc b/src/libexpr/nix_api_expr.cc new file mode 100644 index 000000000..df8a66053 --- /dev/null +++ b/src/libexpr/nix_api_expr.cc @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +#include "config.hh" +#include "eval.hh" +#include "globals.hh" +#include "util.hh" + +#include "nix_api_expr.h" +#include "nix_api_expr_internal.h" +#include "nix_api_store.h" +#include "nix_api_store_internal.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" + +#ifdef HAVE_BOEHMGC +#define GC_INCLUDE_NEW 1 +#include "gc_cpp.h" +#endif + +nix_err nix_libexpr_init(nix_c_context *context) { + if (context) + context->last_err_code = NIX_OK; + { + auto ret = nix_libutil_init(context); + if (ret != NIX_OK) + return ret; + } + { + auto ret = nix_libstore_init(context); + if (ret != NIX_OK) + return ret; + } + try { + nix::initGC(); + } + NIXC_CATCH_ERRS +} + +Expr *nix_parse_expr_from_string(nix_c_context *context, State *state, + const char *expr, const char *path, + GCRef *ref) { + if (context) + context->last_err_code = NIX_OK; + try { + Expr *result = state->state.parseExprFromString( + expr, state->state.rootPath(nix::CanonPath(path))); + if (ref) + ref->ptr = result; + return result; + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_expr_eval(nix_c_context *context, State *state, Expr *expr, + Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + state->state.eval((nix::Expr *)expr, *(nix::Value *)value); + } + NIXC_CATCH_ERRS +} + +nix_err nix_value_call(nix_c_context *context, State *state, Value *fn, + Value *arg, Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + state->state.callFunction(*(nix::Value *)fn, *(nix::Value *)arg, + *(nix::Value *)value, nix::noPos); + } + NIXC_CATCH_ERRS +} + +nix_err nix_value_force(nix_c_context *context, State *state, Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + state->state.forceValue(*(nix::Value *)value, nix::noPos); + } + NIXC_CATCH_ERRS +} + +nix_err nix_value_force_deep(nix_c_context *context, State *state, + Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + state->state.forceValueDeep(*(nix::Value *)value); + } + NIXC_CATCH_ERRS +} + +State *nix_state_create(nix_c_context *context, const char **searchPath_c, + Store *store) { + if (context) + context->last_err_code = NIX_OK; + try { + nix::Strings searchPath; + if (searchPath_c != nullptr) + for (size_t i = 0; searchPath_c[i] != nullptr; i++) + searchPath.push_back(searchPath_c[i]); + + return new State{ + nix::EvalState(nix::SearchPath::parse(searchPath), store->ptr)}; + } + NIXC_CATCH_ERRS_NULL +} + +void nix_state_free(State *state) { delete state; } + +GCRef *nix_gc_ref(nix_c_context *context, void *obj) { + if (context) + context->last_err_code = NIX_OK; + try { +#if HAVE_BOEHMGC + return new (NoGC) GCRef{obj}; +#else + return new GCRef{obj}; +#endif + } + NIXC_CATCH_ERRS_NULL +} + +void nix_gc_free(GCRef *ref) { +#if HAVE_BOEHMGC + GC_FREE(ref); +#else + delete ref; +#endif +} + +void nix_gc_register_finalizer(void *obj, void *cd, + void (*finalizer)(void *obj, void *cd)) { +#ifdef HAVE_BOEHMGC + GC_REGISTER_FINALIZER(obj, finalizer, cd, 0, 0); +#endif +} diff --git a/src/libexpr/nix_api_expr.h b/src/libexpr/nix_api_expr.h new file mode 100644 index 000000000..90aec8d18 --- /dev/null +++ b/src/libexpr/nix_api_expr.h @@ -0,0 +1,176 @@ +#ifndef NIX_API_EXPR_H +#define NIX_API_EXPR_H +/** @file + * @brief Main entry for the libexpr C bindings + */ + +#include "nix_api_store.h" +#include "nix_api_util.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +// Type definitions +/** + * @brief Represents a parsed nix Expression, can be evaluated into a Value. + * + * Owned by the GC. + */ +typedef void Expr; // nix::Expr +/** + * @brief Represents a nix evaluator state. + * + * Multiple can be created for multi-threaded + * operation. + */ +typedef struct State State; // nix::EvalState +/** + * @brief Represents a nix value. + * + * Owned by the GC. + */ +typedef void Value; // nix::Value +/** + * @brief Reference for the GC + * + * Nix uses a garbage collector that may not be able to see into + * your stack and heap. Keep GCRef objects around for every + * garbage-collected object that you want to keep alive. + */ +typedef struct GCRef GCRef; // void* + +// Function propotypes +/** + * @brief Initializes the Nix expression evaluator. + * + * This function should be called before creating a State. + * This function can be called multiple times. + * + * @param[out] context Optional, stores error information + * @return NIX_OK if the initialization was successful, an error code otherwise. + */ +nix_err nix_libexpr_init(nix_c_context *context); + +/** + * @brief Parses a Nix expression from a string. + * + * The returned expression is owned by the garbage collector. + * Pass a gcref to keep a reference. + * + * @param[out] context Optional, stores error information + * @param[in] state Evaluator state. + * @param[in] expr The Nix expression to parse. + * @param[in] path The file path to associate with the expression. + * @param[out] ref Optional, will store a reference to the returned value. + * @return A parsed expression or NULL on failure. + */ +Expr *nix_parse_expr_from_string(nix_c_context *context, State *state, + const char *expr, const char *path, + GCRef *ref); + +/** + * @brief Evaluates a parsed Nix expression. + * + * @param[out] context Optional, stores error information + * @param[in] state The state of the evaluation. + * @param[in] expr The Nix expression to evaluate. + * @param[in] value The result of the evaluation. + * @return NIX_OK if the evaluation was successful, an error code otherwise. + */ +nix_err nix_expr_eval(nix_c_context *context, State *state, Expr *expr, + Value *value); + +/** + * @brief Calls a Nix function with an argument. + * + * @param[out] context Optional, stores error information + * @param[in] state The state of the evaluation. + * @param[in] fn The Nix function to call. + * @param[in] arg The argument to pass to the function. + * @param[out] value The result of the function call. + * @return NIX_OK if the function call was successful, an error code otherwise. + */ +nix_err nix_value_call(nix_c_context *context, State *state, Value *fn, + Value *arg, Value *value); + +/** + * @brief Forces the evaluation of a Nix value. + * + * @param[out] context Optional, stores error information + * @param[in] state The state of the evaluation. + * @param[in,out] value The Nix value to force. + * @return NIX_OK if the force operation was successful, an error code + * otherwise. + */ +nix_err nix_value_force(nix_c_context *context, State *state, Value *value); + +/** + * @brief Forces the deep evaluation of a Nix value. + * + * @param[out] context Optional, stores error information + * @param[in] state The state of the evaluation. + * @param[in,out] value The Nix value to force. + * @return NIX_OK if the deep force operation was successful, an error code + * otherwise. + */ +nix_err nix_value_force_deep(nix_c_context *context, State *state, + Value *value); + +/** + * @brief Creates a new Nix state. + * + * @param[out] context Optional, stores error information + * @param[in] searchPath The NIX_PATH. + * @param[in] store The Nix store to use. + * @return A new Nix state or NULL on failure. + */ +State *nix_state_create(nix_c_context *context, const char **searchPath, + Store *store); + +/** + * @brief Frees a Nix state. + * + * Does not fail. + * + * @param[in] state The state to free. + */ +void nix_state_free(State *state); + +/** + * @brief Creates a new garbage collector reference. + * + * @param[out] context Optional, stores error information + * @param[in] obj The object to create a reference for. + * @return A new garbage collector reference or NULL on failure. + */ +GCRef *nix_gc_ref(nix_c_context *context, void *obj); + +/** + * @brief Frees a garbage collector reference. + * + * Does not fail. + * + * @param[in] ref The reference to free. + */ +void nix_gc_free(GCRef *ref); + +/** + * @brief Register a callback that gets called when the object is garbage + * collected. + * @note objects can only have a single finalizer. This function overwrites + * silently. + * @param[in] obj the object to watch + * @param[in] cd the data to pass to the finalizer + * @param[in] finalizer the callback function, called with obj and cd + */ +void nix_gc_register_finalizer(void *obj, void *cd, + void (*finalizer)(void *obj, void *cd)); + +// cffi end +#ifdef __cplusplus +} +#endif + +#endif // NIX_API_EXPR_H diff --git a/src/libexpr/nix_api_expr_internal.h b/src/libexpr/nix_api_expr_internal.h new file mode 100644 index 000000000..424ca2874 --- /dev/null +++ b/src/libexpr/nix_api_expr_internal.h @@ -0,0 +1,17 @@ +#ifndef NIX_API_EXPR_INTERNAL_H +#define NIX_API_EXPR_INTERNAL_H + +// forward declaration +namespace nix { +class EvalState; +}; + +struct State { + nix::EvalState state; +}; + +struct GCRef { + void *ptr; +}; + +#endif // NIX_API_EXPR_INTERNAL_H diff --git a/src/libexpr/nix_api_external.cc b/src/libexpr/nix_api_external.cc new file mode 100644 index 000000000..971a175fb --- /dev/null +++ b/src/libexpr/nix_api_external.cc @@ -0,0 +1,198 @@ +#include "attr-set.hh" +#include "config.hh" +#include "eval.hh" +#include "gc/gc.h" +#include "globals.hh" +#include "value.hh" + +#include "nix_api_expr.h" +#include "nix_api_expr_internal.h" +#include "nix_api_external.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "nix_api_value.h" +#include "value/context.hh" + +#include + +#ifdef HAVE_BOEHMGC +#define GC_INCLUDE_NEW 1 +#include "gc_cpp.h" +#endif + +struct nix_returned_string { + std::string str; +}; + +struct nix_printer { + std::ostream &s; +}; + +struct nix_string_context { + nix::NixStringContext &ctx; +}; + +nix_returned_string *nix_external_alloc_string(const char *c) { + return new nix_returned_string{c}; +} +void nix_external_dealloc_string(nix_returned_string *str) { delete str; } + +nix_err nix_external_print(nix_c_context *context, nix_printer *printer, + const char *c) { + if (context) + context->last_err_code = NIX_OK; + try { + printer->s << c; + } + NIXC_CATCH_ERRS +} + +nix_err nix_external_add_string_context(nix_c_context *context, + nix_string_context *ctx, + const char *c) { + if (context) + context->last_err_code = NIX_OK; + try { + auto r = nix::NixStringContextElem::parse(c); + ctx->ctx.insert(r); + } + NIXC_CATCH_ERRS +} + +class NixCExternalValue : public nix::ExternalValueBase { + NixCExternalValueDesc &desc; + void *v; + +public: + NixCExternalValue(NixCExternalValueDesc &desc, void *v) : desc(desc), v(v){}; + void *get_ptr() { return v; } + /** + * Print out the value + */ + virtual std::ostream &print(std::ostream &str) const override { + nix_printer p{str}; + desc.print(v, &p); + return str; + } + + /** + * Return a simple string describing the type + */ + virtual std::string showType() const override { + std::unique_ptr r(desc.showType(v)); + return std::move(r->str); + } + + /** + * Return a string to be used in builtins.typeOf + */ + virtual std::string typeOf() const override { + std::unique_ptr r(desc.typeOf(v)); + return std::move(r->str); + } + + /** + * Coerce the value to a string. + */ + virtual std::string coerceToString(const nix::Pos &pos, + nix::NixStringContext &context, + bool copyMore, + bool copyToStore) const override { + if (!desc.coerceToString) { + return nix::ExternalValueBase::coerceToString(pos, context, copyMore, + copyToStore); + } + nix_string_context ctx{context}; + // todo: pos, errors + std::unique_ptr r( + desc.coerceToString(v, &ctx, copyMore, copyToStore)); + if (!r) { + return nix::ExternalValueBase::coerceToString(pos, context, copyMore, + copyToStore); + } + return std::move(r->str); + } + + /** + * Compare to another value of the same type. + */ + virtual bool operator==(const ExternalValueBase &b) const override { + if (!desc.equal) { + return false; + } + auto r = dynamic_cast(&b); + if (!r) + return false; + return desc.equal(v, r->v); + } + + /** + * Print the value as JSON. + */ + virtual nlohmann::json + printValueAsJSON(nix::EvalState &state, bool strict, + nix::NixStringContext &context, + bool copyToStore = true) const override { + if (!desc.printValueAsJSON) { + return nix::ExternalValueBase::printValueAsJSON(state, strict, context, + copyToStore); + } + nix_string_context ctx{context}; + std::unique_ptr r( + desc.printValueAsJSON((State *)&state, strict, &ctx, copyToStore)); + if (!r) { + return nix::ExternalValueBase::printValueAsJSON(state, strict, context, + copyToStore); + } + return nlohmann::json::parse(r->str); + } + + /** + * Print the value as XML. + */ + virtual void printValueAsXML(nix::EvalState &state, bool strict, + bool location, nix::XMLWriter &doc, + nix::NixStringContext &context, + nix::PathSet &drvsSeen, + const nix::PosIdx pos) const override { + if (!desc.printValueAsXML) { + return nix::ExternalValueBase::printValueAsXML( + state, strict, location, doc, context, drvsSeen, pos); + } + nix_string_context ctx{context}; + desc.printValueAsXML((State *)&state, strict, location, &doc, &ctx, + &drvsSeen, *reinterpret_cast(&pos)); + } + + virtual ~NixCExternalValue() override{}; +}; + +ExternalValue *nix_create_external_value(nix_c_context *context, + NixCExternalValueDesc *desc, void *v, + GCRef *gc) { + if (context) + context->last_err_code = NIX_OK; + try { + auto ret = new +#ifdef HAVE_BOEHMGC + (GC) +#endif + NixCExternalValue(*desc, v); + if (gc) + gc->ptr = ret; + return (ExternalValue *)ret; + } + NIXC_CATCH_ERRS_NULL +} + +void *nix_get_external_value_content(nix_c_context *context, ExternalValue *b) { + if (context) + context->last_err_code = NIX_OK; + try { + auto r = dynamic_cast((nix::ExternalValueBase *)b); + if (r) + return r->get_ptr(); + return nullptr; + } + NIXC_CATCH_ERRS_NULL +} diff --git a/src/libexpr/nix_api_external.h b/src/libexpr/nix_api_external.h new file mode 100644 index 000000000..2bb53e349 --- /dev/null +++ b/src/libexpr/nix_api_external.h @@ -0,0 +1,195 @@ +#ifndef NIX_API_EXTERNAL_H +#define NIX_API_EXTERNAL_H +/** @file + * @brief libexpr C bindings dealing with external values + */ + +#include "nix_api_expr.h" +#include "nix_api_util.h" +#include "nix_api_value.h" +#include "stdbool.h" +#include "stddef.h" +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +/** + * @brief Represents a string meant for consumption by nix. + */ +typedef struct nix_returned_string nix_returned_string; +/** + * @brief Wraps a stream that can output multiple string pieces. + */ +typedef struct nix_printer nix_printer; +/** + * @brief A list of string context items + */ +typedef struct nix_string_context nix_string_context; + +/** + * @brief Allocate a nix_returned_string from a const char*. + * + * Copies the passed string. + * @param[in] c The string to copy + * @returns A nix_returned_string* + */ +nix_returned_string *nix_external_alloc_string(const char *c); + +/** + * @brief Deallocate a nix_returned_string + * + * There's generally no need to call this, since + * returning the string will pass ownership to nix, + * but you can use it in case of errors. + * @param[in] str The string to deallocate + */ +void nix_external_dealloc_string(nix_returned_string *str); + +/** + * Print to the nix_printer + * + * @param[out] context Optional, stores error information + * @param printer The nix_printer to print to + * @param[in] str The string to print + * @returns NIX_OK if everything worked + */ +nix_err nix_external_print(nix_c_context *context, nix_printer *printer, + const char *str); + +/** + * Add string context to the nix_string_context object + * @param[out] context Optional, stores error information + * @param[out] string_context The nix_string_context to add to + * @param[in] c The context string to add + * @returns NIX_OK if everything worked + */ +nix_err nix_external_add_string_context(nix_c_context *context, + nix_string_context *string_context, + const char *c); + +/** + * @brief Definition for a class of external values + * + * Create and implement one of these, then pass it to nix_create_external_value + * Make sure to keep it alive while the external value lives. + * + * Optional functions can be set to NULL + * + * @see nix_create_external_value + */ +typedef struct NixCExternalValueDesc { + /** + * @brief Called when printing the external value + * + * @param[in] self the void* passed to nix_create_external_value + * @param[out] printer The printer to print to, pass to nix_external_print + */ + void (*print)(void *self, nix_printer *printer); + /** + * @brief Called on :t + * @param[in] self the void* passed to nix_create_external_value + * @returns a nix_returned_string, ownership passed to nix + */ + nix_returned_string *(*showType)(void *self); // std::string + /** + * @brief Called on `builtins.typeOf` + * @param self the void* passed to nix_create_external_value + * @returns a nix_returned_string, ownership passed to nix + */ + nix_returned_string *(*typeOf)(void *self); // std::string + /** + * @brief Called on "${str}" and builtins.toString. + * + * The latter with coerceMore=true + * Optional, the default is to throw an error. + * @param[in] self the void* passed to nix_create_external_value + * @param[out] c writable string context for the resulting string + * @param[in] coerceMore boolean, try to coerce to strings in more cases + * instead of throwing an error + * @param[in] copyToStore boolean, whether to copy referenced paths to store + * or keep them as-is + * @returns a nix_returned_string, ownership passed to nix. Optional, + * returning NULL will make the conversion throw an error. + */ + nix_returned_string *(*coerceToString)(void *self, nix_string_context *c, + int coerceMore, int copyToStore); + /** + * @brief Try to compare two external values + * + * Optional, the default is always false. + * If the other object was not a Nix C external value, this comparison will + * also return false + * @param[in] self the void* passed to nix_create_external_value + * @param[in] other the void* passed to the other object's + * nix_create_external_value + * @returns true if the objects are deemed to be equal + */ + int (*equal)(void *self, void *other); + /** + * @brief Convert the external value to json + * + * Optional, the default is to throw an error + * @param[in] state The evaluator state + * @param[in] strict boolean Whether to force the value before printing + * @param[out] c writable string context for the resulting string + * @param[in] copyToStore whether to copy referenced paths to store or keep + * them as-is + * @returns string that gets parsed as json. Optional, returning NULL will + * make the conversion throw an error. + */ + nix_returned_string *(*printValueAsJSON)(State *, int strict, + nix_string_context *c, + bool copyToStore); + /** + * @brief Convert the external value to XML + * + * Optional, the default is to throw an error + * @todo The mechanisms for this call are incomplete. There are no C + * bindings to work with XML, pathsets and positions. + * @param[in] state The evaluator state + * @param[in] strict boolean Whether to force the value before printing + * @param[in] location boolean Whether to include position information in the + * xml + * @param[out] doc XML document to output to + * @param[out] c writable string context for the resulting string + * @param[in,out] drvsSeen a path set to avoid duplicating derivations + * @param[in] pos The position of the call. + */ + void (*printValueAsXML)(State *, int strict, int location, void *doc, + nix_string_context *c, void *drvsSeen, int pos); +} NixCExternalValueDesc; + +/** + * @brief Create an external value, that can be given to nix_set_external + * + * Pass a gcref to keep a reference. + * @param[out] context Optional, stores error information + * @param[in] desc a NixCExternalValueDesc, you should keep this alive as long + * as the ExternalValue lives + * @param[in] v the value to store + * @param[out] ref Optional, will store a reference to the returned value. + * @returns external value, owned by the garbage collector + * @see nix_set_external + */ +ExternalValue *nix_create_external_value(nix_c_context *context, + NixCExternalValueDesc *desc, void *v, + GCRef *ref); + +/** + * @brief Extract the pointer from a nix c external value. + * @param[out] context Optional, stores error information + * @param[in] b The external value + * @returns The pointer, or null if the external value was not from nix c. + * @see nix_get_external + */ +void *nix_get_external_value_content(nix_c_context *context, ExternalValue *b); + +// cffi end +#ifdef __cplusplus +} +#endif + +#endif // NIX_API_EXTERNAL_H diff --git a/src/libexpr/nix_api_value.cc b/src/libexpr/nix_api_value.cc new file mode 100644 index 000000000..f58500367 --- /dev/null +++ b/src/libexpr/nix_api_value.cc @@ -0,0 +1,439 @@ +#include "attr-set.hh" +#include "config.hh" +#include "eval.hh" +#include "gc/gc.h" +#include "globals.hh" +#include "value.hh" + +#include "nix_api_expr.h" +#include "nix_api_expr_internal.h" +#include "nix_api_util.h" +#include "nix_api_util_internal.h" +#include "nix_api_value.h" + +#ifdef HAVE_BOEHMGC +#define GC_INCLUDE_NEW 1 +#include "gc_cpp.h" +#endif + +// Helper function to throw an exception if value is null +static const nix::Value &check_value_not_null(const Value *value) { + if (!value) { + throw std::runtime_error("Value is null"); + } + return *((const nix::Value *)value); +} + +static nix::Value &check_value_not_null(Value *value) { + if (!value) { + throw std::runtime_error("Value is null"); + } + return *((nix::Value *)value); +} + +PrimOp *nix_alloc_primop(nix_c_context *context, PrimOpFun fun, int arity, + const char *name, const char **args, const char *doc, + GCRef *ref) { + if (context) + context->last_err_code = NIX_OK; + try { + auto fun2 = (nix::PrimOpFun)fun; + auto p = new +#ifdef HAVE_BOEHMGC + (GC) +#endif + nix::PrimOp{.name = name, .args = {}, .doc = doc, .fun = fun2}; + if (args) + for (size_t i = 0; args[i]; i++) + p->args.emplace_back(*args); + if (ref) + ref->ptr = p; + return (PrimOp *)p; + } + NIXC_CATCH_ERRS_NULL +} + +Value *nix_alloc_value(nix_c_context *context, State *state, GCRef *ref) { + if (context) + context->last_err_code = NIX_OK; + try { + Value *res = state->state.allocValue(); + if (ref) + ref->ptr = res; + return res; + } + NIXC_CATCH_ERRS_NULL +} + +ValueType nix_get_type(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + using namespace nix; + switch (v.type()) { + case nThunk: + return NIX_TYPE_THUNK; + case nInt: + return NIX_TYPE_INT; + case nFloat: + return NIX_TYPE_FLOAT; + case nBool: + return NIX_TYPE_BOOL; + case nString: + return NIX_TYPE_STRING; + case nPath: + return NIX_TYPE_PATH; + case nNull: + return NIX_TYPE_NULL; + case nAttrs: + return NIX_TYPE_ATTRS; + case nList: + return NIX_TYPE_LIST; + case nFunction: + return NIX_TYPE_FUNCTION; + case nExternal: + return NIX_TYPE_EXTERNAL; + } + return NIX_TYPE_NULL; + } + NIXC_CATCH_ERRS_RES(NIX_TYPE_NULL); +} + +const char *nix_get_typename(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + auto s = nix::showType(v); + return strdup(s.c_str()); + } + NIXC_CATCH_ERRS_NULL +} + +bool nix_get_bool(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nBool); + return v.boolean; + } + NIXC_CATCH_ERRS_RES(false); +} + +const char *nix_get_string(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nString); + return v.string.s; + } + NIXC_CATCH_ERRS_NULL +} + +const char *nix_get_path_string(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nPath); + return v._path; + } + NIXC_CATCH_ERRS_NULL +} + +unsigned int nix_get_list_size(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nList); + return v.listSize(); + } + NIXC_CATCH_ERRS_RES(0); +} + +unsigned int nix_get_attrs_size(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nAttrs); + return v.attrs->size(); + } + NIXC_CATCH_ERRS_RES(0); +} + +double nix_get_double(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nFloat); + return v.fpoint; + } + NIXC_CATCH_ERRS_RES(NAN); +} + +int64_t nix_get_int(nix_c_context *context, const Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nInt); + return v.integer; + } + NIXC_CATCH_ERRS_RES(0); +} + +ExternalValue *nix_get_external(nix_c_context *context, Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nExternal); + return (ExternalValue *)v.external; + } + NIXC_CATCH_ERRS_NULL; +} + +Value *nix_get_list_byidx(nix_c_context *context, const Value *value, + unsigned int ix, GCRef *ref) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nList); + return (Value *)v.listElems()[ix]; + } + NIXC_CATCH_ERRS_NULL +} + +Value *nix_get_attr_byname(nix_c_context *context, const Value *value, + State *state, const char *name, GCRef *ref) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nAttrs); + nix::Symbol s = state->state.symbols.create(name); + auto attr = v.attrs->get(s); + if (attr) { + if (ref) + ref->ptr = attr->value; + return attr->value; + } + nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute"); + return nullptr; + } + NIXC_CATCH_ERRS_NULL +} + +bool nix_has_attr_byname(nix_c_context *context, const Value *value, + State *state, const char *name) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + assert(v.type() == nix::nAttrs); + nix::Symbol s = state->state.symbols.create(name); + auto attr = v.attrs->get(s); + if (attr) + return true; + return false; + } + NIXC_CATCH_ERRS_RES(false); +} + +Value *nix_get_attr_byidx(nix_c_context *context, const Value *value, + State *state, unsigned int i, const char **name, + GCRef *ref) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + const nix::Attr &a = (*v.attrs)[i]; + *name = ((const std::string &)(state->state.symbols[a.name])).c_str(); + if (ref) + ref->ptr = a.value; + return a.value; + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_set_bool(nix_c_context *context, Value *value, bool b) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + v.mkBool(b); + } + NIXC_CATCH_ERRS +} + +// todo string context +nix_err nix_set_string(nix_c_context *context, Value *value, const char *str) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + v.mkString(std::string_view(str)); + } + NIXC_CATCH_ERRS +} + +nix_err nix_set_path_string(nix_c_context *context, Value *value, + const char *str) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + v.mkPath(std::string_view(str)); + } + NIXC_CATCH_ERRS +} + +nix_err nix_set_double(nix_c_context *context, Value *value, double d) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + v.mkFloat(d); + } + NIXC_CATCH_ERRS +} + +nix_err nix_set_int(nix_c_context *context, Value *value, int64_t i) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + v.mkInt(i); + } + NIXC_CATCH_ERRS +} + +nix_err nix_set_null(nix_c_context *context, Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + v.mkNull(); + } + NIXC_CATCH_ERRS +} + +nix_err nix_set_external(nix_c_context *context, Value *value, + ExternalValue *val) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + auto r = (nix::ExternalValueBase *)val; + v.mkExternal(r); + } + NIXC_CATCH_ERRS +} + +nix_err nix_make_list(nix_c_context *context, State *s, Value *value, + unsigned int size) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + s->state.mkList(v, size); + } + NIXC_CATCH_ERRS +} + +nix_err nix_set_list_byidx(nix_c_context *context, Value *value, + unsigned int ix, Value *elem) { + if (context) + context->last_err_code = NIX_OK; + try { + // todo: assert that this is a list + auto &v = check_value_not_null(value); + auto &e = check_value_not_null(elem); + v.listElems()[ix] = &e; + } + NIXC_CATCH_ERRS +} + +nix_err nix_set_primop(nix_c_context *context, Value *value, PrimOp *p) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + v.mkPrimOp((nix::PrimOp *)p); + } + NIXC_CATCH_ERRS +} + +nix_err nix_copy_value(nix_c_context *context, Value *value, Value *source) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + auto &s = check_value_not_null(source); + v = s; + } + NIXC_CATCH_ERRS +} + +nix_err nix_set_thunk(nix_c_context *context, State *s, Value *value, + Expr *expr) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + s->state.mkThunk_(v, (nix::Expr *)expr); + } + NIXC_CATCH_ERRS +} + +typedef std::shared_ptr BindingsBuilder_Inner; + +nix_err nix_make_attrs(nix_c_context *context, Value *value, + BindingsBuilder *b) { + if (context) + context->last_err_code = NIX_OK; + try { + auto &v = check_value_not_null(value); + nix::BindingsBuilder &builder = **(BindingsBuilder_Inner *)b; + v.mkAttrs(builder); + } + NIXC_CATCH_ERRS +} + +BindingsBuilder *nix_make_bindings_builder(nix_c_context *context, State *state, + size_t capacity) { + if (context) + context->last_err_code = NIX_OK; + try { + auto bb = state->state.buildBindings(capacity); + auto res = new BindingsBuilder_Inner(); + *res = std::allocate_shared( + traceable_allocator(), bb); + return res; + } + NIXC_CATCH_ERRS_NULL +} + +nix_err nix_bindings_builder_insert(nix_c_context *context, BindingsBuilder *b, + const char *name, Value *value) { + if (context) + context->last_err_code = NIX_OK; + try { + nix::BindingsBuilder &builder = **(BindingsBuilder_Inner *)b; + auto &v = check_value_not_null(value); + nix::Symbol s = builder.state.symbols.create(name); + builder.insert(s, &v); + } + NIXC_CATCH_ERRS +} + +void nix_bindings_builder_unref(BindingsBuilder *bb) { + delete (BindingsBuilder_Inner *)bb; +} diff --git a/src/libexpr/nix_api_value.h b/src/libexpr/nix_api_value.h new file mode 100644 index 000000000..5ecedefeb --- /dev/null +++ b/src/libexpr/nix_api_value.h @@ -0,0 +1,355 @@ +#ifndef NIX_API_VALUE_H +#define NIX_API_VALUE_H + +/** @file + * @brief libexpr C bindings dealing with values + */ + +#include "nix_api_util.h" +#include "stdbool.h" +#include "stddef.h" +#include "stdint.h" + +#ifdef __cplusplus +extern "C" { +#endif +// cffi start + +// Type definitions +typedef enum { + NIX_TYPE_THUNK, + NIX_TYPE_INT, + NIX_TYPE_FLOAT, + NIX_TYPE_BOOL, + NIX_TYPE_STRING, + NIX_TYPE_PATH, + NIX_TYPE_NULL, + NIX_TYPE_ATTRS, + NIX_TYPE_LIST, + NIX_TYPE_FUNCTION, + NIX_TYPE_EXTERNAL +} ValueType; + +// forward declarations +typedef void Value; +typedef void Expr; +typedef struct State State; +typedef struct GCRef GCRef; +// type defs +/** @brief Stores an under-construction set of bindings + * Reference-counted + * @see nix_make_bindings_builder, nix_bindings_builder_unref, nix_make_attrs + * @see nix_bindings_builder_insert + */ +typedef void BindingsBuilder; + +/** @brief PrimOp function + * + * Owned by the GC + * @see nix_alloc_primop, nix_set_primop + */ +typedef struct PrimOp PrimOp; +/** @brief External Value + * + * Owned by the GC + * @see nix_api_external.h + */ +typedef struct ExternalValue ExternalValue; + +/** @brief Function pointer for primops + * @param[in] state Evaluator state + * @param[in] pos position of function call + * @param[in] args list of arguments + * @param[out] v return value + * @see nix_alloc_primop, nix_set_primop + */ +typedef void (*PrimOpFun)(State *state, int pos, Value **args, Value *v); + +/** @brief Allocate a primop + * + * Owned by the GC + * Pass a gcref to keep a reference. + * + * @param[out] context Optional, stores error information + * @param[in] fun callback + * @param[in] arity expected amount of function arguments + * @param[in] name function name + * @param[in] args array of argument names + * @param[in] doc optional, documentation for this primop + * @param[out] ref Optional, will store a reference to the returned value. + * @return primop, or null in case of errors + * @see nix_set_primop + */ +PrimOp *nix_alloc_primop(nix_c_context *context, PrimOpFun fun, int arity, + const char *name, const char **args, const char *doc, + GCRef *ref); + +// Function prototypes + +/** @brief Allocate a Nix value + * + * Owned by the GC + * Pass a gcref to keep a reference. + * @param[out] context Optional, stores error information + * @param[in] state nix evaluator state + * @param[out] ref Optional, will store a reference to the returned value. + * @return value, or null in case of errors + * + */ +Value *nix_alloc_value(nix_c_context *context, State *state, GCRef *ref); +/** @name Getters + */ +/**@{*/ +/** @brief Get value type + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return type of nix value + */ +ValueType nix_get_type(nix_c_context *context, const Value *value); +/** @brief Get type name of value + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return type name, owned string + * @todo way to free the result + */ +const char *nix_get_typename(nix_c_context *context, const Value *value); + +/** @brief Get boolean value + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return true or false, error info via context + */ +bool nix_get_bool(nix_c_context *context, const Value *value); +/** @brief Get string + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return string + * @return NULL in case of error. + */ +const char *nix_get_string(nix_c_context *context, const Value *value); +/** @brief Get path as string + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return string + * @return NULL in case of error. + */ +const char *nix_get_path_string(nix_c_context *context, const Value *value); +/** @brief Get the length of a list + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return length of list, error info via context + */ +unsigned int nix_get_list_size(nix_c_context *context, const Value *value); +/** @brief Get the element count of an attrset + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return attrset element count, error info via context + */ +unsigned int nix_get_attrs_size(nix_c_context *context, const Value *value); +/** @brief Get float value in 64 bits + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return float contents, error info via context + */ +double nix_get_double(nix_c_context *context, const Value *value); +/** @brief Get int value + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return int contents, error info via context + */ +int64_t nix_get_int(nix_c_context *context, const Value *value); +/** @brief Get external reference + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @return reference to external, NULL in case of error + */ +ExternalValue *nix_get_external(nix_c_context *context, Value *); + +/** @brief Get the ix'th element of a list + * + * Pass a gcref to keep a reference. + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] ix list element to get + * @param[out] ref Optional, will store a reference to the returned value. + * @return value, NULL in case of errors + */ +Value *nix_get_list_byidx(nix_c_context *context, const Value *value, + unsigned int ix, GCRef *ref); +/** @brief Get an attr by name + * + * Pass a gcref to keep a reference. + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] state nix evaluator state + * @param[in] name attribute name + * @param[out] ref Optional, will store a reference to the returned value. + * @return value, NULL in case of errors + */ +Value *nix_get_attr_byname(nix_c_context *context, const Value *value, + State *state, const char *name, GCRef *ref); + +/** @brief Check if an attribute name exists on a value + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] state nix evaluator state + * @param[in] name attribute name + * @return value, NULL in case of errors + */ +bool nix_has_attr_byname(nix_c_context *context, const Value *value, + State *state, const char *name); + +/** @brief Get an attribute by index in the sorted bindings + * @param[out] context Optional, stores error information + * @param[in] value Nix value to inspect + * @param[in] state nix evaluator state + * @param[in] i attribute index + * @param[out] name will store a pointer to the attribute name + * @return value, NULL in case of errors + */ +Value *nix_get_attr_byidx(nix_c_context *context, const Value *value, + State *state, unsigned int i, const char **name, + GCRef *ref); +/**@}*/ +/** @name Setters + */ +/**@{*/ +/** @brief Set boolean value + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] b the boolean value + * @return error code, NIX_OK on success. + */ +nix_err nix_set_bool(nix_c_context *context, Value *value, bool b); +/** @brief Set a string + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] str the string, copied + * @return error code, NIX_OK on success. + */ +nix_err nix_set_string(nix_c_context *context, Value *value, const char *str); +/** @brief Set a path + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] str the path string, copied + * @return error code, NIX_OK on success. + */ +nix_err nix_set_path_string(nix_c_context *context, Value *value, + const char *str); +/** @brief Set a double + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] d the double + * @return error code, NIX_OK on success. + */ +nix_err nix_set_double(nix_c_context *context, Value *value, double d); +/** @brief Set an int + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] i the int + * @return error code, NIX_OK on success. + */ +nix_err nix_set_int(nix_c_context *context, Value *value, int64_t i); +/** @brief Set null + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @return error code, NIX_OK on success. + */ +nix_err nix_set_null(nix_c_context *context, Value *value); +/** @brief Set an external value + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] val the external value to set. Will be GC-referenced by the value. + * @return error code, NIX_OK on success. + */ +nix_err nix_set_external(nix_c_context *context, Value *value, + ExternalValue *val); +/** @brief Allocate a list + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] size size of list + * @return error code, NIX_OK on success. + */ +nix_err nix_make_list(nix_c_context *context, State *s, Value *value, + unsigned int size); +/** @brief Manipulate a list by index + * + * Don't do this mid-computation. + * @pre your list should be at least 'ix+1' items long + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] ix index to manipulate + * @param[in] elem the value to set, will be gc-referenced by the value + * @return error code, NIX_OK on success. + */ +nix_err nix_set_list_byidx(nix_c_context *context, Value *value, + unsigned int ix, Value *elem); +/** @brief Create an attribute set from a bindings builder + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] b bindings builder to use. Make sure to unref this afterwards. + * @return error code, NIX_OK on success. + */ +nix_err nix_make_attrs(nix_c_context *context, Value *value, + BindingsBuilder *b); +/** @brief Set primop + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] op primop, will be gc-referenced by the value + * @see nix_alloc_primop + * @return error code, NIX_OK on success. + */ +nix_err nix_set_primop(nix_c_context *context, Value *value, PrimOp *op); +/** @brief Copy from another value + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] source value to copy from + * @return error code, NIX_OK on success. + */ +nix_err nix_copy_value(nix_c_context *context, Value *value, Value *source); +/** @brief Make a thunk from an expr. + * + * Expr will be evaluated when the value is forced + * @param[out] context Optional, stores error information + * @param[out] value Nix value to modify + * @param[in] expr the expr to thunk + * @return error code, NIX_OK on success. + */ +nix_err nix_set_thunk(nix_c_context *context, State *s, Value *value, + Expr *expr); +/**@}*/ + +/** @brief Create a bindings builder + +* @param[out] context Optional, stores error information +* @param[in] state nix evaluator state +* @param[in] capacity how many bindings you'll add. Don't exceed. +* @return owned reference to a bindings builder. Make sure to unref when you're +done. +*/ +BindingsBuilder *nix_make_bindings_builder(nix_c_context *context, State *state, + size_t capacity); +/** @brief Insert bindings into a builder + * @param[out] context Optional, stores error information + * @param[in] builder BindingsBuilder to insert into + * @param[in] name attribute name, copied into the symbol store + * @param[in] value value to give the binding + * @return error code, NIX_OK on success. + */ +nix_err nix_bindings_builder_insert(nix_c_context *context, + BindingsBuilder *builder, const char *name, + Value *value); +/** @brief Unref a bindings builder + * + * Does not fail. + * It'll be deallocated when all references are gone. + * @param[in] builder the builder to unref + */ +void nix_bindings_builder_unref(BindingsBuilder *builder); + +// cffi end +#ifdef __cplusplus +} +#endif + +#endif // NIX_API_VALUE_H diff --git a/src/libexpr/search-path.cc b/src/libexpr/search-path.cc index a25767496..e2c3e050a 100644 --- a/src/libexpr/search-path.cc +++ b/src/libexpr/search-path.cc @@ -44,7 +44,7 @@ SearchPath::Elem SearchPath::Elem::parse(std::string_view rawElem) } -SearchPath parseSearchPath(const Strings & rawElems) +SearchPath SearchPath::parse(const Strings & rawElems) { SearchPath res; for (auto & rawElem : rawElems) diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 335801b34..b7b3c6434 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -328,6 +328,7 @@ public: } void mkPath(const SourcePath & path); + void mkPath(std::string_view path); inline void mkPath(InputAccessor * accessor, const char * path) {