Skip to content

Commit

Permalink
Reform and improve string literals (#793)
Browse files Browse the repository at this point in the history
- Combine and simplify string and backtick lexing.
- Allow newlines in strings and backticks.
- Add triple-delimited indented strings and backticks. Common indented literal non-blank line leading whitespace is stripped.
- If a literal newline is escaped, it will be suppressed.
- Backticks starting with `#!` are reserved for a future upgrade.
  • Loading branch information
casey authored Apr 6, 2021
1 parent da97f8d commit fec979c
Show file tree
Hide file tree
Showing 21 changed files with 829 additions and 358 deletions.
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

0 comments on commit fec979c

Please sign in to comment.