From 10c377b88c7fcfd7fb13d56251c1769bf3d51643 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 13 Nov 2016 14:04:20 -0800 Subject: [PATCH] Allow `'` raw strings to contain literal newlines (#107) Fixes #8 --- justfile | 2 +- src/integration.rs | 139 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 39 ++++++------- src/unit.rs | 13 +++++ 4 files changed, 170 insertions(+), 23 deletions(-) diff --git a/justfile b/justfile index f115d09..05c1ee8 100644 --- a/justfile +++ b/justfile @@ -92,7 +92,7 @@ quine: create diff tmp/gen1.c tmp/gen2.c @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: diff --git a/src/integration.rs b/src/integration.rs index e3131f6..5b73578 100644 --- a/src/integration.rs +++ b/src/integration.rs @@ -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=": + | ^ +"#, + ); +} diff --git a/src/lib.rs b/src/lib.rs index fc4865a..8783e35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1467,7 +1467,8 @@ fn tokenize(text: &str) -> Result, CompileError> { static ref NAME: Regex = token(r"([a-zA-Z_-][a-zA-Z0-9_-]*)"); static ref PLUS: Regex = token(r"[+]" ); 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 INTERPOLATION_START: Regex = re(r"^[{][{]" ); static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]" ); @@ -1629,6 +1630,8 @@ fn tokenize(text: &str) -> Result, CompileError> { (captures.at(1).unwrap(), captures.at(2).unwrap(), Comment) } else if let Some(captures) = RAW_STRING.captures(rest) { (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) { let prefix = captures.at(1).unwrap(); let contents = &rest[prefix.len()+1..]; @@ -1661,8 +1664,6 @@ fn tokenize(text: &str) -> Result, CompileError> { return error!(ErrorKind::UnknownStartOfToken) }; - let len = prefix.len() + lexeme.len(); - tokens.push(Token { index: index, line: line, @@ -1673,6 +1674,8 @@ fn tokenize(text: &str) -> Result, CompileError> { kind: kind, }); + let len = prefix.len() + lexeme.len(); + if len == 0 { let last = tokens.last().unwrap(); match last.kind { @@ -1687,10 +1690,19 @@ fn tokenize(text: &str) -> Result, CompileError> { Eol => { line += 1; column = 0; - }, + } Eof => { 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; } @@ -1963,24 +1975,7 @@ impl<'a> Parser<'a> { } fn file(mut self) -> Result, CompileError<'a>> { - // how do i associate comments with recipes? - // save the last doc - // clear it after every item - let mut doc = None; - - /* - trait Swap { - fn swap(&mut self, T) -> T - } - - impl Swap> for Option { - fn swap(&mut self, replacement: Option) -> Option { - std::mem::replace(self, replacement) - } - } - */ - loop { match self.tokens.next() { Some(token) => match token.kind { diff --git a/src/unit.rs b/src/unit.rs index f66cba0..91dbf4b 100644 --- a/src/unit.rs +++ b/src/unit.rs @@ -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] fn string_quote_escape() { parse_summary(