let s = raw.as_str(); let s = iflet Some(stripped) = s.strip_prefix(APOSTROPHE as char) {
stripped
} else {
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("'")])
.with_unexpected(Span::new_unchecked(0, 0)),
);
s
}; let s = iflet Some(stripped) = s.strip_suffix(APOSTROPHE as char) {
stripped
} else {
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("'")])
.with_unexpected(Span::new_unchecked(raw.len(), raw.len())),
);
s
};
for (i, b) in s.as_bytes().iter().enumerate() { if !LITERAL_CHAR.contains_token(b) { let offset = (&s.as_bytes()[i..]).offset_from(&raw.as_bytes());
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Description("non-single-quote visible characters")])
.with_unexpected(Span::new_unchecked(offset, offset)),
);
}
}
if !output.push_str(s) {
error.report_error(
ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
);
}
}
let s = raw.as_str(); let s = iflet Some(stripped) = s.strip_prefix(QUOTATION_MARK as char) {
stripped
} else {
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\"")])
.with_unexpected(Span::new_unchecked(0, 0)),
);
s
}; letmut s = iflet Some(stripped) = s.strip_suffix(QUOTATION_MARK as char) {
stripped
} else {
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\"")])
.with_unexpected(Span::new_unchecked(raw.len(), raw.len())),
);
s
};
let segment = basic_unescaped(&mut s); if !output.push_str(segment) {
error.report_error(
ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
);
} while !s.is_empty() { if s.starts_with("\\") { let _ = s.next_token();
let c = escape_seq_char(&mut s, raw, error); if !output.push_char(c) {
error.report_error(
ParseError::new(ALLOCATION_ERROR)
.with_unexpected(Span::new_unchecked(0, raw.len())),
);
}
} else { let invalid = basic_invalid(&mut s); let start = invalid.offset_from(&raw.as_str()); let end = start + invalid.len();
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[
Expected::Description("non-double-quote visible characters"),
Expected::Literal("\\"),
])
.with_unexpected(Span::new_unchecked(start, end)),
); let _ = output.push_str(invalid);
}
let segment = basic_unescaped(&mut s); if !output.push_str(segment) { let start = segment.offset_from(&raw.as_str()); let end = start + segment.len();
error.report_error(
ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(start, end)),
);
}
}
}
let s = raw.as_str(); let s = iflet Some(stripped) = s.strip_prefix(ML_BASIC_STRING_DELIM) {
stripped
} else {
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\"")])
.with_unexpected(Span::new_unchecked(0, 0)),
);
s
}; let s = strip_start_newline(s); letmut s = iflet Some(stripped) = s.strip_suffix(ML_BASIC_STRING_DELIM) {
stripped
} else {
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\"")])
.with_unexpected(Span::new_unchecked(raw.len(), raw.len())),
);
s
};
let segment = mlb_unescaped(&mut s); if !output.push_str(segment) {
error.report_error(
ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(0, raw.len())),
);
} while !s.is_empty() { if s.starts_with("\\") { let _ = s.next_token();
if s.as_bytes()
.first()
.map(|b| (WSCHAR, b'\r', b'\n').contains_token(b))
.unwrap_or(false)
{
mlb_escaped_nl(&mut s, raw, error);
} else { let c = escape_seq_char(&mut s, raw, error); if !output.push_char(c) {
error.report_error(
ParseError::new(ALLOCATION_ERROR)
.with_unexpected(Span::new_unchecked(0, raw.len())),
);
}
}
} elseif s.starts_with("\r") { let offset = if s.starts_with("\r\n") { "\r\n".len()
} else { let start = s.offset_from(&raw.as_str()) + 1;
error.report_error(
ParseError::new("carriage return must be followed by newline")
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\n")])
.with_unexpected(Span::new_unchecked(start, start)),
); "\r".len()
}; #[cfg(feature = "unsafe")] // SAFETY: Newlines ensure `offset` is along UTF-8 boundary let newline = unsafe { s.next_slice_unchecked(offset) }; #[cfg(not(feature = "unsafe"))] let newline = s.next_slice(offset); if !output.push_str(newline) { let start = newline.offset_from(&raw.as_str()); let end = start + newline.len();
error.report_error(
ParseError::new(ALLOCATION_ERROR)
.with_unexpected(Span::new_unchecked(start, end)),
);
}
} else { let invalid = mlb_invalid(&mut s); let start = invalid.offset_from(&raw.as_str()); let end = start + invalid.len();
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\\"), Expected::Description("characters")])
.with_unexpected(Span::new_unchecked(start, end)),
); let _ = output.push_str(invalid);
}
let segment = mlb_unescaped(&mut s); if !output.push_str(segment) { let start = segment.offset_from(&raw.as_str()); let end = start + segment.len();
error.report_error(
ParseError::new(ALLOCATION_ERROR).with_unexpected(Span::new_unchecked(start, end)),
);
}
}
}
let start = stream.checkpoint(); match stream.next_token() {
Some('\n') => {}
Some('\r') => { if stream.as_bytes().first() == Some(&b'\n') { let _ = stream.next_token();
} else { let start = stream.offset_from(&raw.as_str()); let end = start;
error.report_error(
ParseError::new("carriage return must be followed by newline")
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\n")])
.with_unexpected(Span::new_unchecked(start, end)),
);
}
}
_ => {
stream.reset(&start);
let start = stream.offset_from(&raw.as_str()); let end = start;
error.report_error(
ParseError::new(INVALID_STRING)
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\n")])
.with_unexpected(Span::new_unchecked(start, end)),
);
}
}
loop { let start_offset = stream.offset_from(&raw.as_str());
let offset = stream
.as_bytes()
.offset_for(|b| !(WSCHAR, b'\n').contains_token(b))
.unwrap_or(stream.len()); #[cfg(feature = "unsafe")] // SAFETY: WSCHAR ensure `offset` is along UTF-8 boundary unsafe {
stream.next_slice_unchecked(offset);
} #[cfg(not(feature = "unsafe"))]
stream.next_slice(offset);
if stream.starts_with("\r") { let offset = if stream.starts_with("\r\n") { "\r\n".len()
} else { let start = stream.offset_from(&raw.as_str()) + 1;
error.report_error(
ParseError::new("carriage return must be followed by newline")
.with_context(Span::new_unchecked(0, raw.len()))
.with_expected(&[Expected::Literal("\n")])
.with_unexpected(Span::new_unchecked(start, start)),
); "\r".len()
}; #[cfg(feature = "unsafe")] // SAFETY: Newlines ensure `offset` is along UTF-8 boundary let _ = unsafe { stream.next_slice_unchecked(offset) }; #[cfg(not(feature = "unsafe"))] let _ = stream.next_slice(offset);
}
let end_offset = stream.offset_from(&raw.as_str()); if start_offset == end_offset { break;
}
}
}
"#]]
.raw(),
),
(
r#"'''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved. '''"#,
str![[r#"
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
"#]]
.raw(),
),
(
r#""I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF. \U0002070E""#,
str![[r#"
I'm a string. "You can quote me". Name José
Location SF. "#]]
.raw(),
str![[r#"
[]
#[test] fn ml_basic_string() { let cases = [
(
r#""""
Roses are red
Violets are blue""""#,
str![[r#"
Roses are red
Violets are blue "#]]
.raw(),
str![[r#"
[]
fox jumps over \
the lazy dog.""""#,
str!["The quick brown fox jumps over the lazy dog."].raw(),
str![[r#"
[]
"#]]
.raw(),
),
(
r#""""\
The quick brown \
fox jumps over \
the lazy dog.\ """"#,
str!["The quick brown fox jumps over the lazy dog."].raw(),
str![[r#"
[]
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.