diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 68e3ad4cb..cc9407a1b 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -1224,27 +1224,22 @@ pub fn parse_raw_string_literal( match stream.get_next() { Some('"') => pos.advance(), Some(c) => return Err((LERR::UnexpectedInput(c.to_string()), start)), - None => { - return Err((LERR::UnterminatedString, start)); - } + None => return Err((LERR::UnterminatedString, start)) } - let mut seen_hashes: Option = None; // Match everything until the same number of '#'s are seen, prepended by a '"' + + // Counts the number of '#' characters seen after a quotation mark. + // Becomes Some(0) after a quote is seen, but resets to None if a hash doesn't follow. + let mut seen_hashes: Option = None; let mut result = SmartString::new_const(); + loop { let next_char = match stream.get_next() { - Some(ch) => { - pos.advance(); - ch - } - None => { - pos.advance(); - return Err((LERR::UnterminatedString, start)); - } + Some(ch) => ch, + None => return Err((LERR::UnterminatedString, start)) }; - pos.advance(); match (next_char, &mut seen_hashes) { // Begin attempt to close string @@ -1282,9 +1277,7 @@ pub fn parse_raw_string_literal( seen_hashes = None; } // Normal new character seen - (c, None) => { - result.push(c); - } + (c, None) => result.push(c) } if next_char == '\n' { @@ -1324,7 +1317,7 @@ pub fn parse_raw_string_literal( /// |`` `hello``_{LF}{EOF}_ |`StringConstant("hello\n")` |``Some('`')`` | /// |`` `hello ${`` |`InterpolatedString("hello ")`
next token is `{`|`None` | /// |`` } hello` `` |`StringConstant(" hello")` |`None` | -/// |`} hello`_{EOF}_ |`StringConstant(" hello")` |``Some('`')`` | | +/// |`} hello`_{EOF}_ |`StringConstant(" hello")` |``Some('`')`` | /// /// This function does not throw a `LexError` for the following conditions: /// diff --git a/tests/string.rs b/tests/string.rs index 6547c5e0a..69ae0e5d3 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, ImmutableString, Scope, INT}; +use rhai::{Engine, EvalAltResult, ImmutableString, LexError, ParseErrorType, Position, Scope, INT}; #[test] fn test_string() { @@ -18,8 +18,28 @@ fn test_string() { assert_eq!(engine.eval::(r#""Test string: \x58""#).unwrap(), "Test string: X"); assert_eq!(engine.eval::(r#""\"hello\"""#).unwrap(), r#""hello""#); assert_eq!(engine.eval::(r#"r"Test""#).unwrap(), "Test"); - assert_eq!(engine.eval::(r##"r"Test string: \\u2764\nhello,\nworld!""##).unwrap(), r"Test string: \\u2764\nhello,\nworld!"); + assert_eq!(engine.eval::(r#"r"Test string: \\u2764\nhello,\nworld!""#).unwrap(), r#"Test string: \\u2764\nhello,\nworld!"#); assert_eq!(engine.eval::(r###"r##"Test string: r#"\\u2764\nhello,\\nworld!"#"##"###).unwrap(), r##"Test string: r#"\\u2764\nhello,\\nworld!"#"##); + assert_eq!(engine.eval::(r###"r##"Test string: "## + "\u2764""###).unwrap(), "Test string: ❤"); + let bad_result = *engine.eval::(r###"r#"Test string: \"##"###).unwrap_err(); + if let EvalAltResult::ErrorParsing(parse_error, pos) = bad_result { + assert_eq!(parse_error, ParseErrorType::UnknownOperator("#".to_string())); + assert_eq!(pos, Position::new(1, 19)); + } else { + panic!("Wrong error type: {}", bad_result); + } + let bad_result = *engine + .eval::( + r###"r##"Test string: + \"#"###, + ) + .unwrap_err(); + if let EvalAltResult::ErrorParsing(parse_error, pos) = bad_result { + assert_eq!(parse_error, ParseErrorType::BadInput(LexError::UnterminatedString)); + assert_eq!(pos, Position::new(1, 1)); + } else { + panic!("Wrong error type: {}", bad_result); + } assert_eq!(engine.eval::(r#""foo" + "bar""#).unwrap(), "foobar");