1use std::str;
2
3use aya_log_common::DisplayHint;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct Parameter {
8 pub hint: DisplayHint,
10}
11
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum Fragment {
14 Literal(String),
16
17 Parameter(Parameter),
19}
20
21fn push_literal(frag: &mut Vec<Fragment>, unescaped_literal: &str) -> Result<(), String> {
22 let mut last_open = false;
26 let mut last_close = false;
27 for c in unescaped_literal.chars() {
28 match c {
29 '{' => last_open = !last_open,
30 '}' => last_close = !last_close,
31 _ => {
32 if last_open {
33 return Err("unmatched `{` in format string".into());
34 }
35 if last_close {
36 return Err("unmatched `}` in format string".into());
37 }
38 }
39 }
40 }
41
42 if last_open {
44 return Err("unmatched `{` in format string".into());
45 }
46 if last_close {
47 return Err("unmatched `}` in format string".into());
48 }
49
50 let literal = unescaped_literal.replace("{{", "{").replace("}}", "}");
51 frag.push(Fragment::Literal(literal));
52 Ok(())
53}
54
55fn parse_display_hint(s: &str) -> Result<DisplayHint, String> {
57 Ok(match s {
58 "p" | "x" => DisplayHint::LowerHex,
59 "X" => DisplayHint::UpperHex,
60 "i" => DisplayHint::Ip,
61 "mac" => DisplayHint::LowerMac,
62 "MAC" => DisplayHint::UpperMac,
63 _ => return Err(format!("unknown display hint: {s:?}")),
64 })
65}
66
67fn parse_param(mut input: &str) -> Result<Parameter, String> {
71 const HINT_PREFIX: &str = ":";
72
73 let mut hint = DisplayHint::Default;
75
76 if input.starts_with(HINT_PREFIX) {
77 input = &input[HINT_PREFIX.len()..];
79 if input.is_empty() {
80 return Err("malformed format string (missing display hint after ':')".into());
81 }
82
83 hint = parse_display_hint(input)?;
84 } else if !input.is_empty() {
85 return Err(format!("unexpected content {input:?} in format string"));
86 }
87
88 Ok(Parameter { hint })
89}
90
91pub fn parse(format_string: &str) -> Result<Vec<Fragment>, String> {
94 let mut fragments = Vec::new();
95
96 let mut end_pos = 0;
98
99 let mut chars = format_string.char_indices();
100 while let Some((brace_pos, ch)) = chars.next() {
101 if ch != '{' {
102 continue;
104 }
105
106 if chars.as_str().starts_with('{') {
108 chars.next();
110 continue;
111 }
112
113 if brace_pos > end_pos {
114 let unescaped_literal = &format_string[end_pos..brace_pos];
117 push_literal(&mut fragments, unescaped_literal)?;
118 }
119
120 let len = chars
122 .as_str()
123 .find('}')
124 .ok_or("missing `}` in format string")?;
125 end_pos = brace_pos + 1 + len + 1;
126
127 let param_str = &format_string[brace_pos + 1..][..len];
129 let param = parse_param(param_str)?;
130 fragments.push(Fragment::Parameter(param));
131 }
132
133 if end_pos != format_string.len() {
135 push_literal(&mut fragments, &format_string[end_pos..])?;
136 }
137
138 Ok(fragments)
139}
140
141#[cfg(test)]
142mod test {
143 use super::*;
144
145 #[test]
146 fn test_parse() {
147 assert_eq!(
148 parse("foo {} bar {:x} test {:X} ayy {:i} lmao {{}} {{something}} {:p}"),
149 Ok(vec![
150 Fragment::Literal("foo ".into()),
151 Fragment::Parameter(Parameter {
152 hint: DisplayHint::Default
153 }),
154 Fragment::Literal(" bar ".into()),
155 Fragment::Parameter(Parameter {
156 hint: DisplayHint::LowerHex
157 }),
158 Fragment::Literal(" test ".into()),
159 Fragment::Parameter(Parameter {
160 hint: DisplayHint::UpperHex
161 }),
162 Fragment::Literal(" ayy ".into()),
163 Fragment::Parameter(Parameter {
164 hint: DisplayHint::Ip
165 }),
166 Fragment::Literal(" lmao {} {something} ".into()),
167 Fragment::Parameter(Parameter {
168 hint: DisplayHint::LowerHex
169 }),
170 ])
171 );
172 assert!(parse("foo {:}").is_err());
173 assert!(parse("foo { bar").is_err());
174 assert!(parse("foo } bar").is_err());
175 assert!(parse("foo { bar }").is_err());
176 }
177}