mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-28 08:36:15 +02:00
514 lines
13 KiB
C++
514 lines
13 KiB
C++
#pragma once
|
|
///@file
|
|
|
|
#include <cassert>
|
|
#include <climits>
|
|
#include <span>
|
|
|
|
#include "symbol-table.hh"
|
|
#include "value/context.hh"
|
|
#include "source-path.hh"
|
|
#include "print-options.hh"
|
|
|
|
#if HAVE_BOEHMGC
|
|
#include <gc/gc_allocator.h>
|
|
#endif
|
|
#include <nlohmann/json_fwd.hpp>
|
|
|
|
namespace nix {
|
|
|
|
struct Value;
|
|
class BindingsBuilder;
|
|
|
|
|
|
typedef enum {
|
|
tUninitialized = 0,
|
|
tInt = 1,
|
|
tBool,
|
|
tString,
|
|
tPath,
|
|
tNull,
|
|
tAttrs,
|
|
tList1,
|
|
tList2,
|
|
tListN,
|
|
tThunk,
|
|
tApp,
|
|
tLambda,
|
|
tPrimOp,
|
|
tPrimOpApp,
|
|
tExternal,
|
|
tFloat
|
|
} InternalType;
|
|
|
|
/**
|
|
* This type abstracts over all actual value types in the language,
|
|
* grouping together implementation details like tList*, different function
|
|
* types, and types in non-normal form (so thunks and co.)
|
|
*/
|
|
typedef enum {
|
|
nThunk,
|
|
nInt,
|
|
nFloat,
|
|
nBool,
|
|
nString,
|
|
nPath,
|
|
nNull,
|
|
nAttrs,
|
|
nList,
|
|
nFunction,
|
|
nExternal
|
|
} ValueType;
|
|
|
|
class Bindings;
|
|
struct Env;
|
|
struct Expr;
|
|
struct ExprLambda;
|
|
struct ExprBlackHole;
|
|
struct PrimOp;
|
|
class Symbol;
|
|
class PosIdx;
|
|
struct Pos;
|
|
class StorePath;
|
|
class EvalState;
|
|
class XMLWriter;
|
|
class Printer;
|
|
|
|
typedef int64_t NixInt;
|
|
typedef double NixFloat;
|
|
|
|
/**
|
|
* External values must descend from ExternalValueBase, so that
|
|
* type-agnostic nix functions (e.g. showType) can be implemented
|
|
*/
|
|
class ExternalValueBase
|
|
{
|
|
friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
|
|
friend class Printer;
|
|
protected:
|
|
/**
|
|
* Print out the value
|
|
*/
|
|
virtual std::ostream & print(std::ostream & str) const = 0;
|
|
|
|
public:
|
|
/**
|
|
* Return a simple string describing the type
|
|
*/
|
|
virtual std::string showType() const = 0;
|
|
|
|
/**
|
|
* Return a string to be used in builtins.typeOf
|
|
*/
|
|
virtual std::string typeOf() const = 0;
|
|
|
|
/**
|
|
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
|
* error.
|
|
*/
|
|
virtual std::string coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
|
|
|
|
/**
|
|
* Compare to another value of the same type. Defaults to uncomparable,
|
|
* i.e. always false.
|
|
*/
|
|
virtual bool operator ==(const ExternalValueBase & b) const;
|
|
|
|
/**
|
|
* Print the value as JSON. Defaults to unconvertable, i.e. throws an error
|
|
*/
|
|
virtual nlohmann::json printValueAsJSON(EvalState & state, bool strict,
|
|
NixStringContext & context, bool copyToStore = true) const;
|
|
|
|
/**
|
|
* Print the value as XML. Defaults to unevaluated
|
|
*/
|
|
virtual void printValueAsXML(EvalState & state, bool strict, bool location,
|
|
XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen,
|
|
const PosIdx pos) const;
|
|
|
|
virtual ~ExternalValueBase()
|
|
{
|
|
};
|
|
};
|
|
|
|
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
|
|
|
|
|
|
class ListBuilder
|
|
{
|
|
const size_t size;
|
|
Value * inlineElems[2] = {nullptr, nullptr};
|
|
public:
|
|
Value * * elems;
|
|
ListBuilder(EvalState & state, size_t size);
|
|
|
|
ListBuilder(ListBuilder && x)
|
|
: size(x.size)
|
|
, inlineElems{x.inlineElems[0], x.inlineElems[1]}
|
|
, elems(size <= 2 ? inlineElems : x.elems)
|
|
{ }
|
|
|
|
Value * & operator [](size_t n)
|
|
{
|
|
return elems[n];
|
|
}
|
|
|
|
typedef Value * * iterator;
|
|
|
|
iterator begin() { return &elems[0]; }
|
|
iterator end() { return &elems[size]; }
|
|
|
|
friend struct Value;
|
|
};
|
|
|
|
|
|
struct Value
|
|
{
|
|
private:
|
|
InternalType internalType = tUninitialized;
|
|
|
|
friend std::string showType(const Value & v);
|
|
|
|
public:
|
|
|
|
void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {});
|
|
|
|
// Functions needed to distinguish the type
|
|
// These should be removed eventually, by putting the functionality that's
|
|
// needed by callers into methods of this type
|
|
|
|
// type() == nThunk
|
|
inline bool isThunk() const { return internalType == tThunk; };
|
|
inline bool isApp() const { return internalType == tApp; };
|
|
inline bool isBlackhole() const;
|
|
|
|
// type() == nFunction
|
|
inline bool isLambda() const { return internalType == tLambda; };
|
|
inline bool isPrimOp() const { return internalType == tPrimOp; };
|
|
inline bool isPrimOpApp() const { return internalType == tPrimOpApp; };
|
|
|
|
/**
|
|
* Strings in the evaluator carry a so-called `context` which
|
|
* is a list of strings representing store paths. This is to
|
|
* allow users to write things like
|
|
*
|
|
* "--with-freetype2-library=" + freetype + "/lib"
|
|
*
|
|
* where `freetype` is a derivation (or a source to be copied
|
|
* to the store). If we just concatenated the strings without
|
|
* keeping track of the referenced store paths, then if the
|
|
* string is used as a derivation attribute, the derivation
|
|
* will not have the correct dependencies in its inputDrvs and
|
|
* inputSrcs.
|
|
|
|
* The semantics of the context is as follows: when a string
|
|
* with context C is used as a derivation attribute, then the
|
|
* derivations in C will be added to the inputDrvs of the
|
|
* derivation, and the other store paths in C will be added to
|
|
* the inputSrcs of the derivations.
|
|
|
|
* For canonicity, the store paths should be in sorted order.
|
|
*/
|
|
struct StringWithContext {
|
|
const char * c_str;
|
|
const char * * context; // must be in sorted order
|
|
};
|
|
|
|
struct Path {
|
|
SourceAccessor * accessor;
|
|
const char * path;
|
|
};
|
|
|
|
struct ClosureThunk {
|
|
Env * env;
|
|
Expr * expr;
|
|
};
|
|
|
|
struct FunctionApplicationThunk {
|
|
Value * left, * right;
|
|
};
|
|
|
|
struct Lambda {
|
|
Env * env;
|
|
ExprLambda * fun;
|
|
};
|
|
|
|
using Payload = union
|
|
{
|
|
NixInt integer;
|
|
bool boolean;
|
|
|
|
StringWithContext string;
|
|
|
|
Path path;
|
|
|
|
Bindings * attrs;
|
|
struct {
|
|
size_t size;
|
|
Value * const * elems;
|
|
} bigList;
|
|
Value * smallList[2];
|
|
ClosureThunk thunk;
|
|
FunctionApplicationThunk app;
|
|
Lambda lambda;
|
|
PrimOp * primOp;
|
|
FunctionApplicationThunk primOpApp;
|
|
ExternalValueBase * external;
|
|
NixFloat fpoint;
|
|
};
|
|
|
|
Payload payload;
|
|
|
|
/**
|
|
* Returns the normal type of a Value. This only returns nThunk if
|
|
* the Value hasn't been forceValue'd
|
|
*
|
|
* @param invalidIsThunk Instead of aborting an an invalid (probably
|
|
* 0, so uninitialized) internal type, return `nThunk`.
|
|
*/
|
|
inline ValueType type(bool invalidIsThunk = false) const
|
|
{
|
|
switch (internalType) {
|
|
case tUninitialized: break;
|
|
case tInt: return nInt;
|
|
case tBool: return nBool;
|
|
case tString: return nString;
|
|
case tPath: return nPath;
|
|
case tNull: return nNull;
|
|
case tAttrs: return nAttrs;
|
|
case tList1: case tList2: case tListN: return nList;
|
|
case tLambda: case tPrimOp: case tPrimOpApp: return nFunction;
|
|
case tExternal: return nExternal;
|
|
case tFloat: return nFloat;
|
|
case tThunk: case tApp: return nThunk;
|
|
}
|
|
if (invalidIsThunk)
|
|
return nThunk;
|
|
else
|
|
abort();
|
|
}
|
|
|
|
inline void finishValue(InternalType newType, Payload newPayload)
|
|
{
|
|
payload = newPayload;
|
|
internalType = newType;
|
|
}
|
|
|
|
/**
|
|
* A value becomes valid when it is initialized. We don't use this
|
|
* in the evaluator; only in the bindings, where the slight extra
|
|
* cost is warranted because of inexperienced callers.
|
|
*/
|
|
inline bool isValid() const
|
|
{
|
|
return internalType != tUninitialized;
|
|
}
|
|
|
|
inline void mkInt(NixInt n)
|
|
{
|
|
finishValue(tInt, { .integer = n });
|
|
}
|
|
|
|
inline void mkBool(bool b)
|
|
{
|
|
finishValue(tBool, { .boolean = b });
|
|
}
|
|
|
|
inline void mkString(const char * s, const char * * context = 0)
|
|
{
|
|
finishValue(tString, { .string = { .c_str = s, .context = context } });
|
|
}
|
|
|
|
void mkString(std::string_view s);
|
|
|
|
void mkString(std::string_view s, const NixStringContext & context);
|
|
|
|
void mkStringMove(const char * s, const NixStringContext & context);
|
|
|
|
inline void mkString(const Symbol & s)
|
|
{
|
|
mkString(((const std::string &) s).c_str());
|
|
}
|
|
|
|
void mkPath(const SourcePath & path);
|
|
void mkPath(std::string_view path);
|
|
|
|
inline void mkPath(SourceAccessor * accessor, const char * path)
|
|
{
|
|
finishValue(tPath, { .path = { .accessor = accessor, .path = path } });
|
|
}
|
|
|
|
inline void mkNull()
|
|
{
|
|
finishValue(tNull, {});
|
|
}
|
|
|
|
inline void mkAttrs(Bindings * a)
|
|
{
|
|
finishValue(tAttrs, { .attrs = a });
|
|
}
|
|
|
|
Value & mkAttrs(BindingsBuilder & bindings);
|
|
|
|
void mkList(const ListBuilder & builder)
|
|
{
|
|
if (builder.size == 1)
|
|
finishValue(tList1, { .smallList = { builder.inlineElems[0] } });
|
|
else if (builder.size == 2)
|
|
finishValue(tList2, { .smallList = { builder.inlineElems[0], builder.inlineElems[1] } });
|
|
else
|
|
finishValue(tListN, { .bigList = { .size = builder.size, .elems = builder.elems } });
|
|
}
|
|
|
|
inline void mkThunk(Env * e, Expr * ex)
|
|
{
|
|
finishValue(tThunk, { .thunk = { .env = e, .expr = ex } });
|
|
}
|
|
|
|
inline void mkApp(Value * l, Value * r)
|
|
{
|
|
finishValue(tApp, { .app = { .left = l, .right = r } });
|
|
}
|
|
|
|
inline void mkLambda(Env * e, ExprLambda * f)
|
|
{
|
|
finishValue(tLambda, { .lambda = { .env = e, .fun = f } });
|
|
}
|
|
|
|
inline void mkBlackhole();
|
|
|
|
void mkPrimOp(PrimOp * p);
|
|
|
|
inline void mkPrimOpApp(Value * l, Value * r)
|
|
{
|
|
finishValue(tPrimOpApp, { .primOpApp = { .left = l, .right = r } });
|
|
}
|
|
|
|
/**
|
|
* For a `tPrimOpApp` value, get the original `PrimOp` value.
|
|
*/
|
|
const PrimOp * primOpAppPrimOp() const;
|
|
|
|
inline void mkExternal(ExternalValueBase * e)
|
|
{
|
|
finishValue(tExternal, { .external = e });
|
|
}
|
|
|
|
inline void mkFloat(NixFloat n)
|
|
{
|
|
finishValue(tFloat, { .fpoint = n });
|
|
}
|
|
|
|
bool isList() const
|
|
{
|
|
return internalType == tList1 || internalType == tList2 || internalType == tListN;
|
|
}
|
|
|
|
Value * const * listElems()
|
|
{
|
|
return internalType == tList1 || internalType == tList2 ? payload.smallList : payload.bigList.elems;
|
|
}
|
|
|
|
std::span<Value * const> listItems() const
|
|
{
|
|
assert(isList());
|
|
return std::span<Value * const>(listElems(), listSize());
|
|
}
|
|
|
|
Value * const * listElems() const
|
|
{
|
|
return internalType == tList1 || internalType == tList2 ? payload.smallList : payload.bigList.elems;
|
|
}
|
|
|
|
size_t listSize() const
|
|
{
|
|
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : payload.bigList.size;
|
|
}
|
|
|
|
PosIdx determinePos(const PosIdx pos) const;
|
|
|
|
/**
|
|
* Check whether forcing this value requires a trivial amount of
|
|
* computation. In particular, function applications are
|
|
* non-trivial.
|
|
*/
|
|
bool isTrivial() const;
|
|
|
|
SourcePath path() const
|
|
{
|
|
assert(internalType == tPath);
|
|
return SourcePath(
|
|
ref(payload.path.accessor->shared_from_this()),
|
|
CanonPath(CanonPath::unchecked_t(), payload.path.path));
|
|
}
|
|
|
|
std::string_view string_view() const
|
|
{
|
|
assert(internalType == tString);
|
|
return std::string_view(payload.string.c_str);
|
|
}
|
|
|
|
const char * c_str() const
|
|
{
|
|
assert(internalType == tString);
|
|
return payload.string.c_str;
|
|
}
|
|
|
|
const char * * context() const
|
|
{
|
|
return payload.string.context;
|
|
}
|
|
|
|
ExternalValueBase * external() const
|
|
{ return payload.external; }
|
|
|
|
const Bindings * attrs() const
|
|
{ return payload.attrs; }
|
|
|
|
const PrimOp * primOp() const
|
|
{ return payload.primOp; }
|
|
|
|
bool boolean() const
|
|
{ return payload.boolean; }
|
|
|
|
NixInt integer() const
|
|
{ return payload.integer; }
|
|
|
|
NixFloat fpoint() const
|
|
{ return payload.fpoint; }
|
|
};
|
|
|
|
|
|
extern ExprBlackHole eBlackHole;
|
|
|
|
bool Value::isBlackhole() const
|
|
{
|
|
return internalType == tThunk && payload.thunk.expr == (Expr*) &eBlackHole;
|
|
}
|
|
|
|
void Value::mkBlackhole()
|
|
{
|
|
mkThunk(nullptr, (Expr *) &eBlackHole);
|
|
}
|
|
|
|
|
|
#if HAVE_BOEHMGC
|
|
typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector;
|
|
typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap;
|
|
typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector>>> ValueVectorMap;
|
|
#else
|
|
typedef std::vector<Value *> ValueVector;
|
|
typedef std::map<Symbol, Value *> ValueMap;
|
|
typedef std::map<Symbol, ValueVector> ValueVectorMap;
|
|
#endif
|
|
|
|
|
|
/**
|
|
* A value allocated in traceable memory.
|
|
*/
|
|
typedef std::shared_ptr<Value *> RootValue;
|
|
|
|
RootValue allocRootValue(Value * v);
|
|
|
|
}
|