Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiline strings #793

Merged
merged 18 commits into from
Apr 6, 2021
31 changes: 18 additions & 13 deletions GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ tokens
------

```
BACKTICK = `[^`]*`
COMMENT = #([^!].*)?$
DEDENT = emitted when indentation decreases
EOF = emitted at the end of the file
INDENT = emitted when indentation increases
LINE = emitted before a recipe line
NAME = [a-zA-Z_][a-zA-Z0-9_-]*
NEWLINE = \n|\r\n
RAW_STRING = '[^']*'
STRING = "[^"]*" # also processes \n \r \t \" \\ escapes
TEXT = recipe text, only matches in a recipe body
BACKTICK = `[^`]*`
INDENTED_BACKTICK = ```[^(```)]*```
COMMENT = #([^!].*)?$
DEDENT = emitted when indentation decreases
EOF = emitted at the end of the file
INDENT = emitted when indentation increases
LINE = emitted before a recipe line
NAME = [a-zA-Z_][a-zA-Z0-9_-]*
NEWLINE = \n|\r\n
RAW_STRING = '[^']*'
INDENTED_RAW_STRING = '''[^(''')]*'''
STRING = "[^"]*" # also processes \n \r \t \" \\ escapes
INDENTED_STRING = """[^("""]*""" # also processes \n \r \t \" \\ escapes
TEXT = recipe text, only matches in a recipe body
```

grammar syntax
Expand Down Expand Up @@ -69,14 +72,16 @@ condition : expression '==' expression
| expression '!=' expression

value : NAME '(' sequence? ')'
| STRING
| RAW_STRING
| BACKTICK
| INDENTED_BACKTICK
| NAME
| string
| '(' expression ')'

string : STRING
| INDENTED_STRING
| RAW_STRING
| INDENTED_RAW_STRING

sequence : expression ',' sequence
| expression ','?
Expand Down
36 changes: 36 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ string-with-newline := "\n"
string-with-carriage-return := "\r"
string-with-double-quote := "\""
string-with-slash := "\\"
string-with-no-newline := "\
"
```

```sh
Expand All @@ -553,6 +555,7 @@ $ just --evaluate
string-with-double-quote := """
string-with-newline := "
"
string-with-no-newline := ""
string-with-slash := "\"
string-with-tab := " "
```
Expand Down Expand Up @@ -580,6 +583,25 @@ $ just --evaluate
escapes := "\t\n\r\"\\"
```

Indented versions of both single- and double-quoted strings, delimited by triple single- or triple double-quotes, are supported. Indented string lines are stripped of leading whitespace common to all non-blank lines:

```make
# this string will evaluate to `foo\nbar\n`
x := '''
foo
bar
'''

# this string will evaluate to `abc\n wuv\nbar\n`
y := """
abc
wuv
xyz
"""
```

Similar to unindented strings, indented double-quoted strings process escape sequences, and indented single-quoted strings ignore escape sequences. Escape sequence processing takes place after unindentation. The unindention algorithm does not take escape-sequence produced whitespace or newlines into account.

=== Ignoring Errors

Normally, if a command returns a nonzero exit status, execution will stop. To
Expand Down Expand Up @@ -716,6 +738,20 @@ serve:
./serve {{localhost}} 8080
```

Indented backticks, delimited by three backticks, are de-indented in the same manner as indented strings:

```make
# This backtick evaluates the command `echo foo\necho bar\n`, which produces the value `foo\nbar\n`.
stuff := ```
echo foo
echo bar
```
```

See the <<Strings>> section for details on unindenting.

Backticks may not start with `#!`. This syntax is reserved for a future upgrade.

=== Conditional Expressions

`if`/`else` expressions evaluate different branches depending on if two expressions evaluate to the same value:
Expand Down
5 changes: 3 additions & 2 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// stdlib
pub(crate) use std::{
borrow::Cow,
cmp,
collections::{BTreeMap, BTreeSet},
env,
Expand Down Expand Up @@ -31,7 +30,9 @@ pub(crate) use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
pub(crate) use crate::{config_error, setting};

// functions
pub(crate) use crate::{default::default, empty::empty, load_dotenv::load_dotenv, output::output};
pub(crate) use crate::{
default::default, empty::empty, load_dotenv::load_dotenv, output::output, unindent::unindent,
};

// traits
pub(crate) use crate::{
Expand Down
7 changes: 5 additions & 2 deletions src/compilation_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ impl Display for CompilationError<'_> {
recipe_line.ordinal(),
)?;
},
BacktickShebang => {
writeln!(f, "Backticks may not start with `#!`")?;
},
CircularRecipeDependency { recipe, ref circle } =>
if circle.len() == 2 {
writeln!(f, "Recipe `{}` depends on itself", recipe)?;
Expand Down Expand Up @@ -242,10 +245,10 @@ impl Display for CompilationError<'_> {
UnterminatedInterpolation => {
writeln!(f, "Unterminated interpolation")?;
},
UnterminatedString(StringKind::Cooked) | UnterminatedString(StringKind::Raw) => {
UnterminatedString => {
writeln!(f, "Unterminated string")?;
},
UnterminatedString(StringKind::Backtick) => {
UnterminatedBacktick => {
writeln!(f, "Unterminated backtick")?;
},
Internal { ref message } => {
Expand Down
4 changes: 3 additions & 1 deletion src/compilation_error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub(crate) enum CompilationErrorKind<'src> {
alias: &'src str,
recipe_line: usize,
},
BacktickShebang,
CircularRecipeDependency {
recipe: &'src str,
circle: Vec<&'src str>,
Expand Down Expand Up @@ -107,5 +108,6 @@ pub(crate) enum CompilationErrorKind<'src> {
open_line: usize,
},
UnterminatedInterpolation,
UnterminatedString(StringKind),
UnterminatedString,
UnterminatedBacktick,
}
2 changes: 1 addition & 1 deletion src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
}),
}
},
Expression::StringLiteral { string_literal } => Ok(string_literal.cooked.to_string()),
Expression::StringLiteral { string_literal } => Ok(string_literal.cooked.clone()),
Expression::Backtick { contents, token } =>
if self.config.dry_run {
Ok(format!("`{}`", contents))
Expand Down
2 changes: 1 addition & 1 deletion src/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::common::*;
pub(crate) enum Expression<'src> {
/// `contents`
Backtick {
contents: &'src str,
contents: String,
token: Token<'src>,
},
/// `name(arguments)`
Expand Down
Loading