mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-22 05:56:15 +02:00
Track doc comments and render them in :doc
This commit is contained in:
parent
e5af7cbeb9
commit
7fae378835
25 changed files with 515 additions and 16 deletions
53
doc/manual/rl-next/repl-doc-renders-doc-comments.md
Normal file
53
doc/manual/rl-next/repl-doc-renders-doc-comments.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
synopsis: "`nix-repl`'s `:doc` shows documentation comments"
|
||||||
|
significance: significant
|
||||||
|
issues:
|
||||||
|
- 3904
|
||||||
|
- 10771
|
||||||
|
prs:
|
||||||
|
- 1652
|
||||||
|
- 9054
|
||||||
|
- 11072
|
||||||
|
---
|
||||||
|
|
||||||
|
`nix repl` has a `:doc` command that previously only rendered documentation for internally defined functions.
|
||||||
|
This feature has been extended to also render function documentation comments, in accordance with [RFC 145].
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-repl> :doc lib.toFunction
|
||||||
|
Function toFunction
|
||||||
|
… defined at /home/user/h/nixpkgs/lib/trivial.nix:1072:5
|
||||||
|
|
||||||
|
Turns any non-callable values into constant functions. Returns
|
||||||
|
callable values as is.
|
||||||
|
|
||||||
|
Inputs
|
||||||
|
|
||||||
|
v
|
||||||
|
|
||||||
|
: Any value
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
:::{.example}
|
||||||
|
|
||||||
|
## lib.trivial.toFunction usage example
|
||||||
|
|
||||||
|
| nix-repl> lib.toFunction 1 2
|
||||||
|
| 1
|
||||||
|
|
|
||||||
|
| nix-repl> lib.toFunction (x: x + 1) 2
|
||||||
|
| 3
|
||||||
|
|
||||||
|
:::
|
||||||
|
```
|
||||||
|
|
||||||
|
Known limitations:
|
||||||
|
- It currently only works for functions. We plan to extend this to attributes, which may contain arbitrary values.
|
||||||
|
- Some extensions to markdown are not yet supported, as you can see in the example above.
|
||||||
|
|
||||||
|
We'd like to acknowledge Yingchi Long for proposing a proof of concept for this functionality in [#9054](https://github.com/NixOS/nix/pull/9054), as well as @sternenseemann and Johannes Kirschbauer for their contributions, proposals, and their work on [RFC 145].
|
||||||
|
|
||||||
|
[RFC 145]: https://github.com/NixOS/rfcs/pull/145
|
|
@ -559,6 +559,67 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
||||||
.doc = doc,
|
.doc = doc,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (v.isLambda()) {
|
||||||
|
auto exprLambda = v.payload.lambda.fun;
|
||||||
|
|
||||||
|
std::stringstream s(std::ios_base::out);
|
||||||
|
std::string name;
|
||||||
|
auto pos = positions[exprLambda->getPos()];
|
||||||
|
std::string docStr;
|
||||||
|
|
||||||
|
if (exprLambda->name) {
|
||||||
|
name = symbols[exprLambda->name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exprLambda->docComment) {
|
||||||
|
auto begin = positions[exprLambda->docComment.begin];
|
||||||
|
auto end = positions[exprLambda->docComment.end];
|
||||||
|
auto docCommentStr = begin.getSnippetUpTo(end);
|
||||||
|
|
||||||
|
// Strip "/**" and "*/"
|
||||||
|
constexpr size_t prefixLen = 3;
|
||||||
|
constexpr size_t suffixLen = 2;
|
||||||
|
docStr = docCommentStr.substr(prefixLen, docCommentStr.size() - prefixLen - suffixLen);
|
||||||
|
if (docStr.empty())
|
||||||
|
return {};
|
||||||
|
// Turn the now missing "/**" into indentation
|
||||||
|
docStr = " " + docStr;
|
||||||
|
// Strip indentation (for the whole, potentially multi-line string)
|
||||||
|
docStr = stripIndentation(docStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.empty()) {
|
||||||
|
s << "Function ";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s << "Function **" << name << "**";
|
||||||
|
if (pos)
|
||||||
|
s << "\\\n … " ;
|
||||||
|
else
|
||||||
|
s << "\\\n";
|
||||||
|
}
|
||||||
|
if (pos) {
|
||||||
|
s << "defined at " << pos;
|
||||||
|
}
|
||||||
|
if (!docStr.empty()) {
|
||||||
|
s << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
s << docStr;
|
||||||
|
|
||||||
|
s << '\0'; // for making a c string below
|
||||||
|
std::string ss = s.str();
|
||||||
|
|
||||||
|
return Doc {
|
||||||
|
.pos = pos,
|
||||||
|
.name = name,
|
||||||
|
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
|
||||||
|
.args = {},
|
||||||
|
.doc =
|
||||||
|
// FIXME: this leaks; make the field std::string?
|
||||||
|
strdup(ss.data()),
|
||||||
|
};
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
%option stack
|
%option stack
|
||||||
%option nodefault
|
%option nodefault
|
||||||
%option nounput noyy_top_state
|
%option nounput noyy_top_state
|
||||||
|
%option extra-type="::nix::LexerState *"
|
||||||
|
|
||||||
%s DEFAULT
|
%s DEFAULT
|
||||||
%x STRING
|
%x STRING
|
||||||
|
@ -23,6 +23,12 @@
|
||||||
#include "nixexpr.hh"
|
#include "nixexpr.hh"
|
||||||
#include "parser-tab.hh"
|
#include "parser-tab.hh"
|
||||||
|
|
||||||
|
// !!! FIXME !!!
|
||||||
|
#define YY_EXTRA_TYPE ::nix::LexerState *
|
||||||
|
int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner);
|
||||||
|
YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner );
|
||||||
|
#undef YY_EXTRA_TYPE
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -35,10 +41,24 @@ static void initLoc(YYLTYPE * loc)
|
||||||
loc->first_column = loc->last_column = 0;
|
loc->first_column = loc->last_column = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
|
static void adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len)
|
||||||
{
|
{
|
||||||
loc->stash();
|
loc->stash();
|
||||||
|
|
||||||
|
LexerState & lexerState = *yyget_extra(yyscanner);
|
||||||
|
|
||||||
|
if (lexerState.docCommentDistance == 1) {
|
||||||
|
// Preceding token was a doc comment.
|
||||||
|
ParserLocation doc;
|
||||||
|
doc.first_column = lexerState.lastDocCommentLoc.first_column;
|
||||||
|
ParserLocation docEnd;
|
||||||
|
docEnd.first_column = lexerState.lastDocCommentLoc.last_column;
|
||||||
|
DocComment docComment{lexerState.at(doc), lexerState.at(docEnd)};
|
||||||
|
PosIdx locPos = lexerState.at(*loc);
|
||||||
|
lexerState.positionToDocComment.emplace(locPos, docComment);
|
||||||
|
}
|
||||||
|
lexerState.docCommentDistance++;
|
||||||
|
|
||||||
loc->first_column = loc->last_column;
|
loc->first_column = loc->last_column;
|
||||||
loc->last_column += len;
|
loc->last_column += len;
|
||||||
}
|
}
|
||||||
|
@ -79,7 +99,7 @@ static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length)
|
||||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||||
|
|
||||||
#define YY_USER_INIT initLoc(yylloc)
|
#define YY_USER_INIT initLoc(yylloc)
|
||||||
#define YY_USER_ACTION adjustLoc(yylloc, yytext, yyleng);
|
#define YY_USER_ACTION adjustLoc(yyscanner, yylloc, yytext, yyleng);
|
||||||
|
|
||||||
#define PUSH_STATE(state) yy_push_state(state, yyscanner)
|
#define PUSH_STATE(state) yy_push_state(state, yyscanner)
|
||||||
#define POP_STATE() yy_pop_state(yyscanner)
|
#define POP_STATE() yy_pop_state(yyscanner)
|
||||||
|
@ -279,9 +299,33 @@ or { return OR_KW; }
|
||||||
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
|
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
|
||||||
{URI} { yylval->uri = {yytext, (size_t) yyleng}; return URI; }
|
{URI} { yylval->uri = {yytext, (size_t) yyleng}; return URI; }
|
||||||
|
|
||||||
[ \t\r\n]+ /* eat up whitespace */
|
%{
|
||||||
\#[^\r\n]* /* single-line comments */
|
// Doc comment rule
|
||||||
\/\*([^*]|\*+[^*/])*\*+\/ /* long comments */
|
//
|
||||||
|
// \/\*\* /**
|
||||||
|
// [^/*] reject /**/ (empty comment) and /***
|
||||||
|
// ([^*]|\*+[^*/])*\*+\/ same as the long comment rule
|
||||||
|
// ( )* zero or more non-ending sequences
|
||||||
|
// \* end(1)
|
||||||
|
// \/ end(2)
|
||||||
|
%}
|
||||||
|
\/\*\*[^/*]([^*]|\*+[^*/])*\*+\/ /* doc comments */ {
|
||||||
|
LexerState & lexerState = *yyget_extra(yyscanner);
|
||||||
|
lexerState.docCommentDistance = 0;
|
||||||
|
lexerState.lastDocCommentLoc.first_line = yylloc->first_line;
|
||||||
|
lexerState.lastDocCommentLoc.first_column = yylloc->first_column;
|
||||||
|
lexerState.lastDocCommentLoc.last_column = yylloc->last_column;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
%{
|
||||||
|
// The following rules have docCommentDistance--
|
||||||
|
// This compensates for the docCommentDistance++ which happens by default to
|
||||||
|
// make all the other rules invalidate the doc comment.
|
||||||
|
%}
|
||||||
|
[ \t\r\n]+ /* eat up whitespace */ { yyget_extra(yyscanner)->docCommentDistance--; }
|
||||||
|
\#[^\r\n]* /* single-line comments */ { yyget_extra(yyscanner)->docCommentDistance--; }
|
||||||
|
\/\*([^*]|\*+[^*/])*\*+\/ /* long comments */ { yyget_extra(yyscanner)->docCommentDistance--; }
|
||||||
|
|
||||||
{ANY} {
|
{ANY} {
|
||||||
/* Don't return a negative number, as this will cause
|
/* Don't return a negative number, as this will cause
|
||||||
|
|
|
@ -583,6 +583,20 @@ std::string ExprLambda::showNamePos(const EvalState & state) const
|
||||||
return fmt("%1% at %2%", id, state.positions[pos]);
|
return fmt("%1% at %2%", id, state.positions[pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExprLambda::setDocComment(DocComment docComment) {
|
||||||
|
if (!this->docComment) {
|
||||||
|
this->docComment = docComment;
|
||||||
|
|
||||||
|
// Curried functions are defined by putting a function directly
|
||||||
|
// in the body of another function. To render docs for those, we
|
||||||
|
// need to propagate the doc comment to the innermost function.
|
||||||
|
//
|
||||||
|
// If we have our own comment, we've already propagated it, so this
|
||||||
|
// belongs in the same conditional.
|
||||||
|
body->setDocComment(docComment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Position table. */
|
/* Position table. */
|
||||||
|
|
|
@ -11,13 +11,56 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
struct Env;
|
|
||||||
struct Value;
|
|
||||||
class EvalState;
|
class EvalState;
|
||||||
|
class PosTable;
|
||||||
|
struct Env;
|
||||||
struct ExprWith;
|
struct ExprWith;
|
||||||
struct StaticEnv;
|
struct StaticEnv;
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A documentation comment, in the sense of [RFC 145](https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md)
|
||||||
|
*
|
||||||
|
* Note that this does not implement the following:
|
||||||
|
* - argument attribute names ("formals"): TBD
|
||||||
|
* - argument names: these are internal to the function and their names may not be optimal for documentation
|
||||||
|
* - function arity (degree of currying or number of ':'s):
|
||||||
|
* - Functions returning partially applied functions have a higher arity
|
||||||
|
* than can be determined locally and without evaluation.
|
||||||
|
* We do not want to present false data.
|
||||||
|
* - Some functions should be thought of as transformations of other
|
||||||
|
* functions. For instance `overlay -> overlay -> overlay` is the simplest
|
||||||
|
* way to understand `composeExtensions`, but its implementation looks like
|
||||||
|
* `f: g: final: prev: <...>`. The parameters `final` and `prev` are part
|
||||||
|
* of the overlay concept, while distracting from the function's purpose.
|
||||||
|
*/
|
||||||
|
struct DocComment {
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wcomment" // "nested comment start" is intentional
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start of the comment, including `/**`.
|
||||||
|
*/
|
||||||
|
PosIdx begin;
|
||||||
|
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position right after the final asterisk and `/` that terminate the comment.
|
||||||
|
*/
|
||||||
|
PosIdx end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the comment is set.
|
||||||
|
*
|
||||||
|
* A `DocComment` is small enough that it makes sense to pass by value, and
|
||||||
|
* therefore baking optionality into it is also useful, to avoiding the memory
|
||||||
|
* overhead of `std::optional`.
|
||||||
|
*/
|
||||||
|
operator bool() const { return static_cast<bool>(begin); }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attribute path is a sequence of attribute names.
|
* An attribute path is a sequence of attribute names.
|
||||||
|
@ -54,6 +97,7 @@ struct Expr
|
||||||
virtual void eval(EvalState & state, Env & env, Value & v);
|
virtual void eval(EvalState & state, Env & env, Value & v);
|
||||||
virtual Value * maybeThunk(EvalState & state, Env & env);
|
virtual Value * maybeThunk(EvalState & state, Env & env);
|
||||||
virtual void setName(Symbol name);
|
virtual void setName(Symbol name);
|
||||||
|
virtual void setDocComment(DocComment docComment) { };
|
||||||
virtual PosIdx getPos() const { return noPos; }
|
virtual PosIdx getPos() const { return noPos; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -278,6 +322,8 @@ struct ExprLambda : Expr
|
||||||
Symbol arg;
|
Symbol arg;
|
||||||
Formals * formals;
|
Formals * formals;
|
||||||
Expr * body;
|
Expr * body;
|
||||||
|
DocComment docComment;
|
||||||
|
|
||||||
ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body)
|
ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body)
|
||||||
: pos(pos), arg(arg), formals(formals), body(body)
|
: pos(pos), arg(arg), formals(formals), body(body)
|
||||||
{
|
{
|
||||||
|
@ -290,6 +336,7 @@ struct ExprLambda : Expr
|
||||||
std::string showNamePos(const EvalState & state) const;
|
std::string showNamePos(const EvalState & state) const;
|
||||||
inline bool hasFormals() const { return formals != nullptr; }
|
inline bool hasFormals() const { return formals != nullptr; }
|
||||||
PosIdx getPos() const override { return pos; }
|
PosIdx getPos() const override { return pos; }
|
||||||
|
virtual void setDocComment(DocComment docComment) override;
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -35,10 +37,44 @@ struct ParserLocation
|
||||||
first_column = stashed_first_column;
|
first_column = stashed_first_column;
|
||||||
last_column = stashed_last_column;
|
last_column = stashed_last_column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Latest doc comment position, or 0. */
|
||||||
|
int doc_comment_first_line, doc_comment_first_column, doc_comment_last_column;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LexerState
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Tracks the distance to the last doc comment, in terms of lexer tokens.
|
||||||
|
*
|
||||||
|
* The lexer sets this to 0 when reading a doc comment, and increments it
|
||||||
|
* for every matched rule; see `lexer-helpers.cc`.
|
||||||
|
* Whitespace and comment rules decrement the distance, so that they result
|
||||||
|
* in a net 0 change in distance.
|
||||||
|
*/
|
||||||
|
int docCommentDistance = std::numeric_limits<int>::max();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of the last doc comment.
|
||||||
|
*
|
||||||
|
* (stashing fields are not used)
|
||||||
|
*/
|
||||||
|
ParserLocation lastDocCommentLoc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Maps some positions to a DocComment, where the comment is relevant to the location.
|
||||||
|
*/
|
||||||
|
std::map<PosIdx, DocComment> positionToDocComment;
|
||||||
|
|
||||||
|
PosTable & positions;
|
||||||
|
PosTable::Origin origin;
|
||||||
|
|
||||||
|
PosIdx at(const ParserLocation & loc);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ParserState
|
struct ParserState
|
||||||
{
|
{
|
||||||
|
const LexerState & lexerState;
|
||||||
SymbolTable & symbols;
|
SymbolTable & symbols;
|
||||||
PosTable & positions;
|
PosTable & positions;
|
||||||
Expr * result;
|
Expr * result;
|
||||||
|
@ -270,6 +306,11 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
|
||||||
return new ExprConcatStrings(pos, true, es2);
|
return new ExprConcatStrings(pos, true, es2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline PosIdx LexerState::at(const ParserLocation & loc)
|
||||||
|
{
|
||||||
|
return positions.add(origin, loc.first_column);
|
||||||
|
}
|
||||||
|
|
||||||
inline PosIdx ParserState::at(const ParserLocation & loc)
|
inline PosIdx ParserState::at(const ParserLocation & loc)
|
||||||
{
|
{
|
||||||
return positions.add(origin, loc.first_column);
|
return positions.add(origin, loc.first_column);
|
||||||
|
|
|
@ -74,6 +74,14 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char *
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SET_DOC_POS(lambda, pos) setDocPosition(state->lexerState, lambda, state->at(pos))
|
||||||
|
static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, PosIdx start) {
|
||||||
|
auto it = lexerState.positionToDocComment.find(start);
|
||||||
|
if (it != lexerState.positionToDocComment.end()) {
|
||||||
|
lambda->setDocComment(it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
@ -119,6 +127,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char *
|
||||||
%token IND_STRING_OPEN IND_STRING_CLOSE
|
%token IND_STRING_OPEN IND_STRING_CLOSE
|
||||||
%token ELLIPSIS
|
%token ELLIPSIS
|
||||||
|
|
||||||
|
|
||||||
%right IMPL
|
%right IMPL
|
||||||
%left OR
|
%left OR
|
||||||
%left AND
|
%left AND
|
||||||
|
@ -140,18 +149,28 @@ expr: expr_function;
|
||||||
|
|
||||||
expr_function
|
expr_function
|
||||||
: ID ':' expr_function
|
: ID ':' expr_function
|
||||||
{ $$ = new ExprLambda(CUR_POS, state->symbols.create($1), 0, $3); }
|
{ auto me = new ExprLambda(CUR_POS, state->symbols.create($1), 0, $3);
|
||||||
|
$$ = me;
|
||||||
|
SET_DOC_POS(me, @1);
|
||||||
|
}
|
||||||
| '{' formals '}' ':' expr_function
|
| '{' formals '}' ':' expr_function
|
||||||
{ $$ = new ExprLambda(CUR_POS, state->validateFormals($2), $5); }
|
{ auto me = new ExprLambda(CUR_POS, state->validateFormals($2), $5);
|
||||||
|
$$ = me;
|
||||||
|
SET_DOC_POS(me, @1);
|
||||||
|
}
|
||||||
| '{' formals '}' '@' ID ':' expr_function
|
| '{' formals '}' '@' ID ':' expr_function
|
||||||
{
|
{
|
||||||
auto arg = state->symbols.create($5);
|
auto arg = state->symbols.create($5);
|
||||||
$$ = new ExprLambda(CUR_POS, arg, state->validateFormals($2, CUR_POS, arg), $7);
|
auto me = new ExprLambda(CUR_POS, arg, state->validateFormals($2, CUR_POS, arg), $7);
|
||||||
|
$$ = me;
|
||||||
|
SET_DOC_POS(me, @1);
|
||||||
}
|
}
|
||||||
| ID '@' '{' formals '}' ':' expr_function
|
| ID '@' '{' formals '}' ':' expr_function
|
||||||
{
|
{
|
||||||
auto arg = state->symbols.create($1);
|
auto arg = state->symbols.create($1);
|
||||||
$$ = new ExprLambda(CUR_POS, arg, state->validateFormals($4, CUR_POS, arg), $7);
|
auto me = new ExprLambda(CUR_POS, arg, state->validateFormals($4, CUR_POS, arg), $7);
|
||||||
|
$$ = me;
|
||||||
|
SET_DOC_POS(me, @1);
|
||||||
}
|
}
|
||||||
| ASSERT expr ';' expr_function
|
| ASSERT expr ';' expr_function
|
||||||
{ $$ = new ExprAssert(CUR_POS, $2, $4); }
|
{ $$ = new ExprAssert(CUR_POS, $2, $4); }
|
||||||
|
@ -312,7 +331,20 @@ ind_string_parts
|
||||||
;
|
;
|
||||||
|
|
||||||
binds
|
binds
|
||||||
: binds attrpath '=' expr ';' { $$ = $1; state->addAttr($$, std::move(*$2), $4, state->at(@2)); delete $2; }
|
: binds attrpath '=' expr ';' {
|
||||||
|
$$ = $1;
|
||||||
|
|
||||||
|
auto pos = state->at(@2);
|
||||||
|
{
|
||||||
|
auto it = state->lexerState.positionToDocComment.find(pos);
|
||||||
|
if (it != state->lexerState.positionToDocComment.end()) {
|
||||||
|
$4->setDocComment(it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state->addAttr($$, std::move(*$2), $4, pos);
|
||||||
|
delete $2;
|
||||||
|
}
|
||||||
| binds INHERIT attrs ';'
|
| binds INHERIT attrs ';'
|
||||||
{ $$ = $1;
|
{ $$ = $1;
|
||||||
for (auto & [i, iPos] : *$3) {
|
for (auto & [i, iPos] : *$3) {
|
||||||
|
@ -435,17 +467,22 @@ Expr * parseExprFromBuf(
|
||||||
const Expr::AstSymbols & astSymbols)
|
const Expr::AstSymbols & astSymbols)
|
||||||
{
|
{
|
||||||
yyscan_t scanner;
|
yyscan_t scanner;
|
||||||
|
LexerState lexerState {
|
||||||
|
.positions = positions,
|
||||||
|
.origin = positions.addOrigin(origin, length),
|
||||||
|
};
|
||||||
ParserState state {
|
ParserState state {
|
||||||
|
.lexerState = lexerState,
|
||||||
.symbols = symbols,
|
.symbols = symbols,
|
||||||
.positions = positions,
|
.positions = positions,
|
||||||
.basePath = basePath,
|
.basePath = basePath,
|
||||||
.origin = positions.addOrigin(origin, length),
|
.origin = lexerState.origin,
|
||||||
.rootFS = rootFS,
|
.rootFS = rootFS,
|
||||||
.s = astSymbols,
|
.s = astSymbols,
|
||||||
.settings = settings,
|
.settings = settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
yylex_init(&scanner);
|
yylex_init_extra(&lexerState, &scanner);
|
||||||
Finally _destroy([&] { yylex_destroy(scanner); });
|
Finally _destroy([&] { yylex_destroy(scanner); });
|
||||||
|
|
||||||
yy_scan_buffer(text, length, scanner);
|
yy_scan_buffer(text, length, scanner);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
source common.sh
|
source common.sh
|
||||||
|
source characterisation/framework.sh
|
||||||
|
|
||||||
testDir="$PWD"
|
testDir="$PWD"
|
||||||
cd "$TEST_ROOT"
|
cd "$TEST_ROOT"
|
||||||
|
@ -244,3 +245,32 @@ testReplResponseNoRegex '
|
||||||
y = { a = 1 };
|
y = { a = 1 };
|
||||||
}
|
}
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# TODO: move init to characterisation/framework.sh
|
||||||
|
badDiff=0
|
||||||
|
badExitCode=0
|
||||||
|
|
||||||
|
nixVersion="$(nix eval --impure --raw --expr 'builtins.nixVersion' --extra-experimental-features nix-command)"
|
||||||
|
|
||||||
|
runRepl () {
|
||||||
|
# TODO: pass arguments to nix repl; see lang.sh
|
||||||
|
nix repl 2>&1 \
|
||||||
|
| stripColors \
|
||||||
|
| sed \
|
||||||
|
-e "s@$testDir@/path/to/tests/functional@g" \
|
||||||
|
-e "s@$nixVersion@<nix version>@g" \
|
||||||
|
-e "s@Added [0-9]* variables@Added <number omitted> variables@g" \
|
||||||
|
| grep -vF $'warning: you don\'t have Internet access; disabling some network-dependent features' \
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
for test in $(cd "$testDir/repl"; echo *.in); do
|
||||||
|
test="$(basename "$test" .in)"
|
||||||
|
in="$testDir/repl/$test.in"
|
||||||
|
actual="$testDir/repl/$test.actual"
|
||||||
|
expected="$testDir/repl/$test.expected"
|
||||||
|
(cd "$testDir/repl"; set +x; runRepl 2>&1) < "$in" > "$actual"
|
||||||
|
diffAndAcceptInner "$test" "$actual" "$expected"
|
||||||
|
done
|
||||||
|
|
||||||
|
characterisationTestExit
|
||||||
|
|
0
tests/functional/repl/characterisation/empty
Normal file
0
tests/functional/repl/characterisation/empty
Normal file
8
tests/functional/repl/doc-comment-function.expected
Normal file
8
tests/functional/repl/doc-comment-function.expected
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Nix <nix version>
|
||||||
|
Type :? for help.
|
||||||
|
Function defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comment-function.nix:2:1
|
||||||
|
|
||||||
|
A doc comment for a file that only contains a function
|
||||||
|
|
||||||
|
|
1
tests/functional/repl/doc-comment-function.in
Normal file
1
tests/functional/repl/doc-comment-function.in
Normal file
|
@ -0,0 +1 @@
|
||||||
|
:doc import ./doc-comment-function.nix
|
3
tests/functional/repl/doc-comment-function.nix
Normal file
3
tests/functional/repl/doc-comment-function.nix
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/** A doc comment for a file that only contains a function */
|
||||||
|
{ ... }:
|
||||||
|
{ }
|
57
tests/functional/repl/doc-comments.nix
Normal file
57
tests/functional/repl/doc-comments.nix
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
Perform *arithmetic* multiplication. It's kind of like repeated **addition**, very neat.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
multiply 2 3
|
||||||
|
=> 6
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
multiply = x: y: x * y;
|
||||||
|
|
||||||
|
/**👈 precisely this wide 👉*/
|
||||||
|
measurement = x: x;
|
||||||
|
|
||||||
|
floatedIn = /** This also works. */
|
||||||
|
x: y: x;
|
||||||
|
|
||||||
|
compact=/**boom*/x: x;
|
||||||
|
|
||||||
|
/** Ignore!!! */
|
||||||
|
unambiguous =
|
||||||
|
/** Very close */
|
||||||
|
x: x;
|
||||||
|
|
||||||
|
/** Firmly rigid. */
|
||||||
|
constant = true;
|
||||||
|
|
||||||
|
/** Immovably fixed. */
|
||||||
|
lib.version = "9000";
|
||||||
|
|
||||||
|
/** Unchangeably constant. */
|
||||||
|
lib.attr.empty = { };
|
||||||
|
|
||||||
|
lib.attr.undocumented = { };
|
||||||
|
|
||||||
|
nonStrict = /** My syntax is not strict, but I'm strict anyway. */ x: x;
|
||||||
|
strict = /** I don't have to be strict, but I am anyway. */ { ... }: null;
|
||||||
|
# Note that pre and post are the same here. I just had to name them somehow.
|
||||||
|
strictPre = /** Here's one way to do this */ a@{ ... }: a;
|
||||||
|
strictPost = /** Here's another way to do this */ { ... }@a: a;
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
# /** This returns a documented function. */
|
||||||
|
# documentedArgs =
|
||||||
|
# /** x */
|
||||||
|
# x:
|
||||||
|
# /** y */
|
||||||
|
# y:
|
||||||
|
# /** x + y */
|
||||||
|
# x + y;
|
||||||
|
|
||||||
|
# /** Documented formals */
|
||||||
|
# documentedFormals =
|
||||||
|
# /** x */
|
||||||
|
# x: x;
|
||||||
|
}
|
11
tests/functional/repl/doc-compact.expected
Normal file
11
tests/functional/repl/doc-compact.expected
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Nix <nix version>
|
||||||
|
Type :? for help.
|
||||||
|
Added <number omitted> variables.
|
||||||
|
|
||||||
|
Function compact
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:18:20
|
||||||
|
|
||||||
|
boom
|
||||||
|
|
||||||
|
|
2
tests/functional/repl/doc-compact.in
Normal file
2
tests/functional/repl/doc-compact.in
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
:l doc-comments.nix
|
||||||
|
:doc compact
|
11
tests/functional/repl/doc-floatedIn.expected
Normal file
11
tests/functional/repl/doc-floatedIn.expected
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Nix <nix version>
|
||||||
|
Type :? for help.
|
||||||
|
Added <number omitted> variables.
|
||||||
|
|
||||||
|
Function floatedIn
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:16:5
|
||||||
|
|
||||||
|
This also works.
|
||||||
|
|
||||||
|
|
2
tests/functional/repl/doc-floatedIn.in
Normal file
2
tests/functional/repl/doc-floatedIn.in
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
:l doc-comments.nix
|
||||||
|
:doc floatedIn
|
29
tests/functional/repl/doc-lambda-flavors.expected
Normal file
29
tests/functional/repl/doc-lambda-flavors.expected
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
Nix <nix version>
|
||||||
|
Type :? for help.
|
||||||
|
Added <number omitted> variables.
|
||||||
|
|
||||||
|
Function nonStrict
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:36:70
|
||||||
|
|
||||||
|
My syntax is not strict, but I'm strict anyway.
|
||||||
|
|
||||||
|
Function strict
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:37:63
|
||||||
|
|
||||||
|
I don't have to be strict, but I am anyway.
|
||||||
|
|
||||||
|
Function strictPre
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:39:48
|
||||||
|
|
||||||
|
Here's one way to do this
|
||||||
|
|
||||||
|
Function strictPost
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:40:53
|
||||||
|
|
||||||
|
Here's another way to do this
|
||||||
|
|
||||||
|
|
5
tests/functional/repl/doc-lambda-flavors.in
Normal file
5
tests/functional/repl/doc-lambda-flavors.in
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
:l doc-comments.nix
|
||||||
|
:doc nonStrict
|
||||||
|
:doc strict
|
||||||
|
:doc strictPre
|
||||||
|
:doc strictPost
|
11
tests/functional/repl/doc-measurement.expected
Normal file
11
tests/functional/repl/doc-measurement.expected
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Nix <nix version>
|
||||||
|
Type :? for help.
|
||||||
|
Added <number omitted> variables.
|
||||||
|
|
||||||
|
Function measurement
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:13:17
|
||||||
|
|
||||||
|
👈 precisely this wide 👉
|
||||||
|
|
||||||
|
|
2
tests/functional/repl/doc-measurement.in
Normal file
2
tests/functional/repl/doc-measurement.in
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
:l doc-comments.nix
|
||||||
|
:doc measurement
|
15
tests/functional/repl/doc-multiply.expected
Normal file
15
tests/functional/repl/doc-multiply.expected
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
Nix <nix version>
|
||||||
|
Type :? for help.
|
||||||
|
Added <number omitted> variables.
|
||||||
|
|
||||||
|
Function multiply
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:10:14
|
||||||
|
|
||||||
|
Perform arithmetic multiplication. It's kind of like
|
||||||
|
repeated addition, very neat.
|
||||||
|
|
||||||
|
| multiply 2 3
|
||||||
|
| => 6
|
||||||
|
|
||||||
|
|
2
tests/functional/repl/doc-multiply.in
Normal file
2
tests/functional/repl/doc-multiply.in
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
:l doc-comments.nix
|
||||||
|
:doc multiply
|
11
tests/functional/repl/doc-unambiguous.expected
Normal file
11
tests/functional/repl/doc-unambiguous.expected
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Nix <nix version>
|
||||||
|
Type :? for help.
|
||||||
|
Added <number omitted> variables.
|
||||||
|
|
||||||
|
Function unambiguous
|
||||||
|
… defined at
|
||||||
|
/path/to/tests/functional/repl/doc-comments.nix:23:5
|
||||||
|
|
||||||
|
Very close
|
||||||
|
|
||||||
|
|
2
tests/functional/repl/doc-unambiguous.in
Normal file
2
tests/functional/repl/doc-unambiguous.in
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
:l doc-comments.nix
|
||||||
|
:doc unambiguous
|
Loading…
Reference in a new issue