nandi/rustnimpublic Fork 0
b0ccd80a849033974bea0c5eccff995ecb632c3e
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 · 233 lines · 8.4 KBRust Blame HistoryRaw
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 20h 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 enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago19 /// `impl Fn(A) -> B` / `fn(A) -> B`. `nimcall` is the default calling
20 /// convention for a top-level proc, which is what Rust passes here.
21 Proc(Vec<Nim>, Box<Nim>),
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 20h ago22 Unit,
23}
24
25impl Nim {
26 pub fn render(&self) -> String {
27 match self {
28 Nim::Prim(s) => s.clone(),
29 Nim::Seq(t) => format!("seq[{}]", t.render()),
30 Nim::OpenArray(t) => format!("openArray[{}]", t.render()),
31 Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()),
32 Nim::Tuple(ts) => {
33 let inner: Vec<String> = ts.iter().map(|t| t.render()).collect();
34 format!("({})", inner.join(", "))
35 }
36 Nim::Named(n, args) if args.is_empty() => n.clone(),
37 Nim::Named(n, args) => {
38 let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
39 format!("{}[{}]", n, inner.join(", "))
40 }
41 Nim::Var(t) => format!("var {}", t.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago42 Nim::Proc(args, ret) => {
43 let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
44 match &**ret {
45 Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),
46 r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),
47 }
48 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 20h ago49 Nim::Unit => "void".into(),
50 }
51 }
52
53 /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same
54 /// type in an owned position (a field, a return value) must be `seq[T]`.
55 pub fn owned(self) -> Nim {
56 match self {
57 Nim::OpenArray(t) => Nim::Seq(t),
58 Nim::Var(t) => t.owned(),
59 other => other,
60 }
61 }
62
63 pub fn is_integer(&self) -> bool {
64 matches!(self, Nim::Prim(p) if matches!(p.as_str(),
65 "int8"|"int16"|"int32"|"int64"|"int"|
66 "uint8"|"uint16"|"uint32"|"uint64"|"uint"))
67 }
68
69 pub fn is_unsigned(&self) -> bool {
70 matches!(self, Nim::Prim(p) if p.starts_with("uint"))
71 }
72}
73
74pub fn prim(name: &str) -> Option<Nim> {
75 let mapped = match name {
76 "i8" => "int8",
77 "i16" => "int16",
78 "i32" => "int32",
79 "i64" => "int64",
80 "isize" => "int",
81 "u8" => "uint8",
82 "u16" => "uint16",
83 "u32" => "uint32",
84 "u64" => "uint64",
85 "usize" => "uint",
86 "f32" => "float32",
87 "f64" => "float64",
88 "bool" => "bool",
89 "char" => "Rune",
90 "str" | "String" => "string",
91 _ => return None,
92 };
93 Some(Nim::Prim(mapped.into()))
94}
95
96/// Types we refuse rather than approximate.
97pub fn rejected(name: &str) -> Option<&'static str> {
98 match name {
99 "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"),
100 _ => None,
101 }
102}
103
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago104fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
105 match r {
106 syn::ReturnType::Default => Ok(Nim::Unit),
107 syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()),
108 }
109}
110
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 20h ago111pub fn map(t: &Type) -> Result<Nim, String> {
112 match t {
113 Type::Path(p) => {
114 let seg = p
115 .path
116 .segments
117 .last()
118 .ok_or_else(|| "empty type path".to_string())?;
119 let name = seg.ident.to_string();
120
121 if let Some(why) = rejected(&name) {
122 return Err(format!("unsupported type `{}`: {}", name, why));
123 }
124
125 let args: Vec<Nim> = match &seg.arguments {
126 PathArguments::AngleBracketed(a) => a
127 .args
128 .iter()
129 .filter_map(|g| match g {
130 GenericArgument::Type(t) => Some(map(t)),
131 _ => None,
132 })
133 .collect::<Result<_, _>>()?,
134 _ => vec![],
135 };
136
137 match (name.as_str(), args.len()) {
138 ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))),
139 ("Option", 1) => Ok(Nim::Named("Option".into(), args)),
140 ("Result", 2) => Ok(Nim::Named("Result".into(), args)),
141 ("Box", 1) => Ok(args[0].clone()),
142 _ => {
143 if let Some(p) = prim(&name) {
144 Ok(p)
145 } else {
146 Ok(Nim::Named(name, args))
147 }
148 }
149 }
150 }
151 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
152 // decides whether a `var` is legal in the position it is used.
153 Type::Reference(r) => {
154 let inner = map(&r.elem)?;
155 if r.mutability.is_some() {
156 Ok(Nim::Var(Box::new(inner)))
157 } else {
158 Ok(inner)
159 }
160 }
161 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
162 Type::Array(a) => {
163 let len = match &a.len {
164 syn::Expr::Lit(syn::ExprLit {
165 lit: syn::Lit::Int(i),
166 ..
167 }) => i
168 .base10_parse::<usize>()
169 .map_err(|e| format!("array length: {}", e))?,
170 _ => return Err("array length must be a literal".into()),
171 };
172 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
173 }
174 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
175 Type::Tuple(t) => Ok(Nim::Tuple(
176 t.elems.iter().map(map).collect::<Result<_, _>>()?,
177 )),
178 Type::Paren(p) => map(&p.elem),
179 Type::Group(g) => map(&g.elem),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago180 Type::FnPtr(f) => {
181 let args: Vec<Nim> = f
182 .inputs
183 .iter()
184 .map(|a| map(&a.ty))
185 .collect::<Result<_, _>>()?;
186 Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?)))
187 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 20h ago188 Type::ImplTrait(i) => {
189 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
190 // shape where we can recognise it, since Nim has no impl-trait.
191 for b in &i.bounds {
192 if let TypeParamBound::Trait(tb) = b {
193 if let Some(seg) = tb.path.segments.last() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago194 // `impl Fn(A) -> B` is a callable; Nim has a proc type
195 // for exactly this.
196 if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" {
197 if let PathArguments::Parenthesized(a) = &seg.arguments {
198 let args: Vec<Nim> = a
199 .inputs
200 .iter()
201 .map(|a| map(&a.ty))
202 .collect::<Result<_, _>>()?;
203 return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
204 }
205 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 20h ago206 if seg.ident == "AsRef" || seg.ident == "Into" {
207 if let PathArguments::AngleBracketed(a) = &seg.arguments {
208 for g in &a.args {
209 if let GenericArgument::Type(t) = g {
210 return map(t);
211 }
212 }
213 }
214 }
215 }
216 }
217 }
218 Err("unsupported `impl Trait` type".into())
219 }
220 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
221 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
222 }
223}
224
225fn discriminant(t: &Type) -> &'static str {
226 match t {
227 Type::Ptr(_) => "raw pointer",
228 Type::TraitObject(_) => "trait object",
229 Type::Never(_) => "never",
230 Type::Macro(_) => "macro",
231 _ => "other",
232 }
233}