nandi/rustnimpublic Fork 0
ff34e1b3229df6e21b0c5d77053bb9b7364db5cd
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.

ty.rs · 287 lines · 10.4 KBRust Blame HistoryRaw
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago1//! Rust type -> Nim type mapping.
2//!
3//! Integer width is preserved exactly. Anything that cannot be represented
4//! faithfully in Nim is reported as an error rather than approximated: a
5//! silently widened integer would change the meaning of wrapping arithmetic,
6//! which is precisely the kind of code people write in Rust.
7
8use syn::{GenericArgument, PathArguments, Type, TypeParamBound};
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum Nim {
12 Prim(String),
13 Seq(Box<Nim>),
14 OpenArray(Box<Nim>),
15 Array(usize, Box<Nim>),
16 Tuple(Vec<Nim>),
17 Named(String, Vec<Nim>),
18 Var(Box<Nim>),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago19 /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling
20 /// convention (`closure`), which accepts both a plain top-level proc and
21 /// a closure that captures -- and Rust's `impl Fn` accepts both too.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago22 Proc(Vec<Nim>, Box<Nim>),
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago23 Unit,
24}
25
26impl Nim {
27 pub fn render(&self) -> String {
28 match self {
29 Nim::Prim(s) => s.clone(),
30 Nim::Seq(t) => format!("seq[{}]", t.render()),
31 Nim::OpenArray(t) => format!("openArray[{}]", t.render()),
32 Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()),
33 Nim::Tuple(ts) => {
34 let inner: Vec<String> = ts.iter().map(|t| t.render()).collect();
35 format!("({})", inner.join(", "))
36 }
37 Nim::Named(n, args) if args.is_empty() => n.clone(),
38 Nim::Named(n, args) => {
39 let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
40 format!("{}[{}]", n, inner.join(", "))
41 }
42 Nim::Var(t) => format!("var {}", t.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago43 Nim::Proc(args, ret) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago44 // Nim's proc types name their parameters even when the name is
45 // never used.
46 let inner: Vec<String> = args
47 .iter()
48 .enumerate()
49 .map(|(i, t)| format!("a{}: {}", i, t.render()))
50 .collect();
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago51 match &**ret {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago52 Nim::Unit => format!("proc ({})", inner.join(", ")),
53 r => format!("proc ({}): {}", inner.join(", "), r.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago54 }
55 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago56 Nim::Unit => "void".into(),
57 }
58 }
59
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago60 /// Strip a `var`, which is a parameter-passing mode rather than a type.
61 /// Unlike `owned`, this keeps a view a view.
62 pub fn unvar(self) -> Nim {
63 match self {
64 Nim::Var(t) => t.unvar(),
65 other => other,
66 }
67 }
68
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago69 /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same
70 /// type in an owned position (a field, a return value) must be `seq[T]`.
71 pub fn owned(self) -> Nim {
72 match self {
73 Nim::OpenArray(t) => Nim::Seq(t),
74 Nim::Var(t) => t.owned(),
75 other => other,
76 }
77 }
78
79 pub fn is_integer(&self) -> bool {
80 matches!(self, Nim::Prim(p) if matches!(p.as_str(),
81 "int8"|"int16"|"int32"|"int64"|"int"|
82 "uint8"|"uint16"|"uint32"|"uint64"|"uint"))
83 }
84
85 pub fn is_unsigned(&self) -> bool {
86 matches!(self, Nim::Prim(p) if p.starts_with("uint"))
87 }
88}
89
90pub fn prim(name: &str) -> Option<Nim> {
91 let mapped = match name {
92 "i8" => "int8",
93 "i16" => "int16",
94 "i32" => "int32",
95 "i64" => "int64",
96 "isize" => "int",
97 "u8" => "uint8",
98 "u16" => "uint16",
99 "u32" => "uint32",
100 "u64" => "uint64",
101 "usize" => "uint",
102 "f32" => "float32",
103 "f64" => "float64",
104 "bool" => "bool",
105 "char" => "Rune",
106 "str" | "String" => "string",
107 _ => return None,
108 };
109 Some(Nim::Prim(mapped.into()))
110}
111
112/// Types we refuse rather than approximate.
113pub fn rejected(name: &str) -> Option<&'static str> {
114 match name {
115 "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"),
116 _ => None,
117 }
118}
119
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago120fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
121 match r {
122 syn::ReturnType::Default => Ok(Nim::Unit),
123 syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()),
124 }
125}
126
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago127/// Types from `core::fmt` that must not be confused with a user type of the
128/// same short name. `fmt::Error` and a crate's own `Error` are different
129/// types, and collapsing a path to its last segment would merge them.
130fn std_qualified(p: &syn::Path) -> Option<Nim> {
131 let segs: Vec<String> = p.segments.iter().map(|s| s.ident.to_string()).collect();
132 if segs.len() < 2 {
133 return None;
134 }
135 let (q, name) = (&segs[segs.len() - 2], segs.last()?.as_str());
136 if q != "fmt" {
137 return None;
138 }
139 Some(Nim::Prim(
140 match name {
141 "Error" => "FmtError",
142 "Formatter" => "Formatter",
143 // `fmt::Result` is `Result<(), fmt::Error>`. A formatting impl is
144 // lowered to a proc that returns the formatted string, so the
145 // result type is erased there; this spelling exists so that a
146 // signature mentioning it still maps to something.
147 "Result" => "FmtResult",
148 _ => return None,
149 }
150 .into(),
151 ))
152}
153
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago154pub fn map(t: &Type) -> Result<Nim, String> {
155 match t {
156 Type::Path(p) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago157 if let Some(n) = std_qualified(&p.path) {
158 return Ok(n);
159 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago160 let seg = p
161 .path
162 .segments
163 .last()
164 .ok_or_else(|| "empty type path".to_string())?;
165 let name = seg.ident.to_string();
166
167 if let Some(why) = rejected(&name) {
168 return Err(format!("unsupported type `{}`: {}", name, why));
169 }
170
171 let args: Vec<Nim> = match &seg.arguments {
172 PathArguments::AngleBracketed(a) => a
173 .args
174 .iter()
175 .filter_map(|g| match g {
176 GenericArgument::Type(t) => Some(map(t)),
177 _ => None,
178 })
179 .collect::<Result<_, _>>()?,
180 _ => vec![],
181 };
182
183 match (name.as_str(), args.len()) {
184 ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))),
185 ("Option", 1) => Ok(Nim::Named("Option".into(), args)),
186 ("Result", 2) => Ok(Nim::Named("Result".into(), args)),
187 ("Box", 1) => Ok(args[0].clone()),
188 _ => {
189 if let Some(p) = prim(&name) {
190 Ok(p)
191 } else {
192 Ok(Nim::Named(name, args))
193 }
194 }
195 }
196 }
197 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
198 // decides whether a `var` is legal in the position it is used.
199 Type::Reference(r) => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago200 // `&str` is a borrowed view of characters, not an owned string.
201 // Nim accepts a `string` argument for an `openArray[char]`
202 // parameter, so a literal still passes straight through.
203 if let Type::Path(p) = &*r.elem {
204 if p.path.is_ident("str") {
205 return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into()))));
206 }
207 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago208 let inner = map(&r.elem)?;
209 if r.mutability.is_some() {
210 Ok(Nim::Var(Box::new(inner)))
211 } else {
212 Ok(inner)
213 }
214 }
215 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
216 Type::Array(a) => {
217 let len = match &a.len {
218 syn::Expr::Lit(syn::ExprLit {
219 lit: syn::Lit::Int(i),
220 ..
221 }) => i
222 .base10_parse::<usize>()
223 .map_err(|e| format!("array length: {}", e))?,
224 _ => return Err("array length must be a literal".into()),
225 };
226 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
227 }
228 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
229 Type::Tuple(t) => Ok(Nim::Tuple(
230 t.elems.iter().map(map).collect::<Result<_, _>>()?,
231 )),
232 Type::Paren(p) => map(&p.elem),
233 Type::Group(g) => map(&g.elem),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago234 Type::FnPtr(f) => {
235 let args: Vec<Nim> = f
236 .inputs
237 .iter()
238 .map(|a| map(&a.ty))
239 .collect::<Result<_, _>>()?;
240 Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?)))
241 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago242 Type::ImplTrait(i) => {
243 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
244 // shape where we can recognise it, since Nim has no impl-trait.
245 for b in &i.bounds {
246 if let TypeParamBound::Trait(tb) = b {
247 if let Some(seg) = tb.path.segments.last() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago248 // `impl Fn(A) -> B` is a callable; Nim has a proc type
249 // for exactly this.
250 if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" {
251 if let PathArguments::Parenthesized(a) = &seg.arguments {
252 let args: Vec<Nim> = a
253 .inputs
254 .iter()
255 .map(|a| map(&a.ty))
256 .collect::<Result<_, _>>()?;
257 return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
258 }
259 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago260 if seg.ident == "AsRef" || seg.ident == "Into" {
261 if let PathArguments::AngleBracketed(a) = &seg.arguments {
262 for g in &a.args {
263 if let GenericArgument::Type(t) = g {
264 return map(t);
265 }
266 }
267 }
268 }
269 }
270 }
271 }
272 Err("unsupported `impl Trait` type".into())
273 }
274 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
275 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
276 }
277}
278
279fn discriminant(t: &Type) -> &'static str {
280 match t {
281 Type::Ptr(_) => "raw pointer",
282 Type::TraitObject(_) => "trait object",
283 Type::Never(_) => "never",
284 Type::Macro(_) => "macro",
285 _ => "other",
286 }
287}