forked from lix-project/lix
a385e51a08
after #6218 `Symbol` no longer confers a uniqueness invariant on the string it wraps, it is now possible to create multiple symbols that compare equal but whose string contents have different addresses. this guarantee is now only provided by `SymbolIdx`, leaving `Symbol` only as a string wrapper that knows about the intricacies of how symbols need to be formatted for output. this change renames `SymbolIdx` to `Symbol` to restore the previous semantics of `Symbol` to that name. we also keep the wrapper type and rename it to `SymbolStr` instead of returning plain strings from lookups into the symbol table because symbols are formatted for output in many places. theoretically we do not need `SymbolStr`, only a function that formats a string for output as a symbol, but having to wrap every symbol that appears in a message into eg `formatSymbol()` is error-prone and inconvient.
140 lines
4 KiB
C++
140 lines
4 KiB
C++
#include "attr-path.hh"
|
|
#include "eval-inline.hh"
|
|
#include "util.hh"
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
|
static Strings parseAttrPath(std::string_view s)
|
|
{
|
|
Strings res;
|
|
std::string cur;
|
|
auto i = s.begin();
|
|
while (i != s.end()) {
|
|
if (*i == '.') {
|
|
res.push_back(cur);
|
|
cur.clear();
|
|
} else if (*i == '"') {
|
|
++i;
|
|
while (1) {
|
|
if (i == s.end())
|
|
throw ParseError("missing closing quote in selection path '%1%'", s);
|
|
if (*i == '"') break;
|
|
cur.push_back(*i++);
|
|
}
|
|
} else
|
|
cur.push_back(*i);
|
|
++i;
|
|
}
|
|
if (!cur.empty()) res.push_back(cur);
|
|
return res;
|
|
}
|
|
|
|
|
|
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s)
|
|
{
|
|
std::vector<Symbol> res;
|
|
for (auto & a : parseAttrPath(s))
|
|
res.push_back(state.symbols.create(a));
|
|
return res;
|
|
}
|
|
|
|
|
|
std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath,
|
|
Bindings & autoArgs, Value & vIn)
|
|
{
|
|
Strings tokens = parseAttrPath(attrPath);
|
|
|
|
Value * v = &vIn;
|
|
PosIdx pos = noPos;
|
|
|
|
for (auto & attr : tokens) {
|
|
|
|
/* Is i an index (integer) or a normal attribute name? */
|
|
auto attrIndex = string2Int<unsigned int>(attr);
|
|
|
|
/* Evaluate the expression. */
|
|
Value * vNew = state.allocValue();
|
|
state.autoCallFunction(autoArgs, *v, *vNew);
|
|
v = vNew;
|
|
state.forceValue(*v, noPos);
|
|
|
|
/* It should evaluate to either a set or an expression,
|
|
according to what is specified in the attrPath. */
|
|
|
|
if (!attrIndex) {
|
|
|
|
if (v->type() != nAttrs)
|
|
throw TypeError(
|
|
"the expression selected by the selection path '%1%' should be a set but is %2%",
|
|
attrPath,
|
|
showType(*v));
|
|
if (attr.empty())
|
|
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
|
|
|
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
|
|
if (a == v->attrs->end()) {
|
|
std::set<std::string> attrNames;
|
|
for (auto & attr : *v->attrs)
|
|
attrNames.insert(state.symbols[attr.name]);
|
|
|
|
auto suggestions = Suggestions::bestMatches(attrNames, attr);
|
|
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
|
}
|
|
v = &*a->value;
|
|
pos = a->pos;
|
|
}
|
|
|
|
else {
|
|
|
|
if (!v->isList())
|
|
throw TypeError(
|
|
"the expression selected by the selection path '%1%' should be a list but is %2%",
|
|
attrPath,
|
|
showType(*v));
|
|
if (*attrIndex >= v->listSize())
|
|
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);
|
|
|
|
v = v->listElems()[*attrIndex];
|
|
pos = noPos;
|
|
}
|
|
|
|
}
|
|
|
|
return {v, pos};
|
|
}
|
|
|
|
|
|
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
|
|
{
|
|
Value * v2;
|
|
try {
|
|
auto dummyArgs = state.allocBindings(0);
|
|
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
|
|
} catch (Error &) {
|
|
throw NoPositionInfo("package '%s' has no source location information", what);
|
|
}
|
|
|
|
// FIXME: is it possible to extract the Pos object instead of doing this
|
|
// toString + parsing?
|
|
auto pos = state.forceString(*v2);
|
|
|
|
auto colon = pos.rfind(':');
|
|
if (colon == std::string::npos)
|
|
throw ParseError("cannot parse meta.position attribute '%s'", pos);
|
|
|
|
std::string filename(pos, 0, colon);
|
|
unsigned int lineno;
|
|
try {
|
|
lineno = std::stoi(std::string(pos, colon + 1, std::string::npos));
|
|
} catch (std::invalid_argument & e) {
|
|
throw ParseError("cannot parse line number '%s'", pos);
|
|
}
|
|
|
|
return { std::move(filename), lineno };
|
|
}
|
|
|
|
|
|
}
|