2 use super::strcursor::StrCursor as Cur;
3 use syntax_pos::InnerSpan;
5 /// Represents a single `printf`-style substitution.
6 #[derive(Clone, PartialEq, Debug)]
7 pub enum Substitution<'a> {
8 /// A formatted output substitution with its internal byte offset.
10 /// A literal `%%` escape.
14 impl<'a> Substitution<'a> {
15 pub fn as_str(&self) -> &str {
17 Substitution::Format(ref fmt) => fmt.span,
18 Substitution::Escape => "%%",
22 pub fn position(&self) -> Option<InnerSpan> {
24 Substitution::Format(ref fmt) => Some(fmt.position),
29 pub fn set_position(&mut self, start: usize, end: usize) {
31 Substitution::Format(ref mut fmt) => {
32 fmt.position = InnerSpan::new(start, end);
39 /// Translate this substitution into an equivalent Rust formatting directive.
41 /// This ignores cases where the substitution does not have an exact equivalent, or where
42 /// the substitution would be unnecessary.
43 pub fn translate(&self) -> Option<String> {
45 Substitution::Format(ref fmt) => fmt.translate(),
46 Substitution::Escape => None,
51 #[derive(Clone, PartialEq, Debug)]
52 /// A single `printf`-style formatting directive.
53 pub struct Format<'a> {
54 /// The entire original formatting directive.
56 /// The (1-based) parameter to be converted.
57 pub parameter: Option<u16>,
60 /// Minimum width of the output.
61 pub width: Option<Num>,
62 /// Precision of the conversion.
63 pub precision: Option<Num>,
64 /// Length modifier for the conversion.
65 pub length: Option<&'a str>,
66 /// Type of parameter being converted.
68 /// Byte offset for the start and end of this formatting directive.
69 pub position: InnerSpan,
73 /// Translate this directive into an equivalent Rust formatting directive.
75 /// Returns `None` in cases where the `printf` directive does not have an exact Rust
76 /// equivalent, rather than guessing.
77 pub fn translate(&self) -> Option<String> {
80 let (c_alt, c_zero, c_left, c_plus) = {
81 let mut c_alt = false;
82 let mut c_zero = false;
83 let mut c_left = false;
84 let mut c_plus = false;
85 for c in self.flags.chars() {
94 (c_alt, c_zero, c_left, c_plus)
97 // Has a special form in Rust for numbers.
98 let fill = if c_zero { Some("0") } else { None };
100 let align = if c_left { Some("<") } else { None };
102 // Rust doesn't have an equivalent to the `' '` flag.
103 let sign = if c_plus { Some("+") } else { None };
105 // Not *quite* the same, depending on the type...
108 let width = match self.width {
110 // NOTE: Rust doesn't support this.
113 w @ Some(Num::Arg(_)) => w,
114 w @ Some(Num::Num(_)) => w,
118 let precision = self.precision;
120 // NOTE: although length *can* have an effect, we can't duplicate the effect in Rust, so
121 // we just ignore it.
123 let (type_, use_zero_fill, is_int) = match self.type_ {
124 "d" | "i" | "u" => (None, true, true),
125 "f" | "F" => (None, false, false),
126 "s" | "c" => (None, false, false),
127 "e" | "E" => (Some(self.type_), true, false),
128 "x" | "X" | "o" => (Some(self.type_), true, true),
129 "p" => (Some(self.type_), false, true),
130 "g" => (Some("e"), true, false),
131 "G" => (Some("E"), true, false),
135 let (fill, width, precision) = match (is_int, width, precision) {
136 (true, Some(_), Some(_)) => {
137 // Rust can't duplicate this insanity.
140 (true, None, Some(p)) => (Some("0"), Some(p), None),
141 (true, w, None) => (fill, w, None),
142 (false, w, p) => (fill, w, p),
145 let align = match (self.type_, width.is_some(), align.is_some()) {
146 ("s", true, false) => Some(">"),
150 let (fill, zero_fill) = match (fill, use_zero_fill) {
151 (Some("0"), true) => (None, true),
152 (fill, _) => (fill, false),
155 let alt = match type_ {
156 Some("x") | Some("X") => alt,
160 let has_options = fill.is_some()
166 || precision.is_some()
170 // Initialise with a rough guess.
171 let cap = self.span.len() + if has_options { 2 } else { 0 };
172 let mut s = String::with_capacity(cap);
176 if let Some(arg) = self.parameter {
177 write!(s, "{}", arg.checked_sub(1)?).ok()?;
183 let align = if let Some(fill) = fill {
190 if let Some(align) = align {
194 if let Some(sign) = sign {
206 if let Some(width) = width {
207 width.translate(&mut s).ok()?;
210 if let Some(precision) = precision {
212 precision.translate(&mut s).ok()?;
215 if let Some(type_) = type_ {
225 /// A general number used in a `printf` formatting directive.
226 #[derive(Copy, Clone, PartialEq, Debug)]
228 // The range of these values is technically bounded by `NL_ARGMAX`... but, at least for GNU
229 // libc, it apparently has no real fixed limit. A `u16` is used here on the basis that it
230 // is *vanishingly* unlikely that *anyone* is going to try formatting something wider, or
231 // with more precision, than 32 thousand positions which is so wide it couldn't possibly fit
234 /// A specific, fixed value.
236 /// The value is derived from a positional argument.
238 /// The value is derived from the "next" unconverted argument.
243 fn from_str(s: &str, arg: Option<&str>) -> Self {
244 if let Some(arg) = arg {
245 Num::Arg(arg.parse().unwrap_or_else(|_| panic!("invalid format arg `{:?}`", arg)))
249 Num::Num(s.parse().unwrap_or_else(|_| panic!("invalid format num `{:?}`", s)))
253 fn translate(&self, s: &mut String) -> std::fmt::Result {
256 Num::Num(n) => write!(s, "{}", n),
258 let n = n.checked_sub(1).ok_or(std::fmt::Error)?;
261 Num::Next => write!(s, "*"),
266 /// Returns an iterator over all substitutions in a given string.
267 pub fn iter_subs(s: &str, start_pos: usize) -> Substitutions<'_> {
274 /// Iterator over substitutions in a string.
275 pub struct Substitutions<'a> {
280 impl<'a> Iterator for Substitutions<'a> {
281 type Item = Substitution<'a>;
282 fn next(&mut self) -> Option<Self::Item> {
283 let (mut sub, tail) = parse_next_substitution(self.s)?;
286 Substitution::Format(_) => if let Some(inner_span) = sub.position() {
287 sub.set_position(inner_span.start + self.pos, inner_span.end + self.pos);
288 self.pos += inner_span.end;
290 Substitution::Escape => self.pos += 2,
295 fn size_hint(&self) -> (usize, Option<usize>) {
296 // Substitutions are at least 2 characters long.
297 (0, Some(self.s.len() / 2))
312 /// Parse the next substitution from the input string.
313 pub fn parse_next_substitution(s: &str) -> Option<(Substitution<'_>, &str)> {
317 let start = s.find('%')?;
318 match s[start+1..].chars().next()? {
319 '%' => return Some((Substitution::Escape, &s[start+2..])),
320 _ => {/* fall-through */},
323 Cur::new_at(&s[..], start)
326 // This is meant to be a translation of the following regex:
331 // (?: (?P<parameter> \d+) \$ )?
332 // (?P<flags> [-+ 0\#']* )
333 // (?P<width> \d+ | \* (?: (?P<widtha> \d+) \$ )? )?
334 // (?: \. (?P<precision> \d+ | \* (?: (?P<precisiona> \d+) \$ )? ) )?
337 // hh | h | ll | l | L | z | j | t
340 // | I32 | I64 | I | q
345 // Used to establish the full span at the end.
347 // The current position within the string.
348 let mut at = at.at_next_cp()?;
349 // `c` is the next codepoint, `next` is a cursor after it.
350 let (mut c, mut next) = at.next_cp()?;
352 // Update `at`, `c`, and `next`, exiting if we're out of input.
353 macro_rules! move_to {
357 let (c_, next_) = at.next_cp()?;
364 // Constructs a result when parsing fails.
366 // Note: `move` used to capture copies of the cursors as they are *now*.
367 let fallback = move || {
369 Substitution::Format(Format {
370 span: start.slice_between(next).unwrap(),
376 type_: at.slice_between(next).unwrap(),
377 position: InnerSpan::new(start.at, next.at),
383 // Next parsing state.
384 let mut state = Start;
386 // Sadly, Rust isn't *quite* smart enough to know these *must* be initialised by the end.
387 let mut parameter: Option<u16> = None;
388 let mut flags: &str = "";
389 let mut width: Option<Num> = None;
390 let mut precision: Option<Num> = None;
391 let mut length: Option<&str> = None;
392 let mut type_: &str = "";
395 if let Start = state {
398 let end = at_next_cp_while(next, is_digit);
399 match end.next_cp() {
400 // Yes, this *is* the parameter.
401 Some(('$', end2)) => {
403 parameter = Some(at.slice_between(end).unwrap().parse().unwrap());
406 // Wait, no, actually, it's the width.
411 width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
414 // It's invalid, is what it is.
415 None => return fallback(),
426 if let Flags = state {
427 let end = at_next_cp_while(at, is_flag);
429 flags = at.slice_between(end).unwrap();
433 if let Width = state {
440 let end = at_next_cp_while(next, is_digit);
442 width = Some(Num::from_str(at.slice_between(end).unwrap(), None));
453 if let WidthArg = state {
454 let end = at_next_cp_while(at, is_digit);
455 match end.next_cp() {
456 Some(('$', end2)) => {
458 width = Some(Num::from_str("", Some(at.slice_between(end).unwrap())));
463 width = Some(Num::Next);
469 if let Prec = state {
483 if let PrecInner = state {
486 let end = at_next_cp_while(next, is_digit);
487 match end.next_cp() {
488 Some(('$', end2)) => {
490 precision = Some(Num::from_str("*", next.slice_between(end)));
495 precision = Some(Num::Next);
501 let end = at_next_cp_while(next, is_digit);
503 precision = Some(Num::from_str(at.slice_between(end).unwrap(), None));
506 _ => return fallback(),
510 if let Length = state {
511 let c1_next1 = next.next_cp();
512 match (c, c1_next1) {
513 ('h', Some(('h', next1)))
514 | ('l', Some(('l', next1)))
517 length = Some(at.slice_between(next1).unwrap());
521 ('h', _) | ('l', _) | ('L', _)
522 | ('z', _) | ('j', _) | ('t', _)
526 length = Some(at.slice_between(next).unwrap());
531 let end = next.at_next_cp()
532 .and_then(|end| end.at_next_cp())
533 .map(|end| (next.slice_between(end).unwrap(), end));
534 let end = match end {
535 Some(("32", end)) => end,
536 Some(("64", end)) => end,
540 length = Some(at.slice_between(end).unwrap());
552 if let Type = state {
554 type_ = at.slice_between(next).unwrap();
556 // Don't use `move_to!` here, as we *can* be at the end of the input.
564 let position = InnerSpan::new(start.at, end.at);
567 span: start.slice_between(end).unwrap(),
576 Some((Substitution::Format(f), end.slice_after()))
579 fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_>
580 where F: FnMut(char) -> bool {
582 match cur.next_cp() {
583 Some((c, next)) => if pred(c) {
593 fn is_digit(c: char) -> bool {
600 fn is_flag(c: char) -> bool {
602 '0' | '-' | '+' | ' ' | '#' | '\'' => true,
614 parse_next_substitution as pns,
617 macro_rules! assert_eq_pnsat {
618 ($lhs:expr, $rhs:expr) => {
620 pns($lhs).and_then(|(s, _)| s.translate()),
621 $rhs.map(<String as From<&str>>::from)
628 assert_eq!(pns("has no escapes"), None);
629 assert_eq!(pns("has no escapes, either %"), None);
630 assert_eq!(pns("*so* has a %% escape"), Some((S::Escape," escape")));
631 assert_eq!(pns("%% leading escape"), Some((S::Escape, " leading escape")));
632 assert_eq!(pns("trailing escape %%"), Some((S::Escape, "")));
637 macro_rules! assert_pns_eq_sub {
639 $param:expr, $flags:expr,
640 $width:expr, $prec:expr, $len:expr, $type_:expr,
644 pns(concat!($in_, "!")),
654 position: syntax_pos::InnerSpan::new($pos.0, $pos.1),
662 assert_pns_eq_sub!("%!",
663 { None, "", None, None, None, "!", (0, 2), });
664 assert_pns_eq_sub!("%c",
665 { None, "", None, None, None, "c", (0, 2), });
666 assert_pns_eq_sub!("%s",
667 { None, "", None, None, None, "s", (0, 2), });
668 assert_pns_eq_sub!("%06d",
669 { None, "0", Some(N::Num(6)), None, None, "d", (0, 4), });
670 assert_pns_eq_sub!("%4.2f",
671 { None, "", Some(N::Num(4)), Some(N::Num(2)), None, "f", (0, 5), });
672 assert_pns_eq_sub!("%#x",
673 { None, "#", None, None, None, "x", (0, 3), });
674 assert_pns_eq_sub!("%-10s",
675 { None, "-", Some(N::Num(10)), None, None, "s", (0, 5), });
676 assert_pns_eq_sub!("%*s",
677 { None, "", Some(N::Next), None, None, "s", (0, 3), });
678 assert_pns_eq_sub!("%-10.*s",
679 { None, "-", Some(N::Num(10)), Some(N::Next), None, "s", (0, 7), });
680 assert_pns_eq_sub!("%-*.*s",
681 { None, "-", Some(N::Next), Some(N::Next), None, "s", (0, 6), });
682 assert_pns_eq_sub!("%.6i",
683 { None, "", None, Some(N::Num(6)), None, "i", (0, 4), });
684 assert_pns_eq_sub!("%+i",
685 { None, "+", None, None, None, "i", (0, 3), });
686 assert_pns_eq_sub!("%08X",
687 { None, "0", Some(N::Num(8)), None, None, "X", (0, 4), });
688 assert_pns_eq_sub!("%lu",
689 { None, "", None, None, Some("l"), "u", (0, 3), });
690 assert_pns_eq_sub!("%Iu",
691 { None, "", None, None, Some("I"), "u", (0, 3), });
692 assert_pns_eq_sub!("%I32u",
693 { None, "", None, None, Some("I32"), "u", (0, 5), });
694 assert_pns_eq_sub!("%I64u",
695 { None, "", None, None, Some("I64"), "u", (0, 5), });
696 assert_pns_eq_sub!("%'d",
697 { None, "'", None, None, None, "d", (0, 3), });
698 assert_pns_eq_sub!("%10s",
699 { None, "", Some(N::Num(10)), None, None, "s", (0, 4), });
700 assert_pns_eq_sub!("%-10.10s",
701 { None, "-", Some(N::Num(10)), Some(N::Num(10)), None, "s", (0, 8), });
702 assert_pns_eq_sub!("%1$d",
703 { Some(1), "", None, None, None, "d", (0, 4), });
704 assert_pns_eq_sub!("%2$.*3$d",
705 { Some(2), "", None, Some(N::Arg(3)), None, "d", (0, 8), });
706 assert_pns_eq_sub!("%1$*2$.*3$d",
707 { Some(1), "", Some(N::Arg(2)), Some(N::Arg(3)), None, "d", (0, 11), });
708 assert_pns_eq_sub!("%-8ld",
709 { None, "-", Some(N::Num(8)), None, Some("l"), "d", (0, 5), });
714 let s = "The %d'th word %% is: `%.*s` %!\n";
715 let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate()).collect();
717 subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
718 vec![Some("{}"), None, Some("{:.*}"), None]
722 /// Checks that the translations are what we expect.
724 fn test_translation() {
725 assert_eq_pnsat!("%c", Some("{}"));
726 assert_eq_pnsat!("%d", Some("{}"));
727 assert_eq_pnsat!("%u", Some("{}"));
728 assert_eq_pnsat!("%x", Some("{:x}"));
729 assert_eq_pnsat!("%X", Some("{:X}"));
730 assert_eq_pnsat!("%e", Some("{:e}"));
731 assert_eq_pnsat!("%E", Some("{:E}"));
732 assert_eq_pnsat!("%f", Some("{}"));
733 assert_eq_pnsat!("%g", Some("{:e}"));
734 assert_eq_pnsat!("%G", Some("{:E}"));
735 assert_eq_pnsat!("%s", Some("{}"));
736 assert_eq_pnsat!("%p", Some("{:p}"));
738 assert_eq_pnsat!("%06d", Some("{:06}"));
739 assert_eq_pnsat!("%4.2f", Some("{:4.2}"));
740 assert_eq_pnsat!("%#x", Some("{:#x}"));
741 assert_eq_pnsat!("%-10s", Some("{:<10}"));
742 assert_eq_pnsat!("%*s", None);
743 assert_eq_pnsat!("%-10.*s", Some("{:<10.*}"));
744 assert_eq_pnsat!("%-*.*s", None);
745 assert_eq_pnsat!("%.6i", Some("{:06}"));
746 assert_eq_pnsat!("%+i", Some("{:+}"));
747 assert_eq_pnsat!("%08X", Some("{:08X}"));
748 assert_eq_pnsat!("%lu", Some("{}"));
749 assert_eq_pnsat!("%Iu", Some("{}"));
750 assert_eq_pnsat!("%I32u", Some("{}"));
751 assert_eq_pnsat!("%I64u", Some("{}"));
752 assert_eq_pnsat!("%'d", None);
753 assert_eq_pnsat!("%10s", Some("{:>10}"));
754 assert_eq_pnsat!("%-10.10s", Some("{:<10.10}"));
755 assert_eq_pnsat!("%1$d", Some("{0}"));
756 assert_eq_pnsat!("%2$.*3$d", Some("{1:02$}"));
757 assert_eq_pnsat!("%1$*2$.*3$s", Some("{0:>1$.2$}"));
758 assert_eq_pnsat!("%-8ld", Some("{:<8}"));
764 use super::strcursor::StrCursor as Cur;
765 use syntax_pos::InnerSpan;
767 #[derive(Clone, PartialEq, Debug)]
768 pub enum Substitution<'a> {
769 Ordinal(u8, (usize, usize)),
770 Name(&'a str, (usize, usize)),
771 Escape((usize, usize)),
774 impl Substitution<'_> {
775 pub fn as_str(&self) -> String {
777 Substitution::Ordinal(n, _) => format!("${}", n),
778 Substitution::Name(n, _) => format!("${}", n),
779 Substitution::Escape(_) => "$$".into(),
783 pub fn position(&self) -> Option<InnerSpan> {
785 Substitution::Ordinal(_, pos) |
786 Substitution::Name(_, pos) |
787 Substitution::Escape(pos) => Some(InnerSpan::new(pos.0, pos.1)),
791 pub fn set_position(&mut self, start: usize, end: usize) {
793 Substitution::Ordinal(_, ref mut pos) |
794 Substitution::Name(_, ref mut pos) |
795 Substitution::Escape(ref mut pos) => *pos = (start, end),
799 pub fn translate(&self) -> Option<String> {
801 Substitution::Ordinal(n, _) => Some(format!("{{{}}}", n)),
802 Substitution::Name(n, _) => Some(format!("{{{}}}", n)),
803 Substitution::Escape(_) => None,
808 /// Returns an iterator over all substitutions in a given string.
809 pub fn iter_subs(s: &str, start_pos: usize) -> Substitutions<'_> {
816 /// Iterator over substitutions in a string.
817 pub struct Substitutions<'a> {
822 impl<'a> Iterator for Substitutions<'a> {
823 type Item = Substitution<'a>;
824 fn next(&mut self) -> Option<Self::Item> {
825 match parse_next_substitution(self.s) {
826 Some((mut sub, tail)) => {
828 if let Some(InnerSpan { start, end }) = sub.position() {
829 sub.set_position(start + self.pos, end + self.pos);
838 fn size_hint(&self) -> (usize, Option<usize>) {
839 (0, Some(self.s.len()))
843 /// Parse the next substitution from the input string.
844 pub fn parse_next_substitution(s: &str) -> Option<(Substitution<'_>, &str)> {
846 let start = s.find('$')?;
847 match s[start+1..].chars().next()? {
848 '$' => return Some((Substitution::Escape((start, start+2)), &s[start+2..])),
850 let n = (c as u8) - b'0';
851 return Some((Substitution::Ordinal(n, (start, start+2)), &s[start+2..]));
853 _ => {/* fall-through */},
856 Cur::new_at(&s[..], start)
859 let at = at.at_next_cp()?;
860 let (c, inner) = at.next_cp()?;
862 if !is_ident_head(c) {
865 let end = at_next_cp_while(inner, is_ident_tail);
866 let slice = at.slice_between(end).unwrap();
867 let start = at.at - 1;
868 let end_pos = at.at + slice.len();
869 Some((Substitution::Name(slice, (start, end_pos)), end.slice_after()))
873 fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_>
874 where F: FnMut(char) -> bool {
876 match cur.next_cp() {
877 Some((c, next)) => if pred(c) {
887 fn is_ident_head(c: char) -> bool {
889 'a' ..= 'z' | 'A' ..= 'Z' | '_' => true,
894 fn is_ident_tail(c: char) -> bool {
897 c => is_ident_head(c)
905 parse_next_substitution as pns,
908 macro_rules! assert_eq_pnsat {
909 ($lhs:expr, $rhs:expr) => {
911 pns($lhs).and_then(|(f, _)| f.translate()),
912 $rhs.map(<String as From<&str>>::from)
919 assert_eq!(pns("has no escapes"), None);
920 assert_eq!(pns("has no escapes, either $"), None);
921 assert_eq!(pns("*so* has a $$ escape"), Some((S::Escape((11, 13)), " escape")));
922 assert_eq!(pns("$$ leading escape"), Some((S::Escape((0, 2)), " leading escape")));
923 assert_eq!(pns("trailing escape $$"), Some((S::Escape((16, 18)), "")));
928 macro_rules! assert_pns_eq_sub {
929 ($in_:expr, $kind:ident($arg:expr, $pos:expr)) => {
930 assert_eq!(pns(concat!($in_, "!")), Some((S::$kind($arg.into(), $pos), "!")))
934 assert_pns_eq_sub!("$0", Ordinal(0, (0, 2)));
935 assert_pns_eq_sub!("$1", Ordinal(1, (0, 2)));
936 assert_pns_eq_sub!("$9", Ordinal(9, (0, 2)));
937 assert_pns_eq_sub!("$N", Name("N", (0, 2)));
938 assert_pns_eq_sub!("$NAME", Name("NAME", (0, 5)));
943 use super::iter_subs;
944 let s = "The $0'th word $$ is: `$WORD` $!\n";
945 let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate()).collect();
947 subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
948 vec![Some("{0}"), None, Some("{WORD}")]
953 fn test_translation() {
954 assert_eq_pnsat!("$0", Some("{0}"));
955 assert_eq_pnsat!("$9", Some("{9}"));
956 assert_eq_pnsat!("$1", Some("{1}"));
957 assert_eq_pnsat!("$10", Some("{1}"));
958 assert_eq_pnsat!("$stuff", Some("{stuff}"));
959 assert_eq_pnsat!("$NAME", Some("{NAME}"));
960 assert_eq_pnsat!("$PREFIX/bin", Some("{PREFIX}"));
967 pub struct StrCursor<'a> {
972 impl<'a> StrCursor<'a> {
973 pub fn new_at(s: &'a str, at: usize) -> StrCursor<'a> {
980 pub fn at_next_cp(mut self) -> Option<StrCursor<'a>> {
981 match self.try_seek_right_cp() {
987 pub fn next_cp(mut self) -> Option<(char, StrCursor<'a>)> {
988 let cp = self.cp_after()?;
989 self.seek_right(cp.len_utf8());
993 fn slice_before(&self) -> &'a str {
997 pub fn slice_after(&self) -> &'a str {
1001 pub fn slice_between(&self, until: StrCursor<'a>) -> Option<&'a str> {
1002 if !str_eq_literal(self.s, until.s) {
1005 use std::cmp::{max, min};
1006 let beg = min(self.at, until.at);
1007 let end = max(self.at, until.at);
1008 Some(&self.s[beg..end])
1012 fn cp_after(&self) -> Option<char> {
1013 self.slice_after().chars().next()
1016 fn try_seek_right_cp(&mut self) -> bool {
1017 match self.slice_after().chars().next() {
1019 self.at += c.len_utf8();
1026 fn seek_right(&mut self, bytes: usize) {
1031 impl Copy for StrCursor<'_> {}
1033 impl<'a> Clone for StrCursor<'a> {
1034 fn clone(&self) -> StrCursor<'a> {
1039 impl std::fmt::Debug for StrCursor<'_> {
1040 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1041 write!(fmt, "StrCursor({:?} | {:?})", self.slice_before(), self.slice_after())
1045 fn str_eq_literal(a: &str, b: &str) -> bool {
1046 a.as_bytes().as_ptr() == b.as_bytes().as_ptr()
1047 && a.len() == b.len()