Skip to content

Commit

Permalink
nixd/AST: support construct attrpaths from AST
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Aug 25, 2023
1 parent f88deb4 commit 075ee10
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 2 deletions.
24 changes: 24 additions & 0 deletions nixd/include/nixd/AST/AttrLocator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include "nixd/AST/ParseAST.h"

#include "lspserver/Protocol.h"

#include <map>

namespace nixd {

class AttrLocator {
std::map<lspserver::Range, const nix::Expr *> 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
13 changes: 13 additions & 0 deletions nixd/include/nixd/AST/ParseAST.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,18 @@ class ParseAST {

void prepareDefRef();

/// Maps nix expression pointers back, to the name of attribute
///
/// { foo = "bar"; }
/// ^~~ <- ^~~~
std::map<const nix::Expr *, nix::Symbol> AttrNamesMap;

void constructAttrNamesMap();

void staticAnalysis() {
ParentMap = getParentMap(root());
prepareDefRef();
constructAttrNamesMap();
}

void bindVarsStatic(const nix::StaticEnv &Env) {
Expand Down Expand Up @@ -117,6 +126,10 @@ class ParseAST {
}
}

/// Get the attrpath of the expreesion \p Expr
[[nodiscard]] std::vector<std::string>
getAttrPath(const nix::Expr *Expr) const;

std::optional<Definition> searchDef(const nix::ExprVar *Var) const;

[[nodiscard]] std::vector<const nix::ExprVar *> ref(Definition D) const {
Expand Down
2 changes: 1 addition & 1 deletion nixd/include/nixd/Nix/Value.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ bool isDerivation(nix::EvalState &State, nix::Value &V);
std::optional<std::string> 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<std::string> &AttrPath);

Expand Down
41 changes: 41 additions & 0 deletions nixd/lib/AST/AttrLocator.cpp
Original file line number Diff line number Diff line change
@@ -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<VTy> {
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
25 changes: 25 additions & 0 deletions nixd/lib/AST/ParseAST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,31 @@ void ParseAST::prepareDefRef() {
V.traverseExpr(root());
}

void ParseAST::constructAttrNamesMap() {
struct VTy : RecursiveASTVisitor<VTy> {
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<std::string> ParseAST::getAttrPath(const nix::Expr *Expr) const {
std::vector<std::string> Result;
for (; parent(Expr) != Expr; Expr = parent(Expr)) {
if (dynamic_cast<const nix::ExprAttrs *>(parent(Expr))) {
auto Symbol = AttrNamesMap.at(Expr);
Result.emplace_back(symbols()[Symbol]);
}
}
std::reverse(Result.begin(), Result.end());
return Result;
}

std::optional<ParseAST::Definition>
ParseAST::searchDef(const nix::ExprVar *Var) const {
if (Var->fromWith)
Expand Down
1 change: 1 addition & 0 deletions nixd/lib/AST/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ libnixdASTDeps = [ nixd_lsp_server
]

libnixdAST = library('nixdAST'
, 'AttrLocator.cpp'
, 'EvalAST.cpp'
, 'ParseAST.cpp'
, include_directories: nixd_inc
Expand Down
27 changes: 26 additions & 1 deletion nixd/test/ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <nix/canon-path.hh>
#include <nix/eval.hh>

#include <boost/algorithm/string/join.hpp>

namespace nixd {

TEST(AST, lookupEnd) {
Expand Down Expand Up @@ -99,4 +102,26 @@ TEST(AST, lookupContainMin) {
}
}

static void checkVec(const std::vector<std::string> &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

0 comments on commit 075ee10

Please sign in to comment.