Allow ' raw strings to contain literal newlines (#107)

Fixes #8
This commit is contained in:
Casey Rodarmor 2016-11-13 14:04:20 -08:00 committed by GitHub
parent 9d9aaa91b1
commit 10c377b88c
4 changed files with 170 additions and 23 deletions

View File

@ -92,7 +92,7 @@ quine: create
diff tmp/gen1.c tmp/gen2.c diff tmp/gen1.c tmp/gen2.c
@echo 'It was a quine!' @echo 'It was a quine!'
quine-text = "int printf(const char*, ...); int main() { char *s = \"int printf(const char*, ...); int main() { char *s = %c%s%c; printf(s, 34, s, 34); return 0; }\"; printf(s, 34, s, 34); return 0; }" quine-text = 'int printf(const char*, ...); int main() { char *s = "int printf(const char*, ...); int main() { char *s = %c%s%c; printf(s, 34, s, 34); return 0; }"; printf(s, 34, s, 34); return 0; }'
# create our quine # create our quine
create: create:

View File

@ -1272,3 +1272,142 @@ fn long_circular_recipe_dependency() {
", ",
); );
} }
#[test]
fn multiline_raw_string() {
integration_test(
&["a"],
"
string = 'hello
whatever'
a:
echo '{{string}}'
",
0,
"hello
whatever
",
"echo 'hello
whatever'
",
);
}
#[test]
fn error_line_after_multiline_raw_string() {
integration_test(
&["a"],
"
string = 'hello
whatever' + 'yo'
a:
echo '{{foo}}'
",
255,
"",
"error: variable `foo` not defined
|
7 | echo '{{foo}}'
| ^^^
",
);
}
#[test]
fn error_column_after_multiline_raw_string() {
integration_test(
&["a"],
"
string = 'hello
whatever' + bar
a:
echo '{{string}}'
",
255,
"",
"error: variable `bar` not defined
|
4 | whatever' + bar
| ^^^
",
);
}
#[test]
fn multiline_raw_string_in_interpolation() {
integration_test(
&["a"],
r#"
a:
echo '{{"a" + '
' + "b"}}'
"#,
0,
"a
b
",
"echo 'a
b'
",
);
}
#[test]
fn error_line_after_multiline_raw_string_in_interpolation() {
integration_test(
&["a"],
r#"
a:
echo '{{"a" + '
' + "b"}}'
echo {{b}}
"#,
255,
"",
"error: variable `b` not defined
|
6 | echo {{b}}
| ^
",
);
}
#[test]
fn unterminated_raw_string() {
integration_test(
&["a"],
"
a b=':
",
255,
"",
"error: unterminated string
|
2 | a b=':
| ^
",
);
}
#[test]
fn unterminated_string() {
integration_test(
&["a"],
r#"
a b=":
"#,
255,
"",
r#"error: unterminated string
|
2 | a b=":
| ^
"#,
);
}

View File

@ -1467,7 +1467,8 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
static ref NAME: Regex = token(r"([a-zA-Z_-][a-zA-Z0-9_-]*)"); static ref NAME: Regex = token(r"([a-zA-Z_-][a-zA-Z0-9_-]*)");
static ref PLUS: Regex = token(r"[+]" ); static ref PLUS: Regex = token(r"[+]" );
static ref STRING: Regex = token("\"" ); static ref STRING: Regex = token("\"" );
static ref RAW_STRING: Regex = token(r#"'[^'\r\n]*'"# ); static ref RAW_STRING: Regex = token(r#"'[^']*'"# );
static ref UNTERMINATED_RAW_STRING: Regex = token(r#"'[^']*"# );
static ref INDENT: Regex = re(r"^([ \t]*)[^ \t\n\r]" ); static ref INDENT: Regex = re(r"^([ \t]*)[^ \t\n\r]" );
static ref INTERPOLATION_START: Regex = re(r"^[{][{]" ); static ref INTERPOLATION_START: Regex = re(r"^[{][{]" );
static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]" ); static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]" );
@ -1629,6 +1630,8 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
(captures.at(1).unwrap(), captures.at(2).unwrap(), Comment) (captures.at(1).unwrap(), captures.at(2).unwrap(), Comment)
} else if let Some(captures) = RAW_STRING.captures(rest) { } else if let Some(captures) = RAW_STRING.captures(rest) {
(captures.at(1).unwrap(), captures.at(2).unwrap(), RawString) (captures.at(1).unwrap(), captures.at(2).unwrap(), RawString)
} else if UNTERMINATED_RAW_STRING.is_match(rest) {
return error!(ErrorKind::UnterminatedString);
} else if let Some(captures) = STRING.captures(rest) { } else if let Some(captures) = STRING.captures(rest) {
let prefix = captures.at(1).unwrap(); let prefix = captures.at(1).unwrap();
let contents = &rest[prefix.len()+1..]; let contents = &rest[prefix.len()+1..];
@ -1661,8 +1664,6 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
return error!(ErrorKind::UnknownStartOfToken) return error!(ErrorKind::UnknownStartOfToken)
}; };
let len = prefix.len() + lexeme.len();
tokens.push(Token { tokens.push(Token {
index: index, index: index,
line: line, line: line,
@ -1673,6 +1674,8 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
kind: kind, kind: kind,
}); });
let len = prefix.len() + lexeme.len();
if len == 0 { if len == 0 {
let last = tokens.last().unwrap(); let last = tokens.last().unwrap();
match last.kind { match last.kind {
@ -1687,10 +1690,19 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
Eol => { Eol => {
line += 1; line += 1;
column = 0; column = 0;
}, }
Eof => { Eof => {
break; break;
}, }
RawString => {
let lexeme_lines = lexeme.lines().count();
line += lexeme_lines - 1;
if lexeme_lines == 1 {
column += len;
} else {
column = lexeme.lines().last().unwrap().len();
}
}
_ => { _ => {
column += len; column += len;
} }
@ -1963,24 +1975,7 @@ impl<'a> Parser<'a> {
} }
fn file(mut self) -> Result<Justfile<'a>, CompileError<'a>> { fn file(mut self) -> Result<Justfile<'a>, CompileError<'a>> {
// how do i associate comments with recipes?
// save the last doc
// clear it after every item
let mut doc = None; let mut doc = None;
/*
trait Swap<T> {
fn swap(&mut self, T) -> T
}
impl<I> Swap<Option<I>> for Option<I> {
fn swap(&mut self, replacement: Option<I>) -> Option<I> {
std::mem::replace(self, replacement)
}
}
*/
loop { loop {
match self.tokens.next() { match self.tokens.next() {
Some(token) => match token.kind { Some(token) => match token.kind {

View File

@ -612,6 +612,19 @@ fn unterminated_string_with_escapes() {
}); });
} }
#[test]
fn unterminated_raw_string() {
let text = "r a='asdf";
parse_error(text, CompileError {
text: text,
index: 4,
line: 0,
column: 4,
width: None,
kind: ErrorKind::UnterminatedString,
});
}
#[test] #[test]
fn string_quote_escape() { fn string_quote_escape() {
parse_summary( parse_summary(