Skip to content

Commit

Permalink
feat: Implement string escape sequences (noir-lang#2803)
Browse files Browse the repository at this point in the history
  • Loading branch information
jfecher authored and Sakapoi committed Oct 19, 2023
1 parent a100103 commit 617aec0
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 12 deletions.
26 changes: 19 additions & 7 deletions compiler/noirc_frontend/src/lexer/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ pub enum LexerErrorKind {
LogicalAnd { span: Span },
#[error("Unterminated block comment")]
UnterminatedBlockComment { span: Span },
#[error("Unterminated string literal")]
UnterminatedStringLiteral { span: Span },
#[error(
"'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character."
)]
InvalidEscape { escaped: char, span: Span },
}

impl LexerErrorKind {
Expand All @@ -33,6 +39,8 @@ impl LexerErrorKind {
LexerErrorKind::TooManyBits { span, .. } => *span,
LexerErrorKind::LogicalAnd { span } => *span,
LexerErrorKind::UnterminatedBlockComment { span } => *span,
LexerErrorKind::UnterminatedStringLiteral { span } => *span,
LexerErrorKind::InvalidEscape { span, .. } => *span,
}
}

Expand All @@ -46,30 +54,30 @@ impl LexerErrorKind {
let found: String = found.map(Into::into).unwrap_or_else(|| "<eof>".into());

(
"an unexpected character was found".to_string(),
format!(" expected {expected} , but got {found}"),
"An unexpected character was found".to_string(),
format!("Expected {expected}, but found {found}"),
*span,
)
},
LexerErrorKind::NotADoubleChar { span, found } => (
format!("tried to parse {found} as double char"),
format!("Tried to parse {found} as double char"),
format!(
" {found:?} is not a double char, this is an internal error"
),
*span,
),
LexerErrorKind::InvalidIntegerLiteral { span, found } => (
"invalid integer literal".to_string(),
"Invalid integer literal".to_string(),
format!(" {found} is not an integer"),
*span,
),
LexerErrorKind::MalformedFuncAttribute { span, found } => (
"malformed function attribute".to_string(),
"Malformed function attribute".to_string(),
format!(" {found} is not a valid attribute"),
*span,
),
LexerErrorKind::TooManyBits { span, max, got } => (
"integer literal too large".to_string(),
"Integer literal too large".to_string(),
format!(
"The maximum number of bits needed to represent a field is {max}, This integer type needs {got} bits"
),
Expand All @@ -80,7 +88,11 @@ impl LexerErrorKind {
"Try `&` instead, or use `if` only if you require short-circuiting".to_string(),
*span,
),
LexerErrorKind::UnterminatedBlockComment { span } => ("unterminated block comment".to_string(), "Unterminated block comment".to_string(), *span),
LexerErrorKind::UnterminatedBlockComment { span } => ("Unterminated block comment".to_string(), "Unterminated block comment".to_string(), *span),
LexerErrorKind::UnterminatedStringLiteral { span } =>
("Unterminated string literal".to_string(), "Unterminated string literal".to_string(), *span),
LexerErrorKind::InvalidEscape { escaped, span } =>
(format!("'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character."), "Invalid escape sequence".to_string(), *span),
}
}
}
Expand Down
30 changes: 26 additions & 4 deletions compiler/noirc_frontend/src/lexer/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,12 +332,34 @@ impl<'a> Lexer<'a> {

fn eat_string_literal(&mut self) -> SpannedTokenResult {
let start = self.position;
let mut string = String::new();

while let Some(next) = self.next_char() {
let char = match next {
'"' => break,
'\\' => match self.next_char() {
Some('r') => '\r',
Some('n') => '\n',
Some('t') => '\t',
Some('0') => '\0',
Some('"') => '"',
Some('\\') => '\\',
Some(escaped) => {
let span = Span::inclusive(start, self.position);
return Err(LexerErrorKind::InvalidEscape { escaped, span });
}
None => {
let span = Span::inclusive(start, self.position);
return Err(LexerErrorKind::UnterminatedStringLiteral { span });
}
},
other => other,
};

let str_literal = self.eat_while(None, |ch| ch != '"');

let str_literal_token = Token::Str(str_literal);
string.push(char);
}

self.next_char(); // Advance past the closing quote
let str_literal_token = Token::Str(string);

let end = self.position;
Ok(str_literal_token.into_span(start, end))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn main(message : pub str<11>, y : Field, hex_as_string : str<4>, hex_as_field :
assert(y == 5); // Change to y != 5 to see how the later print statements are not called
std::println(array);

bad_message = "helld world";
bad_message = "hell\0\"world";
std::println(bad_message);
assert(message != bad_message);

Expand Down

0 comments on commit 617aec0

Please sign in to comment.