From bbd919328e072f856a9f81a3ba23aa3c254a176a Mon Sep 17 00:00:00 2001 From: Piotr Czarnecki Date: Thu, 23 Nov 2023 22:10:20 +0100 Subject: [PATCH 1/9] feat: Implement raw string literals --- compiler/noirc_frontend/src/ast/expression.rs | 10 +++ .../src/hir/resolution/resolver.rs | 1 + compiler/noirc_frontend/src/lexer/lexer.rs | 75 +++++++++++++++++++ compiler/noirc_frontend/src/lexer/token.rs | 11 ++- compiler/noirc_frontend/src/parser/parser.rs | 20 +++++ 5 files changed, 116 insertions(+), 1 deletion(-) diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index d29e1670944..41807d7eca7 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -76,6 +76,10 @@ impl ExpressionKind { ExpressionKind::Literal(Literal::Str(contents)) } + pub fn raw_string(contents: String, hashes: u8) -> ExpressionKind { + ExpressionKind::Literal(Literal::RawStr(contents, hashes)) + } + pub fn format_string(contents: String) -> ExpressionKind { ExpressionKind::Literal(Literal::FmtStr(contents)) } @@ -312,6 +316,7 @@ pub enum Literal { Bool(bool), Integer(FieldElement), Str(String), + RawStr(String, u8), FmtStr(String), Unit, } @@ -507,6 +512,11 @@ impl Display for Literal { Literal::Bool(boolean) => write!(f, "{}", if *boolean { "true" } else { "false" }), Literal::Integer(integer) => write!(f, "{}", integer.to_u128()), Literal::Str(string) => write!(f, "\"{string}\""), + Literal::RawStr(string, num_hashes) => { + let hashes: String = + std::iter::once('#').cycle().take(*num_hashes as usize).collect(); + write!(f, "r{hashes}\"{string}\"{hashes}") + } Literal::FmtStr(string) => write!(f, "f\"{string}\""), Literal::Unit => write!(f, "()"), } diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 4b829932b76..52d592404c8 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1203,6 +1203,7 @@ impl<'a> Resolver<'a> { } Literal::Integer(integer) => HirLiteral::Integer(integer), Literal::Str(str) => HirLiteral::Str(str), + Literal::RawStr(str, _) => HirLiteral::Str(str), Literal::FmtStr(str) => self.resolve_fmt_str_literal(str, expr.span), Literal::Unit => HirLiteral::Unit, }), diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index be24c1249c6..0f32a0aea81 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -126,6 +126,7 @@ impl<'a> Lexer<'a> { Some(']') => self.single_char_token(Token::RightBracket), Some('"') => self.eat_string_literal(), Some('f') => self.eat_format_string_or_alpha_numeric(), + Some('r') => self.eat_raw_string_or_alpha_numeric(), Some('#') => self.eat_attribute(), Some(ch) if ch.is_ascii_alphanumeric() || ch == '_' => self.eat_alpha_numeric(ch), Some(ch) => { @@ -400,6 +401,80 @@ impl<'a> Lexer<'a> { } } + fn eat_raw_string(&mut self) -> SpannedTokenResult { + let start = self.position; + + let beginning_hashes = self.eat_while(None, |ch| ch == '#'); + let beginning_hashes_count = beginning_hashes.chars().count(); + if beginning_hashes_count > 255 { + // too many hashes (unlikely in practice) + // also, Rust disallows 256+ hashes as well + return Err(LexerErrorKind::UnexpectedCharacter { + span: Span::single_char(self.position + 255), + found: Some('#'), + expected: "\"".to_owned(), + }); + } + + if !self.peek_char_is('"') { + return Err(LexerErrorKind::UnexpectedCharacter { + span: Span::single_char(self.position), + found: self.next_char(), + expected: "\"".to_owned(), + }); + } + self.next_char(); + + let mut str_literal = String::new(); + loop { + let chars = self.eat_while(None, |ch| ch != '"'); + str_literal.push_str(&chars[..]); + if !self.peek_char_is('"') { + return Err(LexerErrorKind::UnexpectedCharacter { + span: Span::single_char(self.position), + found: self.next_char(), + expected: "\"".to_owned(), + }); + } + self.next_char(); + let mut ending_hashes_count = 0; + while let Some('#') = self.peek_char() { + if ending_hashes_count == beginning_hashes_count { + break; + } + self.next_char(); + ending_hashes_count += 1; + } + if ending_hashes_count == beginning_hashes_count { + break; + } else { + str_literal.push('"'); + for _ in 0..ending_hashes_count { + str_literal.push('#'); + } + } + } + + let str_literal_token = Token::RawStr(str_literal, beginning_hashes_count as u8); + + let end = self.position; + Ok(str_literal_token.into_span(start, end)) + } + + fn eat_raw_string_or_alpha_numeric(&mut self) -> SpannedTokenResult { + // Problem: we commit to eating raw strings once we see one or two characters. + // This is unclean, but likely ok in all practical cases, and works with existing + // `Lexer` methods. + let peek2 = self.peek2_char(); + if (self.peek_char_is('#') && (peek2 == Some('#') || peek2 == Some('"'))) + || self.peek_char_is('"') + { + self.eat_raw_string() + } else { + self.eat_alpha_numeric('r') + } + } + fn parse_comment(&mut self, start: u32) -> SpannedTokenResult { let doc_style = match self.peek_char() { Some('!') => { diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 72be71865cc..b16de42c0ba 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -15,6 +15,7 @@ pub enum Token { Int(FieldElement), Bool(bool), Str(String), + RawStr(String, u8), FmtStr(String), Keyword(Keyword), IntType(IntType), @@ -157,6 +158,10 @@ impl fmt::Display for Token { Token::Bool(b) => write!(f, "{b}"), Token::Str(ref b) => write!(f, "{b}"), Token::FmtStr(ref b) => write!(f, "f{b}"), + Token::RawStr(ref b, hashes) => { + let h: String = std::iter::once('#').cycle().take(hashes as usize).collect(); + write!(f, "r{h}\"{b}\"{h}") + } Token::Keyword(k) => write!(f, "{k}"), Token::Attribute(ref a) => write!(f, "{a}"), Token::LineComment(ref s, _style) => write!(f, "//{s}"), @@ -227,7 +232,11 @@ impl Token { pub fn kind(&self) -> TokenKind { match *self { Token::Ident(_) => TokenKind::Ident, - Token::Int(_) | Token::Bool(_) | Token::Str(_) | Token::FmtStr(_) => TokenKind::Literal, + Token::Int(_) + | Token::Bool(_) + | Token::Str(_) + | Token::RawStr(..) + | Token::FmtStr(_) => TokenKind::Literal, Token::Keyword(_) => TokenKind::Keyword, Token::Attribute(_) => TokenKind::Attribute, ref tok => TokenKind::Token(tok.clone()), diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 6b8589cc6e5..3411f259ce0 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -1657,6 +1657,7 @@ fn literal() -> impl NoirParser { Token::Int(x) => ExpressionKind::integer(x), Token::Bool(b) => ExpressionKind::boolean(b), Token::Str(s) => ExpressionKind::string(s), + Token::RawStr(s, hashes) => ExpressionKind::raw_string(s, hashes), Token::FmtStr(s) => ExpressionKind::format_string(s), unexpected => unreachable!("Non-literal {} parsed as a literal", unexpected), }) @@ -2549,4 +2550,23 @@ mod test { check_cases_with_errors(&cases[..], block(fresh_statement())); } + + #[test] + fn parse_raw_string() { + let cases = vec![ + Case { source: r##" r"foo" "##, expect: r##"r"foo""##, errors: 0 }, + Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, + // backslash + Case { source: r##" r"\\" "##, expect: r##"r"\\""##, errors: 0 }, + Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, + Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, + Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, + // mismatch - errors: + Case { source: r###" r#"foo"## "###, expect: r###"r#"foo"#"###, errors: 1 }, + Case { source: r###" r##"foo"# "###, expect: "(none)", errors: 2 }, + ]; + + check_cases_with_errors(&cases[..], expression()); + check_cases_with_errors(&cases[..], literal()); + } } From fb553d728c161adb23a5a28eed301d57f452e477 Mon Sep 17 00:00:00 2001 From: Piotr Czarnecki Date: Fri, 24 Nov 2023 11:57:40 +0100 Subject: [PATCH 2/9] Add documentation for raw strings --- .../docs/language_concepts/data_types/03_strings.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/docs/language_concepts/data_types/03_strings.md b/docs/docs/language_concepts/data_types/03_strings.md index c42f34ec3ad..947bb090dca 100644 --- a/docs/docs/language_concepts/data_types/03_strings.md +++ b/docs/docs/language_concepts/data_types/03_strings.md @@ -61,3 +61,16 @@ Example: let s = "Hello \"world" // prints "Hello "world" let s = "hey \tyou"; // prints "hey you" ``` + +## Raw strings + +A raw string begins with the letter `r` and is optionally delimited by a number of hashes `#`. + +Escape characters are *not* processed within raw strings. All contents are interpreted literally. + +Example: + +```rust +let s = r"Hello world"; +let s = r#"Simon says "hello world""#; +``` From 1d5eaef4a902f0a732b4bd9562726542013bce39 Mon Sep 17 00:00:00 2001 From: Piotr Czarnecki Date: Fri, 24 Nov 2023 11:57:50 +0100 Subject: [PATCH 3/9] Add tests for raw strings --- .../tests/compile_failure/raw_string_huge/Nargo.toml | 5 +++++ .../tests/compile_failure/raw_string_huge/src/main.nr | 3 +++ .../tests/compile_success_empty/raw_string/Nargo.toml | 6 ++++++ .../tests/compile_success_empty/raw_string/src/main.nr | 10 ++++++++++ 4 files changed, 24 insertions(+) create mode 100644 tooling/nargo_cli/tests/compile_failure/raw_string_huge/Nargo.toml create mode 100644 tooling/nargo_cli/tests/compile_failure/raw_string_huge/src/main.nr create mode 100644 tooling/nargo_cli/tests/compile_success_empty/raw_string/Nargo.toml create mode 100644 tooling/nargo_cli/tests/compile_success_empty/raw_string/src/main.nr diff --git a/tooling/nargo_cli/tests/compile_failure/raw_string_huge/Nargo.toml b/tooling/nargo_cli/tests/compile_failure/raw_string_huge/Nargo.toml new file mode 100644 index 00000000000..ecef0e2a07c --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/raw_string_huge/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "raw_string_huge" +type = "bin" +authors = [""] +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/compile_failure/raw_string_huge/src/main.nr b/tooling/nargo_cli/tests/compile_failure/raw_string_huge/src/main.nr new file mode 100644 index 00000000000..eb00c723afe --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/raw_string_huge/src/main.nr @@ -0,0 +1,3 @@ +fn main() { + let _a = r##############################################################################################################################################################################################################################################################################"hello"##############################################################################################################################################################################################################################################################################; +} diff --git a/tooling/nargo_cli/tests/compile_success_empty/raw_string/Nargo.toml b/tooling/nargo_cli/tests/compile_success_empty/raw_string/Nargo.toml new file mode 100644 index 00000000000..7a15bd803c0 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_success_empty/raw_string/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "unit" +type = "bin" +authors = [""] + +[dependencies] diff --git a/tooling/nargo_cli/tests/compile_success_empty/raw_string/src/main.nr b/tooling/nargo_cli/tests/compile_success_empty/raw_string/src/main.nr new file mode 100644 index 00000000000..97c3f95454b --- /dev/null +++ b/tooling/nargo_cli/tests/compile_success_empty/raw_string/src/main.nr @@ -0,0 +1,10 @@ +global D = r#####"Hello "world""#####; + +fn main() { + let a = "Hello \"world\""; + let b = r#"Hello "world""#; + let c = r##"Hello "world""##; + assert(a == b); + assert(b == c); + assert(c == D); +} From b212b265744b2c095b1eed80b0c92e824f01fb78 Mon Sep 17 00:00:00 2001 From: Piotr Czarnecki Date: Fri, 24 Nov 2023 13:13:05 +0100 Subject: [PATCH 4/9] Changes for raw strings --- compiler/noirc_frontend/src/lexer/lexer.rs | 16 ++++--- compiler/noirc_frontend/src/parser/parser.rs | 46 +++++++++++++++++++- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 0f32a0aea81..fc1148c1750 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -465,13 +465,15 @@ impl<'a> Lexer<'a> { // Problem: we commit to eating raw strings once we see one or two characters. // This is unclean, but likely ok in all practical cases, and works with existing // `Lexer` methods. - let peek2 = self.peek2_char(); - if (self.peek_char_is('#') && (peek2 == Some('#') || peek2 == Some('"'))) - || self.peek_char_is('"') - { - self.eat_raw_string() - } else { - self.eat_alpha_numeric('r') + let peek1 = self.peek_char().unwrap_or('X'); + let peek2 = self.peek2_char().unwrap_or('X'); + match (peek1, peek2) { + ('#', '#') | ('#', '"') | ('"', _) => { + self.eat_raw_string() + } + _ => { + self.eat_alpha_numeric('r') + } } } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 3411f259ce0..e223a2d7e2d 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -2552,7 +2552,7 @@ mod test { } #[test] - fn parse_raw_string() { + fn parse_raw_string_expr() { let cases = vec![ Case { source: r##" r"foo" "##, expect: r##"r"foo""##, errors: 0 }, Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, @@ -2561,12 +2561,54 @@ mod test { Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, + // escape sequence + Case { source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, errors: 0 }, + Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, // mismatch - errors: Case { source: r###" r#"foo"## "###, expect: r###"r#"foo"#"###, errors: 1 }, Case { source: r###" r##"foo"# "###, expect: "(none)", errors: 2 }, + // mismatch: short: + Case { source: r###" r"foo"# "###, expect: r###"r"foo""###, errors: 1 }, + Case { source: r###" r#"foo" "###, expect: "(none)", errors: 2 }, + // empty string + Case { source: r####"r"""####, expect: r####"r"""####, errors: 0 }, + Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, + // miscelanneous + Case { source: r###" r#\"foo\"# "###, expect: "plain::r", errors: 2 }, + Case { source: r###" r\"foo\" "###, expect: "plain::r", errors: 1 }, + Case { source: r###" r##"foo"# "###, expect: "(none)", errors: 2 }, + // missing 'r' letter + Case { source: r###" ##"foo"# "###, expect: r#""foo""#, errors: 2 }, + Case { source: r###" #"foo" "###, expect: "plain::foo", errors: 2 }, + // whitespace + Case { source: r###" r #"foo"# "###, expect: "plain::r", errors: 2 }, + Case { source: r###" r# "foo"# "###, expect: "plain::r", errors: 3 }, + Case { source: r###" r#"foo" # "###, expect: "(none)", errors: 2 }, + // after identifier + Case { source: r###" bar#"foo"# "###, expect: "plain::bar", errors: 2 }, ]; check_cases_with_errors(&cases[..], expression()); - check_cases_with_errors(&cases[..], literal()); + } + + #[test] + fn parse_raw_string_lit() { + let lit_cases = vec![ + Case { source: r##" r"foo" "##, expect: r##"r"foo""##, errors: 0 }, + Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, + // backslash + Case { source: r##" r"\\" "##, expect: r##"r"\\""##, errors: 0 }, + Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, + Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, + Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, + // escape sequence + Case { source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, errors: 0 }, + Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, + // mismatch - errors: + Case { source: r###" r#"foo"## "###, expect: r###"r#"foo"#"###, errors: 1 }, + Case { source: r###" r##"foo"# "###, expect: "(none)", errors: 2 }, + ]; + + check_cases_with_errors(&lit_cases[..], literal()); } } From 71a011b25196a1d4d0f2d6807f5f87b79a90654e Mon Sep 17 00:00:00 2001 From: Piotr Czarnecki Date: Fri, 24 Nov 2023 13:16:24 +0100 Subject: [PATCH 5/9] Fixes for raw strings tests --- compiler/noirc_frontend/src/lexer/lexer.rs | 8 ++------ compiler/noirc_frontend/src/parser/parser.rs | 14 +++++++++++--- .../compile_failure/raw_string_huge/src/main.nr | 1 + .../compile_success_empty/raw_string/Nargo.toml | 2 +- tooling/nargo_fmt/src/rewrite/expr.rs | 8 +++++--- 5 files changed, 20 insertions(+), 13 deletions(-) diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index fc1148c1750..3006ccdc5db 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -468,12 +468,8 @@ impl<'a> Lexer<'a> { let peek1 = self.peek_char().unwrap_or('X'); let peek2 = self.peek2_char().unwrap_or('X'); match (peek1, peek2) { - ('#', '#') | ('#', '"') | ('"', _) => { - self.eat_raw_string() - } - _ => { - self.eat_alpha_numeric('r') - } + ('#', '#') | ('#', '"') | ('"', _) => self.eat_raw_string(), + _ => self.eat_alpha_numeric('r'), } } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index e223a2d7e2d..ec480734b77 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -2562,7 +2562,11 @@ mod test { Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, // escape sequence - Case { source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, errors: 0 }, + Case { + source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, + expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, + errors: 0, + }, Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, // mismatch - errors: Case { source: r###" r#"foo"## "###, expect: r###"r#"foo"#"###, errors: 1 }, @@ -2573,7 +2577,7 @@ mod test { // empty string Case { source: r####"r"""####, expect: r####"r"""####, errors: 0 }, Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, - // miscelanneous + // miscellaneous Case { source: r###" r#\"foo\"# "###, expect: "plain::r", errors: 2 }, Case { source: r###" r\"foo\" "###, expect: "plain::r", errors: 1 }, Case { source: r###" r##"foo"# "###, expect: "(none)", errors: 2 }, @@ -2602,7 +2606,11 @@ mod test { Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, // escape sequence - Case { source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, errors: 0 }, + Case { + source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, + expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, + errors: 0, + }, Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, // mismatch - errors: Case { source: r###" r#"foo"## "###, expect: r###"r#"foo"#"###, errors: 1 }, diff --git a/tooling/nargo_cli/tests/compile_failure/raw_string_huge/src/main.nr b/tooling/nargo_cli/tests/compile_failure/raw_string_huge/src/main.nr index eb00c723afe..7bca9942e7a 100644 --- a/tooling/nargo_cli/tests/compile_failure/raw_string_huge/src/main.nr +++ b/tooling/nargo_cli/tests/compile_failure/raw_string_huge/src/main.nr @@ -1,3 +1,4 @@ fn main() { + // Fails because of too many hashes for raw string (256+ hashes) let _a = r##############################################################################################################################################################################################################################################################################"hello"##############################################################################################################################################################################################################################################################################; } diff --git a/tooling/nargo_cli/tests/compile_success_empty/raw_string/Nargo.toml b/tooling/nargo_cli/tests/compile_success_empty/raw_string/Nargo.toml index 7a15bd803c0..81147e65f34 100644 --- a/tooling/nargo_cli/tests/compile_success_empty/raw_string/Nargo.toml +++ b/tooling/nargo_cli/tests/compile_success_empty/raw_string/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "unit" +name = "raw_string" type = "bin" authors = [""] diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index 4d7279815df..48273073553 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -101,9 +101,11 @@ pub(crate) fn rewrite( format_parens(None, visitor.fork(), shape, exprs.len() == 1, exprs, span, false) } ExpressionKind::Literal(literal) => match literal { - Literal::Integer(_) | Literal::Bool(_) | Literal::Str(_) | Literal::FmtStr(_) => { - visitor.slice(span).to_string() - } + Literal::Integer(_) + | Literal::Bool(_) + | Literal::Str(_) + | Literal::RawStr(..) + | Literal::FmtStr(_) => visitor.slice(span).to_string(), Literal::Array(ArrayLiteral::Repeated { repeated_element, length }) => { let repeated = rewrite_sub_expr(visitor, shape, *repeated_element); let length = rewrite_sub_expr(visitor, shape, *length); From 4bdffc354a1dad113cbe52dd4ab737b922d0f4db Mon Sep 17 00:00:00 2001 From: Piotr Czarnecki Date: Fri, 24 Nov 2023 15:47:33 +0100 Subject: [PATCH 6/9] Add test for raw strings --- .../tests/compile_success_empty/raw_string/src/main.nr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tooling/nargo_cli/tests/compile_success_empty/raw_string/src/main.nr b/tooling/nargo_cli/tests/compile_success_empty/raw_string/src/main.nr index 97c3f95454b..ad8dfe82ae5 100644 --- a/tooling/nargo_cli/tests/compile_success_empty/raw_string/src/main.nr +++ b/tooling/nargo_cli/tests/compile_success_empty/raw_string/src/main.nr @@ -7,4 +7,7 @@ fn main() { assert(a == b); assert(b == c); assert(c == D); + let x = r#"Hello World"#; + let y = r"Hello World"; + assert(x == y); } From 236d71cd4e63d1cf044bd8f5a131e25dabfe325e Mon Sep 17 00:00:00 2001 From: Piotr Czarnecki Date: Fri, 24 Nov 2023 16:19:00 +0100 Subject: [PATCH 7/9] Add test for nested raw strings --- compiler/noirc_frontend/src/parser/parser.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index ec480734b77..7f0bf6376c6 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -2590,6 +2590,12 @@ mod test { Case { source: r###" r#"foo" # "###, expect: "(none)", errors: 2 }, // after identifier Case { source: r###" bar#"foo"# "###, expect: "plain::bar", errors: 2 }, + // nested + Case { + source: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, + expect: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, + errors: 0, + }, ]; check_cases_with_errors(&cases[..], expression()); From 825d5a98c716ef86786ead9a1f14d234ddfa1a4d Mon Sep 17 00:00:00 2001 From: Piotr Czarnecki Date: Fri, 24 Nov 2023 16:28:17 +0100 Subject: [PATCH 8/9] Fix logic bug for raw strings --- compiler/noirc_frontend/src/lexer/lexer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 3006ccdc5db..7a2197ebb93 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -410,7 +410,7 @@ impl<'a> Lexer<'a> { // too many hashes (unlikely in practice) // also, Rust disallows 256+ hashes as well return Err(LexerErrorKind::UnexpectedCharacter { - span: Span::single_char(self.position + 255), + span: Span::single_char(start + 255), found: Some('#'), expected: "\"".to_owned(), }); From 498171ea4f498df99ee3818ca91ef7c23ad49428 Mon Sep 17 00:00:00 2001 From: jfecher Date: Tue, 28 Nov 2023 15:54:18 -0600 Subject: [PATCH 9/9] Update docs/docs/language_concepts/data_types/03_strings.md --- docs/docs/language_concepts/data_types/03_strings.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/language_concepts/data_types/03_strings.md b/docs/docs/language_concepts/data_types/03_strings.md index 947bb090dca..e647a58472f 100644 --- a/docs/docs/language_concepts/data_types/03_strings.md +++ b/docs/docs/language_concepts/data_types/03_strings.md @@ -73,4 +73,7 @@ Example: ```rust let s = r"Hello world"; let s = r#"Simon says "hello world""#; + +// Any number of hashes may be used (>= 1) as long as the string also terminates with the same number of hashes +let s = r#####"One "#, Two "##, Three "###, Four "####, Five will end the string."#####; ```