diff --git a/nixd/include/nixd/AST/AttrLocator.h b/nixd/include/nixd/AST/AttrLocator.h new file mode 100644 index 000000000..4c9d61366 --- /dev/null +++ b/nixd/include/nixd/AST/AttrLocator.h @@ -0,0 +1,24 @@ +#pragma once + +#include "nixd/AST/ParseAST.h" + +#include "lspserver/Protocol.h" + +#include + +namespace nixd { + +class AttrLocator { + std::map Attrs; + +public: + AttrLocator(const ParseAST &AST); + + /// Locate a nix attribute, \return the pointer to the expression + /// { a = "1"; } + /// ^ ^~~ + /// Pos pointer + [[nodiscard]] const nix::Expr *locate(lspserver::Position Pos) const; +}; + +} // namespace nixd diff --git a/nixd/include/nixd/AST/ParseAST.h b/nixd/include/nixd/AST/ParseAST.h index 65bf6f412..01edc8fe9 100644 --- a/nixd/include/nixd/AST/ParseAST.h +++ b/nixd/include/nixd/AST/ParseAST.h @@ -50,9 +50,18 @@ class ParseAST { void prepareDefRef(); + /// Maps nix expression pointers back, to the name of attribute + /// + /// { foo = "bar"; } + /// ^~~ <- ^~~~ + std::map AttrNamesMap; + + void constructAttrNamesMap(); + void staticAnalysis() { ParentMap = getParentMap(root()); prepareDefRef(); + constructAttrNamesMap(); } void bindVarsStatic(const nix::StaticEnv &Env) { @@ -117,6 +126,10 @@ class ParseAST { } } + /// Get the attrpath of the expreesion \p Expr + [[nodiscard]] std::vector + getAttrPath(const nix::Expr *Expr) const; + std::optional searchDef(const nix::ExprVar *Var) const; [[nodiscard]] std::vector ref(Definition D) const { diff --git a/nixd/include/nixd/Nix/Value.h b/nixd/include/nixd/Nix/Value.h index cdbac2a60..c02e38407 100644 --- a/nixd/include/nixd/Nix/Value.h +++ b/nixd/include/nixd/Nix/Value.h @@ -13,7 +13,7 @@ bool isDerivation(nix::EvalState &State, nix::Value &V); std::optional attrPathStr(nix::EvalState &State, nix::Value &V, const std::string &AttrPath) noexcept; -/// Select the path given in \p AttrPath, and return the value +/// Select the path given in \p AttrPath, and \return the value nix::Value selectAttrPath(nix::EvalState &State, nix::Value Set, const std::vector &AttrPath); diff --git a/nixd/lib/AST/AttrLocator.cpp b/nixd/lib/AST/AttrLocator.cpp new file mode 100644 index 000000000..249386162 --- /dev/null +++ b/nixd/lib/AST/AttrLocator.cpp @@ -0,0 +1,41 @@ +#include "nixd/AST/AttrLocator.h" +#include "nixd/AST/ParseAST.h" +#include "nixd/Expr/Expr.h" + +namespace nixd { + +AttrLocator::AttrLocator(const ParseAST &AST) { + // Traverse the AST, record all attr locations + // We can assume that Nix attrs are not interleaving + // i.e. they are concrete tokens. + struct VTy : RecursiveASTVisitor { + AttrLocator &This; + const ParseAST &AST; + bool visitExprAttrs(const nix::ExprAttrs *EA) { + for (const auto &[Symbol, Attr] : EA->attrs) { + lspserver::Range Range = AST.nPair(Attr.pos); + // Note: "a.b.c" will be assigned more than once + // Our visitor model ensures the order that descendant nodes will be + // visited at last, for nesting attrs. + This.Attrs[Range] = Attr.e; + } + return true; + } + } V{.This = *this, .AST = AST}; + V.traverseExpr(AST.root()); +} + +const nix::Expr *AttrLocator::locate(lspserver::Position Pos) const { + auto It = Attrs.upper_bound({Pos, {INT_MAX, INT_MAX}}); + if (It == Attrs.begin()) + return nullptr; + --It; + const auto &[Range, Expr] = *It; + + // Check that the range actually contains the desired position. + // Otherwise, we should return nullptr, denoting the position is not covered + // by any knwon attrs. + return Range.contains(Pos) ? Expr : nullptr; +} + +} // namespace nixd diff --git a/nixd/lib/AST/ParseAST.cpp b/nixd/lib/AST/ParseAST.cpp index 44c702cdd..5e2501519 100644 --- a/nixd/lib/AST/ParseAST.cpp +++ b/nixd/lib/AST/ParseAST.cpp @@ -143,6 +143,31 @@ void ParseAST::prepareDefRef() { V.traverseExpr(root()); } +void ParseAST::constructAttrNamesMap() { + struct VTy : RecursiveASTVisitor { + ParseAST &This; + bool visitExprAttrs(const nix::ExprAttrs *EA) { + for (const auto &[Symbol, AttrDef] : EA->attrs) { + This.AttrNamesMap[AttrDef.e] = Symbol; + } + return true; + } + } V{.This = *this}; + V.traverseExpr(root()); +} + +std::vector ParseAST::getAttrPath(const nix::Expr *Expr) const { + std::vector Result; + for (; parent(Expr) != Expr; Expr = parent(Expr)) { + if (dynamic_cast(parent(Expr))) { + auto Symbol = AttrNamesMap.at(Expr); + Result.emplace_back(symbols()[Symbol]); + } + } + std::reverse(Result.begin(), Result.end()); + return Result; +} + std::optional ParseAST::searchDef(const nix::ExprVar *Var) const { if (Var->fromWith) diff --git a/nixd/lib/AST/meson.build b/nixd/lib/AST/meson.build index 339cdb613..9789bdef6 100644 --- a/nixd/lib/AST/meson.build +++ b/nixd/lib/AST/meson.build @@ -7,6 +7,7 @@ libnixdASTDeps = [ nixd_lsp_server ] libnixdAST = library('nixdAST' +, 'AttrLocator.cpp' , 'EvalAST.cpp' , 'ParseAST.cpp' , include_directories: nixd_inc diff --git a/nixd/test/ast.cpp b/nixd/test/ast.cpp index b9ad6500f..ebc70077b 100644 --- a/nixd/test/ast.cpp +++ b/nixd/test/ast.cpp @@ -2,15 +2,18 @@ #include "lspserver/Protocol.h" +#include "nixd/AST/AttrLocator.h" #include "nixd/AST/EvalAST.h" #include "nixd/AST/ParseAST.h" - #include "nixd/Parser/Parser.h" + #include "nixutil.h" #include #include +#include + namespace nixd { TEST(AST, lookupEnd) { @@ -99,4 +102,26 @@ TEST(AST, lookupContainMin) { } } +static void checkVec(const std::vector &Vec, + const std::string &Expected) { + auto VecStr = boost::algorithm::join(Vec, "."); + ASSERT_EQ(VecStr, Expected); +} + +TEST(AST, AttrLocator) { + std::string NixSrc = R"( +{ + x.y = 1; + foo.bar.baz.foo. +} + )"; + InitNix INix; + auto State = INix.getDummyState(); + auto AST = ParseAST::create( + parse(NixSrc, nix::CanonPath("foo"), nix::CanonPath("/"), *State)); + AttrLocator Locator(*AST); + checkVec(AST->getAttrPath(Locator.locate({2, 4})), "x.y"); + checkVec(AST->getAttrPath(Locator.locate({3, 16})), "foo.bar.baz.foo"); +} + } // namespace nixd