nandi/rustnimpublic Fork 0
1a218c2a103c5c2dec0bdbf9b197a7265bfa8e8b
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 · 197 lines · 6.8 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>),
19 Unit,
20}
21
22impl Nim {
23 pub fn render(&self) -> String {
24 match self {
25 Nim::Prim(s) => s.clone(),
26 Nim::Seq(t) => format!("seq[{}]", t.render()),
27 Nim::OpenArray(t) => format!("openArray[{}]", t.render()),
28 Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()),
29 Nim::Tuple(ts) => {
30 let inner: Vec<String> = ts.iter().map(|t| t.render()).collect();
31 format!("({})", inner.join(", "))
32 }
33 Nim::Named(n, args) if args.is_empty() => n.clone(),
34 Nim::Named(n, args) => {
35 let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
36 format!("{}[{}]", n, inner.join(", "))
37 }
38 Nim::Var(t) => format!("var {}", t.render()),
39 Nim::Unit => "void".into(),
40 }
41 }
42
43 /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same
44 /// type in an owned position (a field, a return value) must be `seq[T]`.
45 pub fn owned(self) -> Nim {
46 match self {
47 Nim::OpenArray(t) => Nim::Seq(t),
48 Nim::Var(t) => t.owned(),
49 other => other,
50 }
51 }
52
53 pub fn is_integer(&self) -> bool {
54 matches!(self, Nim::Prim(p) if matches!(p.as_str(),
55 "int8"|"int16"|"int32"|"int64"|"int"|
56 "uint8"|"uint16"|"uint32"|"uint64"|"uint"))
57 }
58
59 pub fn is_unsigned(&self) -> bool {
60 matches!(self, Nim::Prim(p) if p.starts_with("uint"))
61 }
62}
63
64pub fn prim(name: &str) -> Option<Nim> {
65 let mapped = match name {
66 "i8" => "int8",
67 "i16" => "int16",
68 "i32" => "int32",
69 "i64" => "int64",
70 "isize" => "int",
71 "u8" => "uint8",
72 "u16" => "uint16",
73 "u32" => "uint32",
74 "u64" => "uint64",
75 "usize" => "uint",
76 "f32" => "float32",
77 "f64" => "float64",
78 "bool" => "bool",
79 "char" => "Rune",
80 "str" | "String" => "string",
81 _ => return None,
82 };
83 Some(Nim::Prim(mapped.into()))
84}
85
86/// Types we refuse rather than approximate.
87pub fn rejected(name: &str) -> Option<&'static str> {
88 match name {
89 "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"),
90 _ => None,
91 }
92}
93
94pub fn map(t: &Type) -> Result<Nim, String> {
95 match t {
96 Type::Path(p) => {
97 let seg = p
98 .path
99 .segments
100 .last()
101 .ok_or_else(|| "empty type path".to_string())?;
102 let name = seg.ident.to_string();
103
104 if let Some(why) = rejected(&name) {
105 return Err(format!("unsupported type `{}`: {}", name, why));
106 }
107
108 let args: Vec<Nim> = match &seg.arguments {
109 PathArguments::AngleBracketed(a) => a
110 .args
111 .iter()
112 .filter_map(|g| match g {
113 GenericArgument::Type(t) => Some(map(t)),
114 _ => None,
115 })
116 .collect::<Result<_, _>>()?,
117 _ => vec![],
118 };
119
120 match (name.as_str(), args.len()) {
121 ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))),
122 ("Option", 1) => Ok(Nim::Named("Option".into(), args)),
123 ("Result", 2) => Ok(Nim::Named("Result".into(), args)),
124 ("Box", 1) => Ok(args[0].clone()),
125 _ => {
126 if let Some(p) = prim(&name) {
127 Ok(p)
128 } else {
129 Ok(Nim::Named(name, args))
130 }
131 }
132 }
133 }
134 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
135 // decides whether a `var` is legal in the position it is used.
136 Type::Reference(r) => {
137 let inner = map(&r.elem)?;
138 if r.mutability.is_some() {
139 Ok(Nim::Var(Box::new(inner)))
140 } else {
141 Ok(inner)
142 }
143 }
144 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
145 Type::Array(a) => {
146 let len = match &a.len {
147 syn::Expr::Lit(syn::ExprLit {
148 lit: syn::Lit::Int(i),
149 ..
150 }) => i
151 .base10_parse::<usize>()
152 .map_err(|e| format!("array length: {}", e))?,
153 _ => return Err("array length must be a literal".into()),
154 };
155 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
156 }
157 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
158 Type::Tuple(t) => Ok(Nim::Tuple(
159 t.elems.iter().map(map).collect::<Result<_, _>>()?,
160 )),
161 Type::Paren(p) => map(&p.elem),
162 Type::Group(g) => map(&g.elem),
163 Type::ImplTrait(i) => {
164 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
165 // shape where we can recognise it, since Nim has no impl-trait.
166 for b in &i.bounds {
167 if let TypeParamBound::Trait(tb) = b {
168 if let Some(seg) = tb.path.segments.last() {
169 if seg.ident == "AsRef" || seg.ident == "Into" {
170 if let PathArguments::AngleBracketed(a) = &seg.arguments {
171 for g in &a.args {
172 if let GenericArgument::Type(t) = g {
173 return map(t);
174 }
175 }
176 }
177 }
178 }
179 }
180 }
181 Err("unsupported `impl Trait` type".into())
182 }
183 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
184 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
185 }
186}
187
188fn discriminant(t: &Type) -> &'static str {
189 match t {
190 Type::BareFn(_) => "bare fn",
191 Type::Ptr(_) => "raw pointer",
192 Type::TraitObject(_) => "trait object",
193 Type::Never(_) => "never",
194 Type::Macro(_) => "macro",
195 _ => "other",
196 }
197}