nandi/rustnimpublic Fork 0
12c0a01b02392b18af24b583ba4ff65ad040e86a
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

fmt.rs · 202 lines · 5.9 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 16h ago1//! Rust format strings (`println!`, `format!`) -> Nim string expressions.
2//!
3//! Only the subset that is reproduced exactly is accepted. An unrecognised
4//! format spec is an error, never a best-effort guess: `{:>8.3}` silently
5//! rendered as `{}` would produce plausible-looking wrong output, which is the
6//! failure mode this project exists to avoid.
7
8/// One piece of a parsed format string.
9#[derive(Debug, PartialEq)]
10pub enum Piece {
11 Lit(String),
12 Arg { r#ref: Ref, spec: Spec },
13}
14
15#[derive(Debug, PartialEq)]
16pub enum Ref {
17 /// `{}` — consumes the next positional argument.
18 Next,
19 /// `{0}` — an explicit index.
20 Index(usize),
21 /// `{name}` — an inline captured identifier.
22 Named(String),
23}
24
25#[derive(Debug, PartialEq, Default)]
26pub struct Spec {
27 pub debug: bool,
28 /// `x`, `X`, `b`, `o` — `None` for Display/Debug.
29 pub radix: Option<char>,
30 pub width: usize,
31 pub zero: bool,
32}
33
34pub fn parse(s: &str) -> Result<Vec<Piece>, String> {
35 let mut out = Vec::new();
36 let mut lit = String::new();
37 let mut it = s.chars().peekable();
38
39 while let Some(c) = it.next() {
40 match c {
41 '{' if it.peek() == Some(&'{') => {
42 it.next();
43 lit.push('{');
44 }
45 '}' if it.peek() == Some(&'}') => {
46 it.next();
47 lit.push('}');
48 }
49 '}' => return Err("unmatched `}` in format string".into()),
50 '{' => {
51 if !lit.is_empty() {
52 out.push(Piece::Lit(std::mem::take(&mut lit)));
53 }
54 let mut body = String::new();
55 let mut closed = false;
56 for c in it.by_ref() {
57 if c == '}' {
58 closed = true;
59 break;
60 }
61 body.push(c);
62 }
63 if !closed {
64 return Err("unmatched `{` in format string".into());
65 }
66 let (name, spec) = match body.split_once(':') {
67 Some((n, s)) => (n, parse_spec(s)?),
68 None => (body.as_str(), Spec::default()),
69 };
70 let r#ref = if name.is_empty() {
71 Ref::Next
72 } else if let Ok(i) = name.parse::<usize>() {
73 Ref::Index(i)
74 } else if name.chars().all(|c| c.is_alphanumeric() || c == '_') {
75 Ref::Named(name.to_string())
76 } else {
77 return Err(format!("unsupported format argument `{name}`"));
78 };
79 out.push(Piece::Arg { r#ref, spec });
80 }
81 _ => lit.push(c),
82 }
83 }
84 if !lit.is_empty() {
85 out.push(Piece::Lit(lit));
86 }
87 Ok(out)
88}
89
90fn parse_spec(s: &str) -> Result<Spec, String> {
91 let mut spec = Spec::default();
92 let mut rest = s;
93
94 match rest.chars().last() {
95 Some('?') => {
96 spec.debug = true;
97 rest = &rest[..rest.len() - 1];
98 }
99 Some(r @ ('x' | 'X' | 'b' | 'o')) => {
100 spec.radix = Some(r);
101 rest = &rest[..rest.len() - 1];
102 }
103 _ => {}
104 }
105
106 if let Some(r) = rest.strip_prefix('0') {
107 spec.zero = true;
108 rest = r;
109 }
110 if !rest.is_empty() {
111 spec.width = rest
112 .parse::<usize>()
113 .map_err(|_| format!("unsupported format spec `:{s}` (precision, alignment and fill are not implemented)"))?;
114 }
115 Ok(spec)
116}
117
118/// Build the Nim expression for one argument, given its already-lowered value.
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 14h ago119///
120/// `integer` says whether the value is one of Nim's integer types. `{:x}` on
121/// an integer formats its two's-complement bit pattern; on anything else it is
122/// a call to that type's own `LowerHex`/`UpperHex` impl, which is a different
123/// operation and a different proc.
124pub fn render_arg(value: &str, spec: &Spec, integer: bool) -> String {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 16h ago125 let core = match spec.radix {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 14h ago126 Some(r) if integer => format!(
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 16h ago127 "rsRadix({}, {}, {})",
128 value,
129 match r {
130 'x' | 'X' => 16,
131 'b' => 2,
132 _ => 8,
133 },
134 r == 'X'
135 ),
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 14h ago136 Some(r) => format!(
137 "{}({})",
138 match r {
139 'x' => "rsLowerHex",
140 'X' => "rsUpperHex",
141 'b' => "rsBinary",
142 _ => "rsOctal",
143 },
144 value
145 ),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 16h ago146 None if spec.debug => format!("rsDebug({value})"),
147 None => format!("rsDisplay({value})"),
148 };
149 if spec.width > 0 {
150 format!("rsPad({}, {}, {})", core, spec.width, spec.zero)
151 } else {
152 core
153 }
154}
155
156/// Nim string literal with Rust's escaping rules applied to the bytes we emit.
157pub fn nim_str(s: &str) -> String {
158 let mut out = String::from("\"");
159 for c in s.chars() {
160 match c {
161 '"' => out.push_str("\\\""),
162 '\\' => out.push_str("\\\\"),
163 '\n' => out.push_str("\\n"),
164 '\t' => out.push_str("\\t"),
165 '\r' => out.push_str("\\r"),
166 c => out.push(c),
167 }
168 }
169 out.push('"');
170 out
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn braces_and_specs() {
179 assert_eq!(parse("a{{b").unwrap(), vec![Piece::Lit("a{b".into())]);
180 assert_eq!(
181 parse("{:02x}").unwrap(),
182 vec![Piece::Arg {
183 r#ref: Ref::Next,
184 spec: Spec { debug: false, radix: Some('x'), width: 2, zero: true }
185 }]
186 );
187 assert_eq!(
188 parse("{n:?}").unwrap(),
189 vec![Piece::Arg {
190 r#ref: Ref::Named("n".into()),
191 spec: Spec { debug: true, ..Spec::default() }
192 }]
193 );
194 }
195
196 #[test]
197 fn unsupported_specs_are_rejected_not_guessed() {
198 assert!(parse("{:>8}").is_err());
199 assert!(parse("{:.3}").is_err());
200 assert!(parse("{").is_err());
201 }
202}