aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/parser.y
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2007-11-30 16:48:45 +0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2007-11-30 16:48:45 +0000
commit6d6c68c0d29310b6eca35f58b1e68f495d6cd33a (patch)
treeeec6c3c138951f1f0cb3f9b4b1f3b3c177c31afb /src/libexpr/parser.y
parent633518628f48fb9c06bfd570eeca6f62696aba05 (diff)
* Added a new kind of multi-line string literal delimited by two
single quotes. Example (from NixOS): job = '' start on network-interfaces start script rm -f /var/run/opengl-driver ${if videoDriver == "nvidia" then "ln -sf ${nvidiaDrivers} /var/run/opengl-driver" else if cfg.driSupport then "ln -sf ${mesa} /var/run/opengl-driver" else "" } rm -f /var/log/slim.log end script ''; This style has two big advantages: - \, ' and " aren't special, only '' and ${. So you get a lot less escaping in shell scripts / configuration files in Nixpkgs/NixOS. The delimiter '' is rare in scripts (and can usually be written as ""). ${ is also fairly rare. Other delimiters such as <<...>>, {{...}} and <|...|> were also considered but this one appears to have the fewest drawbacks (thanks Martin). - Indentation is intelligently stripped so that multi-line strings can follow the nesting structure of the containing Nix expression. E.g. in the example above 6 spaces are stripped from the start of each line. This prevents unnecessary indentation in generated files (which sometimes even breaks things). See tests/lang/eval-okay-ind-string.nix for some examples.
Diffstat (limited to 'src/libexpr/parser.y')
-rw-r--r--src/libexpr/parser.y107
1 files changed, 104 insertions, 3 deletions
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 82b24cd07..cd3ba88aa 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -68,9 +68,100 @@ static Expr fixAttrs(int recursive, ATermList as)
}
+static Expr stripIndentation(ATermList es)
+{
+ if (es == ATempty) return makeStr("");
+
+ /* Figure out the minimum indentation. Note that by design
+ whitespace-only final lines are not taken into account. (So
+ the " " in "\n ''" is ignored, but the " " in "\n foo''" is.) */
+ bool atStartOfLine = true; /* = seen only whitespace in the current line */
+ unsigned int minIndent = 1000000;
+ unsigned int curIndent = 0;
+ ATerm e;
+ for (ATermIterator i(es); i; ++i) {
+ if (!matchIndStr(*i, e)) {
+ /* Anti-quotations end the current start-of-line whitespace. */
+ if (atStartOfLine) {
+ atStartOfLine = false;
+ if (curIndent < minIndent) minIndent = curIndent;
+ }
+ continue;
+ }
+ string s = aterm2String(e);
+ for (unsigned int j = 0; j < s.size(); ++j) {
+ if (atStartOfLine) {
+ if (s[j] == ' ')
+ curIndent++;
+ else if (s[j] == '\n') {
+ /* Empty line, doesn't influence minimum
+ indentation. */
+ curIndent = 0;
+ } else {
+ atStartOfLine = false;
+ if (curIndent < minIndent) minIndent = curIndent;
+ }
+ } else if (s[j] == '\n') {
+ atStartOfLine = true;
+ curIndent = 0;
+ }
+ }
+ }
+
+ /* Strip spaces from each line. */
+ ATermList es2 = ATempty;
+ atStartOfLine = true;
+ unsigned int curDropped = 0;
+ unsigned int n = ATgetLength(es);
+ for (ATermIterator i(es); i; ++i, --n) {
+ if (!matchIndStr(*i, e)) {
+ atStartOfLine = false;
+ curDropped = 0;
+ es2 = ATinsert(es2, *i);
+ continue;
+ }
+
+ string s = aterm2String(e);
+ string s2;
+ for (unsigned int j = 0; j < s.size(); ++j) {
+ if (atStartOfLine) {
+ if (s[j] == ' ') {
+ if (curDropped++ >= minIndent)
+ s2 += s[j];
+ }
+ else if (s[j] == '\n') {
+ curDropped = 0;
+ s2 += s[j];
+ } else {
+ atStartOfLine = false;
+ curDropped = 0;
+ s2 += s[j];
+ }
+ } else {
+ s2 += s[j];
+ if (s[j] == '\n') atStartOfLine = true;
+ }
+ }
+
+ /* Remove the last line if it is empty and consists only of
+ spaces. */
+ if (n == 1) {
+ unsigned int p = s2.find_last_of('\n');
+ if (p != string::npos && s2.find_first_not_of(' ', p + 1) == string::npos)
+ s2 = string(s2, 0, p + 1);
+ }
+
+ es2 = ATinsert(es2, makeStr(s2));
+ }
+
+ return makeConcatStrings(ATreverse(es2));
+}
+
+
void backToString(yyscan_t scanner);
+void backToIndString(yyscan_t scanner);
+
-
static Pos makeCurPos(YYLTYPE * loc, ParseData * data)
{
return makePos(toATerm(data->path),
@@ -121,10 +212,11 @@ static void freeAndUnprotect(void * p)
%type <t> start expr expr_function expr_if expr_op
%type <t> expr_app expr_select expr_simple bind inheritsrc formal
-%type <ts> binds ids expr_list formals string_parts
-%token <t> ID INT STR PATH URI
+%type <ts> binds ids expr_list formals string_parts ind_string_parts
+%token <t> ID INT STR IND_STR PATH URI
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL
%token DOLLAR_CURLY /* == ${ */
+%token IND_STRING_OPEN IND_STRING_CLOSE
%nonassoc IMPL
%left OR
@@ -199,6 +291,9 @@ expr_simple
else if (ATgetNext($2) == ATempty) $$ = ATgetFirst($2);
else $$ = makeConcatStrings(ATreverse($2));
}
+ | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
+ $$ = stripIndentation(ATreverse($2));
+ }
| PATH { $$ = makePath(toATerm(absPath(aterm2String($1), data->basePath))); }
| URI { $$ = makeStr($1, ATempty); }
| '(' expr ')' { $$ = $2; }
@@ -219,6 +314,12 @@ string_parts
| { $$ = ATempty; }
;
+ind_string_parts
+ : ind_string_parts IND_STR { $$ = ATinsert($1, $2); }
+ | ind_string_parts DOLLAR_CURLY expr '}' { backToIndString(scanner); $$ = ATinsert($1, $3); }
+ | { $$ = ATempty; }
+ ;
+
binds
: binds bind { $$ = ATinsert($1, $2); }
| { $$ = ATempty; }