Lower `extern "C"` blocks to Nim importc; do not transpile libc
libc is third by dependents in libcosmic's tree but is not a crate to translate. Measured: 129,594 lines across 387 files, of which 54,544 are constants, 7,660 are extern "C" declarations and 1,926 are type aliases, against 121 actual function bodies in the entire crate. There is no code in it, and Nim reaches the same symbols natively -- they are the same objects, not two implementations of one idea. So the general capability was built instead. An extern "C" block lowers to Nim importc declarations; both are statements about a symbol someone else defines and both are bound by the C ABI, so the two declarations describe one symbol rather than one being a translation of the other. Raw pointers map as well, which cleared all 38 raw-pointer blockers in the 400-crate survey. The interesting part is const. Rust emits no C prototype and Nim emits a real one, so declaring strlen as taking *const u8 gets "conflicting types for 'strlen'; have 'NU(char *)'" against string.h -- the C compiler catching a declaration that does not match the symbol, which is a better outcome than Rust's silence. To let honest declarations be expressed, *const T maps to a generated const-qualified alias, one per element type used. Nim's generic form (importc: "const $1*") was tried first and does not work in 2.2.4. A *const T whose C spelling we do not know is rejected rather than quietly declared without the const. Also: as_ptr/as_mut_ptr, pointer and integer-to-pointer casts, the C type aliases (c_int and friends, which Nim has under its own names and which are defined by the same platform compiler), and unsafe blocks in trailing expression position. tests/cases/036-extern-c.rs calls abs, labs, strlen and atoi through this path, byte-identical to rustc. 42 differential cases, all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7db7991 parent: 12c0a01 modified
DESIGN.md +52 -2 | @@ -304,8 +304,7 @@ runner) rather than a wrong answer. | ||
| 304 | 304 | ### Still open |
| 305 | 305 | |
| 306 | 306 | 6. Const generic parameters, `move` closures, closure bodies with statements, |
| 307 | - trait objects, `macro_rules!` definitions and raw pointers are rejected | |
| 308 | - with a reason. A `trait` declaration lowers to nothing — trait resolution | |
| 307 | + trait objects and `macro_rules!` definitions are rejected with a reason. A `trait` declaration lowers to nothing — trait resolution | |
| 309 | 308 | is not modelled — but one giving a method a *default body* is rejected, |
| 310 | 309 | since that body is code with no impl to be emitted into. |
| 311 | 310 | Lifetime parameters are *not* a rejection: they carry no runtime meaning |
| @@ -559,6 +558,57 @@ and `LevelFilter`. The first attempt used Rust's names and broke six existing | ||
| 559 | 558 | cases, because a crate's own `Error` type and an enum field named `Error` |
| 560 | 559 | cannot coexist in one Nim module. |
| 561 | 560 | |
| 561 | +## `libc`: not transpiled, and should not be | |
| 562 | + | |
| 563 | +`libc` is third by dependents in `libcosmic`'s tree (60 of 741), but it is not | |
| 564 | +a crate to translate. Measured: | |
| 565 | + | |
| 566 | +``` | |
| 567 | +129,594 lines across 387 files | |
| 568 | + 54,544 `pub const` | |
| 569 | + 7,660 `pub fn`, nearly all inside `extern "C"` blocks -- declarations | |
| 570 | + 1,926 `pub type` | |
| 571 | + 121 actual function bodies in the entire crate | |
| 572 | +``` | |
| 573 | + | |
| 574 | +There is essentially no code in it. It is a set of declarations binding to the | |
| 575 | +platform's C library, and **Nim reaches those same symbols natively** — the | |
| 576 | +symbols are the same objects, not two implementations of one idea. | |
| 577 | + | |
| 578 | +So what was built is the general capability instead: an `extern "C"` block | |
| 579 | +lowers to Nim `importc` declarations. Both are statements *about* a symbol | |
| 580 | +someone else defines, and both are bound by the C ABI, so the two declarations | |
| 581 | +describe one symbol rather than one being a translation of the other. Raw | |
| 582 | +pointers map too (`*mut T` to `ptr T`), which cleared all 38 raw-pointer | |
| 583 | +blockers in the survey. | |
| 584 | + | |
| 585 | +### `const` matters at the C level even though it does not at the Rust one | |
| 586 | + | |
| 587 | +Rust emits no C prototype; Nim emits a real one. So declaring `strlen` as | |
| 588 | +taking `*const u8` produces: | |
| 589 | + | |
| 590 | +``` | |
| 591 | +error: conflicting types for 'strlen'; have 'NU(char *)' | |
| 592 | +note: previous declaration with type 'size_t(const char *)' | |
| 593 | +``` | |
| 594 | + | |
| 595 | +which is the C compiler catching a declaration that does not match the symbol | |
| 596 | +— a loud failure, and a better outcome than Rust's silence. To make honest | |
| 597 | +declarations expressible, `*const T` maps to a generated const-qualified | |
| 598 | +alias: | |
| 599 | + | |
| 600 | +```nim | |
| 601 | +type RsConstPtrcchar* {.importc: "const char *", nodecl.} = distinct pointer | |
| 602 | +proc strlen*(s: RsConstPtrcchar): uint {.importc: "strlen", cdecl.} | |
| 603 | +``` | |
| 604 | + | |
| 605 | +one per element type actually used. Nim's generic form (`importc: "const $1*"`) | |
| 606 | +was tried first and does not work in 2.2.4. A `*const T` whose C spelling we | |
| 607 | +do not know is rejected rather than declared without the `const`. | |
| 608 | + | |
| 609 | +`tests/cases/036-extern-c.rs` calls `abs`, `labs`, `strlen` and `atoi` through | |
| 610 | +this path, byte-identical to rustc. | |
| 611 | + | |
| 562 | 612 | ## Proof of byte-identity for `base16ct` |
| 563 | 613 | |
| 564 | 614 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive |
| @@ -304,8 +304,7 @@ runner) rather than a wrong answer. | |||
| 304 | ### Still open | 304 | ### Still open |
| 305 | 305 | ||
| 306 | 6. Const generic parameters, `move` closures, closure bodies with statements, | 306 | 6. Const generic parameters, `move` closures, closure bodies with statements, |
| 307 | - trait objects, `macro_rules!` definitions and raw pointers are rejected | 307 | + trait objects and `macro_rules!` definitions are rejected with a reason. A `trait` declaration lowers to nothing — trait resolution |
| 308 | - with a reason. A `trait` declaration lowers to nothing — trait resolution | ||
| 309 | is not modelled — but one giving a method a *default body* is rejected, | 308 | is not modelled — but one giving a method a *default body* is rejected, |
| 310 | since that body is code with no impl to be emitted into. | 309 | since that body is code with no impl to be emitted into. |
| 311 | Lifetime parameters are *not* a rejection: they carry no runtime meaning | 310 | Lifetime parameters are *not* a rejection: they carry no runtime meaning |
| @@ -559,6 +558,57 @@ and `LevelFilter`. The first attempt used Rust's names and broke six existing | |||
| 559 | cases, because a crate's own `Error` type and an enum field named `Error` | 558 | cases, because a crate's own `Error` type and an enum field named `Error` |
| 560 | cannot coexist in one Nim module. | 559 | cannot coexist in one Nim module. |
| 561 | 560 | ||
| 561 | +## `libc`: not transpiled, and should not be | ||
| 562 | + | ||
| 563 | +`libc` is third by dependents in `libcosmic`'s tree (60 of 741), but it is not | ||
| 564 | +a crate to translate. Measured: | ||
| 565 | + | ||
| 566 | +``` | ||
| 567 | +129,594 lines across 387 files | ||
| 568 | + 54,544 `pub const` | ||
| 569 | + 7,660 `pub fn`, nearly all inside `extern "C"` blocks -- declarations | ||
| 570 | + 1,926 `pub type` | ||
| 571 | + 121 actual function bodies in the entire crate | ||
| 572 | +``` | ||
| 573 | + | ||
| 574 | +There is essentially no code in it. It is a set of declarations binding to the | ||
| 575 | +platform's C library, and **Nim reaches those same symbols natively** — the | ||
| 576 | +symbols are the same objects, not two implementations of one idea. | ||
| 577 | + | ||
| 578 | +So what was built is the general capability instead: an `extern "C"` block | ||
| 579 | +lowers to Nim `importc` declarations. Both are statements *about* a symbol | ||
| 580 | +someone else defines, and both are bound by the C ABI, so the two declarations | ||
| 581 | +describe one symbol rather than one being a translation of the other. Raw | ||
| 582 | +pointers map too (`*mut T` to `ptr T`), which cleared all 38 raw-pointer | ||
| 583 | +blockers in the survey. | ||
| 584 | + | ||
| 585 | +### `const` matters at the C level even though it does not at the Rust one | ||
| 586 | + | ||
| 587 | +Rust emits no C prototype; Nim emits a real one. So declaring `strlen` as | ||
| 588 | +taking `*const u8` produces: | ||
| 589 | + | ||
| 590 | +``` | ||
| 591 | +error: conflicting types for 'strlen'; have 'NU(char *)' | ||
| 592 | +note: previous declaration with type 'size_t(const char *)' | ||
| 593 | +``` | ||
| 594 | + | ||
| 595 | +which is the C compiler catching a declaration that does not match the symbol | ||
| 596 | +— a loud failure, and a better outcome than Rust's silence. To make honest | ||
| 597 | +declarations expressible, `*const T` maps to a generated const-qualified | ||
| 598 | +alias: | ||
| 599 | + | ||
| 600 | +```nim | ||
| 601 | +type RsConstPtrcchar* {.importc: "const char *", nodecl.} = distinct pointer | ||
| 602 | +proc strlen*(s: RsConstPtrcchar): uint {.importc: "strlen", cdecl.} | ||
| 603 | +``` | ||
| 604 | + | ||
| 605 | +one per element type actually used. Nim's generic form (`importc: "const $1*"`) | ||
| 606 | +was tried first and does not work in 2.2.4. A `*const T` whose C spelling we | ||
| 607 | +do not know is rejected rather than declared without the `const`. | ||
| 608 | + | ||
| 609 | +`tests/cases/036-extern-c.rs` calls `abs`, `labs`, `strlen` and `atoi` through | ||
| 610 | +this path, byte-identical to rustc. | ||
| 611 | + | ||
| 562 | ## Proof of byte-identity for `base16ct` | 612 | ## Proof of byte-identity for `base16ct` |
| 563 | 613 | ||
| 564 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive | 614 | [`PROOF.md`](PROOF.md) sets out what is actually established: exhaustive |
modified
README.md +12 -0 | @@ -91,6 +91,18 @@ same treatment. rustnim models log's *emitting* side: a transpiled library's | ||
| 91 | 91 | `info!` calls work and, with no logger installed, do nothing, exactly as in |
| 92 | 92 | Rust. Installing a logger is done from Nim with `rsLogSetLogger`. |
| 93 | 93 | |
| 94 | +## C interop | |
| 95 | + | |
| 96 | +An `extern "C"` block lowers to Nim `importc` declarations — both are | |
| 97 | +statements about a symbol someone else defines, bound by the same ABI. Raw | |
| 98 | +pointers map, and `*const T` becomes a const-qualified C declaration, because | |
| 99 | +Nim emits a real prototype where Rust emits none and the C compiler will | |
| 100 | +reject a mismatch. | |
| 101 | + | |
| 102 | +That is the answer to `libc`, which is *not* transpiled: it is 129,594 lines | |
| 103 | +of which 54,544 are constants and 7,660 are declarations, against 121 function | |
| 104 | +bodies in the whole crate. Nim reaches those symbols natively. | |
| 105 | + | |
| 94 | 106 | ## Does it generalise? |
| 95 | 107 | |
| 96 | 108 | `base16ct` is the crate this was built toward, so a second one was tried. |
| @@ -91,6 +91,18 @@ same treatment. rustnim models log's *emitting* side: a transpiled library's | |||
| 91 | `info!` calls work and, with no logger installed, do nothing, exactly as in | 91 | `info!` calls work and, with no logger installed, do nothing, exactly as in |
| 92 | Rust. Installing a logger is done from Nim with `rsLogSetLogger`. | 92 | Rust. Installing a logger is done from Nim with `rsLogSetLogger`. |
| 93 | 93 | ||
| 94 | +## C interop | ||
| 95 | + | ||
| 96 | +An `extern "C"` block lowers to Nim `importc` declarations — both are | ||
| 97 | +statements about a symbol someone else defines, bound by the same ABI. Raw | ||
| 98 | +pointers map, and `*const T` becomes a const-qualified C declaration, because | ||
| 99 | +Nim emits a real prototype where Rust emits none and the C compiler will | ||
| 100 | +reject a mismatch. | ||
| 101 | + | ||
| 102 | +That is the answer to `libc`, which is *not* transpiled: it is 129,594 lines | ||
| 103 | +of which 54,544 are constants and 7,660 are declarations, against 121 function | ||
| 104 | +bodies in the whole crate. Nim reaches those symbols natively. | ||
| 105 | + | ||
| 94 | ## Does it generalise? | 106 | ## Does it generalise? |
| 95 | 107 | ||
| 96 | `base16ct` is the crate this was built toward, so a second one was tried. | 108 | `base16ct` is the crate this was built toward, so a second one was tried. |
modified
src/lower.rs +153 -2 | @@ -216,6 +216,10 @@ pub struct Lowerer { | ||
| 216 | 216 | assoc: HashMap<(String, String), Nim>, |
| 217 | 217 | /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`. |
| 218 | 218 | assoc_consts: HashMap<(String, String), (String, Nim)>, |
| 219 | + /// Symbols declared by an `extern "C"` block. | |
| 220 | + foreign: std::collections::HashSet<String>, | |
| 221 | + /// Const-qualified C pointer aliases already declared. | |
| 222 | + const_ptrs: std::collections::HashSet<String>, | |
| 219 | 223 | /// Types declared by a `bitflags!` invocation. |
| 220 | 224 | bitflags: std::collections::HashSet<String>, |
| 221 | 225 | /// `(type, flag) -> nim const name`. |
| @@ -295,6 +299,8 @@ impl Lowerer { | ||
| 295 | 299 | type_generics: HashMap::new(), |
| 296 | 300 | assoc: HashMap::new(), |
| 297 | 301 | assoc_consts: HashMap::new(), |
| 302 | + foreign: std::collections::HashSet::new(), | |
| 303 | + const_ptrs: std::collections::HashSet::new(), | |
| 298 | 304 | bitflags: std::collections::HashSet::new(), |
| 299 | 305 | flag_consts: HashMap::new(), |
| 300 | 306 | use_map: HashMap::new(), |
| @@ -566,6 +572,17 @@ impl Lowerer { | ||
| 566 | 572 | Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => { |
| 567 | 573 | self.collect_bitflags(&m.mac)?; |
| 568 | 574 | } |
| 575 | + Item::ForeignMod(f) => { | |
| 576 | + for it in &f.items { | |
| 577 | + if let syn::ForeignItem::Fn(fi) = it { | |
| 578 | + let (params, ret) = self.signature(&fi.sig)?; | |
| 579 | + self.fns.insert( | |
| 580 | + (self.cur_mod.clone(), fi.sig.ident.to_string()), | |
| 581 | + Sig { params, ret, generics: Vec::new() }, | |
| 582 | + ); | |
| 583 | + } | |
| 584 | + } | |
| 585 | + } | |
| 569 | 586 | Item::Mod(m) if m.content.is_some() => { |
| 570 | 587 | let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); |
| 571 | 588 | for i in &items { |
| @@ -1217,6 +1234,7 @@ impl Lowerer { | ||
| 1217 | 1234 | match item { |
| 1218 | 1235 | Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item), |
| 1219 | 1236 | Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item), |
| 1237 | + Item::ForeignMod(_) => self.item_inner(item), | |
| 1220 | 1238 | Item::Mod(m) if m.content.is_some() => { |
| 1221 | 1239 | let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); |
| 1222 | 1240 | for i in &items { |
| @@ -1236,7 +1254,9 @@ impl Lowerer { | ||
| 1236 | 1254 | if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) { |
| 1237 | 1255 | return Ok(()); |
| 1238 | 1256 | } |
| 1239 | - if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") { | |
| 1257 | + if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") | |
| 1258 | + || matches!(item, Item::ForeignMod(_)) | |
| 1259 | + { | |
| 1240 | 1260 | return Ok(()); // emitted with the types |
| 1241 | 1261 | } |
| 1242 | 1262 | self.item_inner(item) |
| @@ -1329,6 +1349,7 @@ impl Lowerer { | ||
| 1329 | 1349 | // `use` and `extern crate` are resolution directives with no Nim |
| 1330 | 1350 | // analogue once everything is one module. |
| 1331 | 1351 | Item::Use(_) | Item::ExternCrate(_) => Ok(()), |
| 1352 | + Item::ForeignMod(f) => self.foreign_mod(f), | |
| 1332 | 1353 | Item::Mod(m) if m.content.is_some() => { |
| 1333 | 1354 | // An inline `mod` is flattened; Nim has no nested modules in a |
| 1334 | 1355 | // single file. |
| @@ -1428,6 +1449,107 @@ impl Lowerer { | ||
| 1428 | 1449 | Ok(()) |
| 1429 | 1450 | } |
| 1430 | 1451 | |
| 1452 | + /// An `extern "C" { .. }` block: declarations of symbols someone else | |
| 1453 | + /// defines. Nim's `importc` is the same statement, and both are bound by | |
| 1454 | + /// the C ABI, so the two declarations describe one symbol rather than one | |
| 1455 | + /// being a translation of the other. | |
| 1456 | + fn foreign_mod(&mut self, f: &syn::ItemForeignMod) -> Result<(), String> { | |
| 1457 | + let abi = f | |
| 1458 | + .abi | |
| 1459 | + .name | |
| 1460 | + .as_ref() | |
| 1461 | + .map(|s| s.value()) | |
| 1462 | + .unwrap_or_else(|| "C".into()); | |
| 1463 | + if abi != "C" { | |
| 1464 | + return Err(format!( | |
| 1465 | + "`extern \"{abi}\"` is not the C ABI; only that one has a Nim \ | |
| 1466 | + equivalent" | |
| 1467 | + )); | |
| 1468 | + } | |
| 1469 | + // A const-qualified C pointer needs a type whose C spelling carries | |
| 1470 | + // the `const`; Nim has no such type built in, so one is declared per | |
| 1471 | + // element type actually used. | |
| 1472 | + let mut needed: Vec<Nim> = Vec::new(); | |
| 1473 | + for it in &f.items { | |
| 1474 | + if let syn::ForeignItem::Fn(fi) = it { | |
| 1475 | + let (params, ret) = self.signature(&fi.sig)?; | |
| 1476 | + for t in params.iter().chain(std::iter::once(&ret)) { | |
| 1477 | + if let Nim::ConstPtr(inner) = t { | |
| 1478 | + if !matches!(&**inner, Nim::Prim(p) if p == "void") | |
| 1479 | + && !needed.contains(t) | |
| 1480 | + { | |
| 1481 | + needed.push(t.clone()); | |
| 1482 | + } | |
| 1483 | + } | |
| 1484 | + } | |
| 1485 | + } | |
| 1486 | + } | |
| 1487 | + for t in &needed { | |
| 1488 | + let Nim::ConstPtr(inner) = t else { continue }; | |
| 1489 | + let alias = ty::const_ptr_alias(inner); | |
| 1490 | + if !self.const_ptrs.insert(alias.clone()) { | |
| 1491 | + continue; | |
| 1492 | + } | |
| 1493 | + let c = ty::c_spelling(inner).ok_or_else(|| { | |
| 1494 | + format!( | |
| 1495 | + "`*const {}` has no C spelling we know, so a const-qualified \ | |
| 1496 | + declaration cannot be emitted for it", | |
| 1497 | + inner.render() | |
| 1498 | + ) | |
| 1499 | + })?; | |
| 1500 | + self.line(&format!( | |
| 1501 | + "type {}* {{.importc: \"const {} *\", nodecl.}} = distinct pointer", | |
| 1502 | + alias, c | |
| 1503 | + )); | |
| 1504 | + } | |
| 1505 | + | |
| 1506 | + for it in &f.items { | |
| 1507 | + match it { | |
| 1508 | + syn::ForeignItem::Fn(fi) => { | |
| 1509 | + if fi.sig.variadic.is_some() { | |
| 1510 | + return Err(format!( | |
| 1511 | + "`{}` is variadic; Nim needs `varargs` with a fixed \ | |
| 1512 | + calling shape, which this does not give us", | |
| 1513 | + fi.sig.ident | |
| 1514 | + )); | |
| 1515 | + } | |
| 1516 | + let name = fi.sig.ident.to_string(); | |
| 1517 | + let head = self.head_of(&name, &fi.sig, None)?; | |
| 1518 | + // `importc` names the C symbol, so the Nim name may differ | |
| 1519 | + // from it without changing what is linked. | |
| 1520 | + self.line(&format!( | |
| 1521 | + "{} {{.importc: \"{}\", cdecl.}}", | |
| 1522 | + head, name | |
| 1523 | + )); | |
| 1524 | + let (params, ret) = self.signature(&fi.sig)?; | |
| 1525 | + self.fns.insert( | |
| 1526 | + (self.cur_mod.clone(), name.clone()), | |
| 1527 | + Sig { params, ret, generics: Vec::new() }, | |
| 1528 | + ); | |
| 1529 | + self.foreign.insert(name); | |
| 1530 | + } | |
| 1531 | + syn::ForeignItem::Static(st) => { | |
| 1532 | + let t = self.map_ty(&st.ty)?; | |
| 1533 | + let name = st.ident.to_string(); | |
| 1534 | + self.line(&format!( | |
| 1535 | + "var {}* {{.importc: \"{}\".}}: {}", | |
| 1536 | + ident(&name), | |
| 1537 | + name, | |
| 1538 | + t.render() | |
| 1539 | + )); | |
| 1540 | + self.bind(&name, t); | |
| 1541 | + } | |
| 1542 | + syn::ForeignItem::Type(_) => { | |
| 1543 | + // An opaque C type: Nim spells it as a distinct object. | |
| 1544 | + continue; | |
| 1545 | + } | |
| 1546 | + _ => return Err("unsupported item in an `extern` block".into()), | |
| 1547 | + } | |
| 1548 | + } | |
| 1549 | + self.blank(); | |
| 1550 | + Ok(()) | |
| 1551 | + } | |
| 1552 | + | |
| 1431 | 1553 | /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant |
| 1432 | 1554 | /// namespace, so it becomes a module-level const named for both. |
| 1433 | 1555 | fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> { |
| @@ -3714,6 +3836,17 @@ impl Lowerer { | ||
| 3714 | 3836 | (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => { |
| 3715 | 3837 | format!("Rune(int32({}))", v.code) |
| 3716 | 3838 | } |
| 3839 | + // Pointer-to-pointer, and integer-to-pointer, are reinterpretations | |
| 3840 | + // in both languages. | |
| 3841 | + (Nim::Ptr(_) | Nim::ConstPtr(_), Nim::Ptr(_) | Nim::ConstPtr(_)) => { | |
| 3842 | + format!("cast[{}]({})", to.render(), v.code) | |
| 3843 | + } | |
| 3844 | + (f, Nim::Ptr(_) | Nim::ConstPtr(_)) if f.is_integer() => { | |
| 3845 | + format!("cast[{}]({})", to.render(), v.code) | |
| 3846 | + } | |
| 3847 | + (Nim::Ptr(_) | Nim::ConstPtr(_), t) if t.is_integer() => { | |
| 3848 | + format!("cast[{}]({})", t.render(), v.code) | |
| 3849 | + } | |
| 3717 | 3850 | (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(), |
| 3718 | 3851 | (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => { |
| 3719 | 3852 | // Rust saturates float->int casts; Nim rounds and range-errors. |
| @@ -4599,6 +4732,22 @@ impl Lowerer { | ||
| 4599 | 4732 | }; |
| 4600 | 4733 | (format!("{}({}, {})", f, recv.code, arg.code), Some(out)) |
| 4601 | 4734 | } |
| 4735 | + // `as_ptr` hands a C function the address of the first element, | |
| 4736 | + // which is what Rust's does. An empty slice has no first element | |
| 4737 | + // in either language, and reading through the pointer would be | |
| 4738 | + // undefined in both. | |
| 4739 | + "as_ptr" | "as_mut_ptr" => { | |
| 4740 | + let elem = elem_of(&rt) | |
| 4741 | + .ok_or("`as_ptr` needs a known element type")?; | |
| 4742 | + ( | |
| 4743 | + format!( | |
| 4744 | + "(if {r}.len == 0: nil else: cast[ptr {e}](addr {r}[0]))", | |
| 4745 | + r = recv.code, | |
| 4746 | + e = elem.render() | |
| 4747 | + ), | |
| 4748 | + Some(Nim::Ptr(Box::new(elem))), | |
| 4749 | + ) | |
| 4750 | + } | |
| 4602 | 4751 | "abs" => (format!("abs({})", recv.code), rt.clone()), |
| 4603 | 4752 | "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), |
| 4604 | 4753 | "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), |
| @@ -4985,6 +5134,9 @@ fn expressible(e: &Expr) -> bool { | ||
| 4985 | 5134 | }, |
| 4986 | 5135 | } |
| 4987 | 5136 | } |
| 5137 | + // `unsafe { .. }` is transparent, so it is an expression exactly when | |
| 5138 | + // its block is one. | |
| 5139 | + Expr::Unsafe(u) => single_expr(&u.block).is_some_and(expressible), | |
| 4988 | 5140 | Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false, |
| 4989 | 5141 | _ => true, |
| 4990 | 5142 | } |
| @@ -5362,7 +5514,6 @@ fn item_kind(i: &Item) -> &'static str { | ||
| 5362 | 5514 | Item::Static(_) => "`static`", |
| 5363 | 5515 | Item::Macro(_) => "macro definition", |
| 5364 | 5516 | Item::Union(_) => "`union`", |
| 5365 | - Item::ForeignMod(_) => "`extern` block", | |
| 5366 | 5517 | _ => "item", |
| 5367 | 5518 | } |
| 5368 | 5519 | } |
| @@ -216,6 +216,10 @@ pub struct Lowerer { | |||
| 216 | assoc: HashMap<(String, String), Nim>, | 216 | assoc: HashMap<(String, String), Nim>, |
| 217 | /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`. | 217 | /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`. |
| 218 | assoc_consts: HashMap<(String, String), (String, Nim)>, | 218 | assoc_consts: HashMap<(String, String), (String, Nim)>, |
| 219 | + /// Symbols declared by an `extern "C"` block. | ||
| 220 | + foreign: std::collections::HashSet<String>, | ||
| 221 | + /// Const-qualified C pointer aliases already declared. | ||
| 222 | + const_ptrs: std::collections::HashSet<String>, | ||
| 219 | /// Types declared by a `bitflags!` invocation. | 223 | /// Types declared by a `bitflags!` invocation. |
| 220 | bitflags: std::collections::HashSet<String>, | 224 | bitflags: std::collections::HashSet<String>, |
| 221 | /// `(type, flag) -> nim const name`. | 225 | /// `(type, flag) -> nim const name`. |
| @@ -295,6 +299,8 @@ impl Lowerer { | |||
| 295 | type_generics: HashMap::new(), | 299 | type_generics: HashMap::new(), |
| 296 | assoc: HashMap::new(), | 300 | assoc: HashMap::new(), |
| 297 | assoc_consts: HashMap::new(), | 301 | assoc_consts: HashMap::new(), |
| 302 | + foreign: std::collections::HashSet::new(), | ||
| 303 | + const_ptrs: std::collections::HashSet::new(), | ||
| 298 | bitflags: std::collections::HashSet::new(), | 304 | bitflags: std::collections::HashSet::new(), |
| 299 | flag_consts: HashMap::new(), | 305 | flag_consts: HashMap::new(), |
| 300 | use_map: HashMap::new(), | 306 | use_map: HashMap::new(), |
| @@ -566,6 +572,17 @@ impl Lowerer { | |||
| 566 | Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => { | 572 | Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => { |
| 567 | self.collect_bitflags(&m.mac)?; | 573 | self.collect_bitflags(&m.mac)?; |
| 568 | } | 574 | } |
| 575 | + Item::ForeignMod(f) => { | ||
| 576 | + for it in &f.items { | ||
| 577 | + if let syn::ForeignItem::Fn(fi) = it { | ||
| 578 | + let (params, ret) = self.signature(&fi.sig)?; | ||
| 579 | + self.fns.insert( | ||
| 580 | + (self.cur_mod.clone(), fi.sig.ident.to_string()), | ||
| 581 | + Sig { params, ret, generics: Vec::new() }, | ||
| 582 | + ); | ||
| 583 | + } | ||
| 584 | + } | ||
| 585 | + } | ||
| 569 | Item::Mod(m) if m.content.is_some() => { | 586 | Item::Mod(m) if m.content.is_some() => { |
| 570 | let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); | 587 | let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); |
| 571 | for i in &items { | 588 | for i in &items { |
| @@ -1217,6 +1234,7 @@ impl Lowerer { | |||
| 1217 | match item { | 1234 | match item { |
| 1218 | Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item), | 1235 | Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item), |
| 1219 | Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item), | 1236 | Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item), |
| 1237 | + Item::ForeignMod(_) => self.item_inner(item), | ||
| 1220 | Item::Mod(m) if m.content.is_some() => { | 1238 | Item::Mod(m) if m.content.is_some() => { |
| 1221 | let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); | 1239 | let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); |
| 1222 | for i in &items { | 1240 | for i in &items { |
| @@ -1236,7 +1254,9 @@ impl Lowerer { | |||
| 1236 | if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) { | 1254 | if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) { |
| 1237 | return Ok(()); | 1255 | return Ok(()); |
| 1238 | } | 1256 | } |
| 1239 | - if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") { | 1257 | + if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") |
| 1258 | + || matches!(item, Item::ForeignMod(_)) | ||
| 1259 | + { | ||
| 1240 | return Ok(()); // emitted with the types | 1260 | return Ok(()); // emitted with the types |
| 1241 | } | 1261 | } |
| 1242 | self.item_inner(item) | 1262 | self.item_inner(item) |
| @@ -1329,6 +1349,7 @@ impl Lowerer { | |||
| 1329 | // `use` and `extern crate` are resolution directives with no Nim | 1349 | // `use` and `extern crate` are resolution directives with no Nim |
| 1330 | // analogue once everything is one module. | 1350 | // analogue once everything is one module. |
| 1331 | Item::Use(_) | Item::ExternCrate(_) => Ok(()), | 1351 | Item::Use(_) | Item::ExternCrate(_) => Ok(()), |
| 1352 | + Item::ForeignMod(f) => self.foreign_mod(f), | ||
| 1332 | Item::Mod(m) if m.content.is_some() => { | 1353 | Item::Mod(m) if m.content.is_some() => { |
| 1333 | // An inline `mod` is flattened; Nim has no nested modules in a | 1354 | // An inline `mod` is flattened; Nim has no nested modules in a |
| 1334 | // single file. | 1355 | // single file. |
| @@ -1428,6 +1449,107 @@ impl Lowerer { | |||
| 1428 | Ok(()) | 1449 | Ok(()) |
| 1429 | } | 1450 | } |
| 1430 | 1451 | ||
| 1452 | + /// An `extern "C" { .. }` block: declarations of symbols someone else | ||
| 1453 | + /// defines. Nim's `importc` is the same statement, and both are bound by | ||
| 1454 | + /// the C ABI, so the two declarations describe one symbol rather than one | ||
| 1455 | + /// being a translation of the other. | ||
| 1456 | + fn foreign_mod(&mut self, f: &syn::ItemForeignMod) -> Result<(), String> { | ||
| 1457 | + let abi = f | ||
| 1458 | + .abi | ||
| 1459 | + .name | ||
| 1460 | + .as_ref() | ||
| 1461 | + .map(|s| s.value()) | ||
| 1462 | + .unwrap_or_else(|| "C".into()); | ||
| 1463 | + if abi != "C" { | ||
| 1464 | + return Err(format!( | ||
| 1465 | + "`extern \"{abi}\"` is not the C ABI; only that one has a Nim \ | ||
| 1466 | + equivalent" | ||
| 1467 | + )); | ||
| 1468 | + } | ||
| 1469 | + // A const-qualified C pointer needs a type whose C spelling carries | ||
| 1470 | + // the `const`; Nim has no such type built in, so one is declared per | ||
| 1471 | + // element type actually used. | ||
| 1472 | + let mut needed: Vec<Nim> = Vec::new(); | ||
| 1473 | + for it in &f.items { | ||
| 1474 | + if let syn::ForeignItem::Fn(fi) = it { | ||
| 1475 | + let (params, ret) = self.signature(&fi.sig)?; | ||
| 1476 | + for t in params.iter().chain(std::iter::once(&ret)) { | ||
| 1477 | + if let Nim::ConstPtr(inner) = t { | ||
| 1478 | + if !matches!(&**inner, Nim::Prim(p) if p == "void") | ||
| 1479 | + && !needed.contains(t) | ||
| 1480 | + { | ||
| 1481 | + needed.push(t.clone()); | ||
| 1482 | + } | ||
| 1483 | + } | ||
| 1484 | + } | ||
| 1485 | + } | ||
| 1486 | + } | ||
| 1487 | + for t in &needed { | ||
| 1488 | + let Nim::ConstPtr(inner) = t else { continue }; | ||
| 1489 | + let alias = ty::const_ptr_alias(inner); | ||
| 1490 | + if !self.const_ptrs.insert(alias.clone()) { | ||
| 1491 | + continue; | ||
| 1492 | + } | ||
| 1493 | + let c = ty::c_spelling(inner).ok_or_else(|| { | ||
| 1494 | + format!( | ||
| 1495 | + "`*const {}` has no C spelling we know, so a const-qualified \ | ||
| 1496 | + declaration cannot be emitted for it", | ||
| 1497 | + inner.render() | ||
| 1498 | + ) | ||
| 1499 | + })?; | ||
| 1500 | + self.line(&format!( | ||
| 1501 | + "type {}* {{.importc: \"const {} *\", nodecl.}} = distinct pointer", | ||
| 1502 | + alias, c | ||
| 1503 | + )); | ||
| 1504 | + } | ||
| 1505 | + | ||
| 1506 | + for it in &f.items { | ||
| 1507 | + match it { | ||
| 1508 | + syn::ForeignItem::Fn(fi) => { | ||
| 1509 | + if fi.sig.variadic.is_some() { | ||
| 1510 | + return Err(format!( | ||
| 1511 | + "`{}` is variadic; Nim needs `varargs` with a fixed \ | ||
| 1512 | + calling shape, which this does not give us", | ||
| 1513 | + fi.sig.ident | ||
| 1514 | + )); | ||
| 1515 | + } | ||
| 1516 | + let name = fi.sig.ident.to_string(); | ||
| 1517 | + let head = self.head_of(&name, &fi.sig, None)?; | ||
| 1518 | + // `importc` names the C symbol, so the Nim name may differ | ||
| 1519 | + // from it without changing what is linked. | ||
| 1520 | + self.line(&format!( | ||
| 1521 | + "{} {{.importc: \"{}\", cdecl.}}", | ||
| 1522 | + head, name | ||
| 1523 | + )); | ||
| 1524 | + let (params, ret) = self.signature(&fi.sig)?; | ||
| 1525 | + self.fns.insert( | ||
| 1526 | + (self.cur_mod.clone(), name.clone()), | ||
| 1527 | + Sig { params, ret, generics: Vec::new() }, | ||
| 1528 | + ); | ||
| 1529 | + self.foreign.insert(name); | ||
| 1530 | + } | ||
| 1531 | + syn::ForeignItem::Static(st) => { | ||
| 1532 | + let t = self.map_ty(&st.ty)?; | ||
| 1533 | + let name = st.ident.to_string(); | ||
| 1534 | + self.line(&format!( | ||
| 1535 | + "var {}* {{.importc: \"{}\".}}: {}", | ||
| 1536 | + ident(&name), | ||
| 1537 | + name, | ||
| 1538 | + t.render() | ||
| 1539 | + )); | ||
| 1540 | + self.bind(&name, t); | ||
| 1541 | + } | ||
| 1542 | + syn::ForeignItem::Type(_) => { | ||
| 1543 | + // An opaque C type: Nim spells it as a distinct object. | ||
| 1544 | + continue; | ||
| 1545 | + } | ||
| 1546 | + _ => return Err("unsupported item in an `extern` block".into()), | ||
| 1547 | + } | ||
| 1548 | + } | ||
| 1549 | + self.blank(); | ||
| 1550 | + Ok(()) | ||
| 1551 | + } | ||
| 1552 | + | ||
| 1431 | /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant | 1553 | /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant |
| 1432 | /// namespace, so it becomes a module-level const named for both. | 1554 | /// namespace, so it becomes a module-level const named for both. |
| 1433 | fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> { | 1555 | fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> { |
| @@ -3714,6 +3836,17 @@ impl Lowerer { | |||
| 3714 | (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => { | 3836 | (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => { |
| 3715 | format!("Rune(int32({}))", v.code) | 3837 | format!("Rune(int32({}))", v.code) |
| 3716 | } | 3838 | } |
| 3839 | + // Pointer-to-pointer, and integer-to-pointer, are reinterpretations | ||
| 3840 | + // in both languages. | ||
| 3841 | + (Nim::Ptr(_) | Nim::ConstPtr(_), Nim::Ptr(_) | Nim::ConstPtr(_)) => { | ||
| 3842 | + format!("cast[{}]({})", to.render(), v.code) | ||
| 3843 | + } | ||
| 3844 | + (f, Nim::Ptr(_) | Nim::ConstPtr(_)) if f.is_integer() => { | ||
| 3845 | + format!("cast[{}]({})", to.render(), v.code) | ||
| 3846 | + } | ||
| 3847 | + (Nim::Ptr(_) | Nim::ConstPtr(_), t) if t.is_integer() => { | ||
| 3848 | + format!("cast[{}]({})", t.render(), v.code) | ||
| 3849 | + } | ||
| 3717 | (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(), | 3850 | (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(), |
| 3718 | (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => { | 3851 | (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => { |
| 3719 | // Rust saturates float->int casts; Nim rounds and range-errors. | 3852 | // Rust saturates float->int casts; Nim rounds and range-errors. |
| @@ -4599,6 +4732,22 @@ impl Lowerer { | |||
| 4599 | }; | 4732 | }; |
| 4600 | (format!("{}({}, {})", f, recv.code, arg.code), Some(out)) | 4733 | (format!("{}({}, {})", f, recv.code, arg.code), Some(out)) |
| 4601 | } | 4734 | } |
| 4735 | + // `as_ptr` hands a C function the address of the first element, | ||
| 4736 | + // which is what Rust's does. An empty slice has no first element | ||
| 4737 | + // in either language, and reading through the pointer would be | ||
| 4738 | + // undefined in both. | ||
| 4739 | + "as_ptr" | "as_mut_ptr" => { | ||
| 4740 | + let elem = elem_of(&rt) | ||
| 4741 | + .ok_or("`as_ptr` needs a known element type")?; | ||
| 4742 | + ( | ||
| 4743 | + format!( | ||
| 4744 | + "(if {r}.len == 0: nil else: cast[ptr {e}](addr {r}[0]))", | ||
| 4745 | + r = recv.code, | ||
| 4746 | + e = elem.render() | ||
| 4747 | + ), | ||
| 4748 | + Some(Nim::Ptr(Box::new(elem))), | ||
| 4749 | + ) | ||
| 4750 | + } | ||
| 4602 | "abs" => (format!("abs({})", recv.code), rt.clone()), | 4751 | "abs" => (format!("abs({})", recv.code), rt.clone()), |
| 4603 | "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), | 4752 | "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), |
| 4604 | "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), | 4753 | "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), |
| @@ -4985,6 +5134,9 @@ fn expressible(e: &Expr) -> bool { | |||
| 4985 | }, | 5134 | }, |
| 4986 | } | 5135 | } |
| 4987 | } | 5136 | } |
| 5137 | + // `unsafe { .. }` is transparent, so it is an expression exactly when | ||
| 5138 | + // its block is one. | ||
| 5139 | + Expr::Unsafe(u) => single_expr(&u.block).is_some_and(expressible), | ||
| 4988 | Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false, | 5140 | Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false, |
| 4989 | _ => true, | 5141 | _ => true, |
| 4990 | } | 5142 | } |
| @@ -5362,7 +5514,6 @@ fn item_kind(i: &Item) -> &'static str { | |||
| 5362 | Item::Static(_) => "`static`", | 5514 | Item::Static(_) => "`static`", |
| 5363 | Item::Macro(_) => "macro definition", | 5515 | Item::Macro(_) => "macro definition", |
| 5364 | Item::Union(_) => "`union`", | 5516 | Item::Union(_) => "`union`", |
| 5365 | - Item::ForeignMod(_) => "`extern` block", | ||
| 5366 | _ => "item", | 5517 | _ => "item", |
| 5367 | } | 5518 | } |
| 5368 | } | 5519 | } |
modified
src/ty.rs +87 -2 | @@ -16,6 +16,12 @@ pub enum Nim { | ||
| 16 | 16 | Tuple(Vec<Nim>), |
| 17 | 17 | Named(String, Vec<Nim>), |
| 18 | 18 | Var(Box<Nim>), |
| 19 | + /// `*mut T`. Nim's `ptr` is the same thing: an unmanaged address. | |
| 20 | + Ptr(Box<Nim>), | |
| 21 | + /// `*const T`. The const matters at the C level even though it does not at | |
| 22 | + /// the Rust one: Nim emits a real prototype where Rust emits none, and a | |
| 23 | + /// `char *` declaration against C's `const char *` is a compile error. | |
| 24 | + ConstPtr(Box<Nim>), | |
| 19 | 25 | /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling |
| 20 | 26 | /// convention (`closure`), which accepts both a plain top-level proc and |
| 21 | 27 | /// a closure that captures -- and Rust's `impl Fn` accepts both too. |
| @@ -40,6 +46,14 @@ impl Nim { | ||
| 40 | 46 | format!("{}[{}]", n, inner.join(", ")) |
| 41 | 47 | } |
| 42 | 48 | Nim::Var(t) => format!("var {}", t.render()), |
| 49 | + Nim::Ptr(t) => match &**t { | |
| 50 | + Nim::Prim(p) if p == "void" => "pointer".into(), | |
| 51 | + inner => format!("ptr {}", inner.render()), | |
| 52 | + }, | |
| 53 | + Nim::ConstPtr(t) => match &**t { | |
| 54 | + Nim::Prim(p) if p == "void" => "pointer".into(), | |
| 55 | + inner => const_ptr_alias(inner), | |
| 56 | + }, | |
| 43 | 57 | Nim::Proc(args, ret) => { |
| 44 | 58 | // Nim's proc types name their parameters even when the name is |
| 45 | 59 | // never used. |
| @@ -87,6 +101,33 @@ impl Nim { | ||
| 87 | 101 | } |
| 88 | 102 | } |
| 89 | 103 | |
| 104 | +/// C types, as `libc` spells them. Nim has the same set under its own names, | |
| 105 | +/// and both are defined by the platform's C compiler, so these are equal by | |
| 106 | +/// construction rather than by assumption. | |
| 107 | +pub fn c_type(name: &str) -> Option<&'static str> { | |
| 108 | + Some(match name { | |
| 109 | + "c_char" => "cchar", | |
| 110 | + "c_schar" => "cschar", | |
| 111 | + "c_uchar" => "cuchar", | |
| 112 | + "c_short" => "cshort", | |
| 113 | + "c_ushort" => "cushort", | |
| 114 | + "c_int" => "cint", | |
| 115 | + "c_uint" => "cuint", | |
| 116 | + "c_long" => "clong", | |
| 117 | + "c_ulong" => "culong", | |
| 118 | + "c_longlong" => "clonglong", | |
| 119 | + "c_ulonglong" => "culonglong", | |
| 120 | + "c_float" => "cfloat", | |
| 121 | + "c_double" => "cdouble", | |
| 122 | + "size_t" => "csize_t", | |
| 123 | + "ssize_t" => "int", | |
| 124 | + "intptr_t" => "int", | |
| 125 | + "uintptr_t" => "uint", | |
| 126 | + "c_void" => "void", | |
| 127 | + _ => return None, | |
| 128 | + }) | |
| 129 | +} | |
| 130 | + | |
| 90 | 131 | pub fn prim(name: &str) -> Option<Nim> { |
| 91 | 132 | let mapped = match name { |
| 92 | 133 | "i8" => "int8", |
| @@ -104,7 +145,10 @@ pub fn prim(name: &str) -> Option<Nim> { | ||
| 104 | 145 | "bool" => "bool", |
| 105 | 146 | "char" => "Rune", |
| 106 | 147 | "str" | "String" => "string", |
| 107 | - _ => return None, | |
| 148 | + other => match c_type(other) { | |
| 149 | + Some(c) => c, | |
| 150 | + None => return None, | |
| 151 | + }, | |
| 108 | 152 | }; |
| 109 | 153 | Some(Nim::Prim(mapped.into())) |
| 110 | 154 | } |
| @@ -117,6 +161,40 @@ pub fn rejected(name: &str) -> Option<&'static str> { | ||
| 117 | 161 | } |
| 118 | 162 | } |
| 119 | 163 | |
| 164 | +/// The Nim type name for a const-qualified C pointer to `t`. | |
| 165 | +pub fn const_ptr_alias(t: &Nim) -> String { | |
| 166 | + format!("RsConstPtr{}", t.render().replace(' ', "")) | |
| 167 | +} | |
| 168 | + | |
| 169 | +/// The C spelling of a Nim type, for a const-qualified pointer declaration. | |
| 170 | +pub fn c_spelling(t: &Nim) -> Option<&'static str> { | |
| 171 | + let Nim::Prim(p) = t else { return None }; | |
| 172 | + Some(match p.as_str() { | |
| 173 | + "cchar" => "char", | |
| 174 | + "cschar" => "signed char", | |
| 175 | + "cuchar" => "unsigned char", | |
| 176 | + "cshort" => "short", | |
| 177 | + "cushort" => "unsigned short", | |
| 178 | + "cint" => "int", | |
| 179 | + "cuint" => "unsigned int", | |
| 180 | + "clong" => "long", | |
| 181 | + "culong" => "unsigned long", | |
| 182 | + "clonglong" => "long long", | |
| 183 | + "culonglong" => "unsigned long long", | |
| 184 | + "cfloat" => "float", | |
| 185 | + "cdouble" => "double", | |
| 186 | + "uint8" => "unsigned char", | |
| 187 | + "int8" => "signed char", | |
| 188 | + "uint16" => "unsigned short", | |
| 189 | + "int16" => "short", | |
| 190 | + "uint32" => "unsigned int", | |
| 191 | + "int32" => "int", | |
| 192 | + "uint64" => "unsigned long long", | |
| 193 | + "int64" => "long long", | |
| 194 | + _ => return None, | |
| 195 | + }) | |
| 196 | +} | |
| 197 | + | |
| 120 | 198 | fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> { |
| 121 | 199 | match r { |
| 122 | 200 | syn::ReturnType::Default => Ok(Nim::Unit), |
| @@ -215,6 +293,14 @@ pub fn map(t: &Type) -> Result<Nim, String> { | ||
| 215 | 293 | Ok(inner) |
| 216 | 294 | } |
| 217 | 295 | } |
| 296 | + Type::Ptr(p) => { | |
| 297 | + let inner = Box::new(map(&p.elem)?); | |
| 298 | + Ok(if matches!(p.mutability, syn::PointerMutability::Mut(_)) { | |
| 299 | + Nim::Ptr(inner) | |
| 300 | + } else { | |
| 301 | + Nim::ConstPtr(inner) | |
| 302 | + }) | |
| 303 | + } | |
| 218 | 304 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), |
| 219 | 305 | Type::Array(a) => { |
| 220 | 306 | let len = match &a.len { |
| @@ -309,7 +395,6 @@ pub fn map(t: &Type) -> Result<Nim, String> { | ||
| 309 | 395 | |
| 310 | 396 | fn discriminant(t: &Type) -> &'static str { |
| 311 | 397 | match t { |
| 312 | - Type::Ptr(_) => "raw pointer", | |
| 313 | 398 | Type::TraitObject(_) => "trait object", |
| 314 | 399 | Type::Never(_) => "never", |
| 315 | 400 | Type::Macro(_) => "macro", |
| @@ -16,6 +16,12 @@ pub enum Nim { | |||
| 16 | Tuple(Vec<Nim>), | 16 | Tuple(Vec<Nim>), |
| 17 | Named(String, Vec<Nim>), | 17 | Named(String, Vec<Nim>), |
| 18 | Var(Box<Nim>), | 18 | Var(Box<Nim>), |
| 19 | + /// `*mut T`. Nim's `ptr` is the same thing: an unmanaged address. | ||
| 20 | + Ptr(Box<Nim>), | ||
| 21 | + /// `*const T`. The const matters at the C level even though it does not at | ||
| 22 | + /// the Rust one: Nim emits a real prototype where Rust emits none, and a | ||
| 23 | + /// `char *` declaration against C's `const char *` is a compile error. | ||
| 24 | + ConstPtr(Box<Nim>), | ||
| 19 | /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling | 25 | /// `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 | 26 | /// convention (`closure`), which accepts both a plain top-level proc and |
| 21 | /// a closure that captures -- and Rust's `impl Fn` accepts both too. | 27 | /// a closure that captures -- and Rust's `impl Fn` accepts both too. |
| @@ -40,6 +46,14 @@ impl Nim { | |||
| 40 | format!("{}[{}]", n, inner.join(", ")) | 46 | format!("{}[{}]", n, inner.join(", ")) |
| 41 | } | 47 | } |
| 42 | Nim::Var(t) => format!("var {}", t.render()), | 48 | Nim::Var(t) => format!("var {}", t.render()), |
| 49 | + Nim::Ptr(t) => match &**t { | ||
| 50 | + Nim::Prim(p) if p == "void" => "pointer".into(), | ||
| 51 | + inner => format!("ptr {}", inner.render()), | ||
| 52 | + }, | ||
| 53 | + Nim::ConstPtr(t) => match &**t { | ||
| 54 | + Nim::Prim(p) if p == "void" => "pointer".into(), | ||
| 55 | + inner => const_ptr_alias(inner), | ||
| 56 | + }, | ||
| 43 | Nim::Proc(args, ret) => { | 57 | Nim::Proc(args, ret) => { |
| 44 | // Nim's proc types name their parameters even when the name is | 58 | // Nim's proc types name their parameters even when the name is |
| 45 | // never used. | 59 | // never used. |
| @@ -87,6 +101,33 @@ impl Nim { | |||
| 87 | } | 101 | } |
| 88 | } | 102 | } |
| 89 | 103 | ||
| 104 | +/// C types, as `libc` spells them. Nim has the same set under its own names, | ||
| 105 | +/// and both are defined by the platform's C compiler, so these are equal by | ||
| 106 | +/// construction rather than by assumption. | ||
| 107 | +pub fn c_type(name: &str) -> Option<&'static str> { | ||
| 108 | + Some(match name { | ||
| 109 | + "c_char" => "cchar", | ||
| 110 | + "c_schar" => "cschar", | ||
| 111 | + "c_uchar" => "cuchar", | ||
| 112 | + "c_short" => "cshort", | ||
| 113 | + "c_ushort" => "cushort", | ||
| 114 | + "c_int" => "cint", | ||
| 115 | + "c_uint" => "cuint", | ||
| 116 | + "c_long" => "clong", | ||
| 117 | + "c_ulong" => "culong", | ||
| 118 | + "c_longlong" => "clonglong", | ||
| 119 | + "c_ulonglong" => "culonglong", | ||
| 120 | + "c_float" => "cfloat", | ||
| 121 | + "c_double" => "cdouble", | ||
| 122 | + "size_t" => "csize_t", | ||
| 123 | + "ssize_t" => "int", | ||
| 124 | + "intptr_t" => "int", | ||
| 125 | + "uintptr_t" => "uint", | ||
| 126 | + "c_void" => "void", | ||
| 127 | + _ => return None, | ||
| 128 | + }) | ||
| 129 | +} | ||
| 130 | + | ||
| 90 | pub fn prim(name: &str) -> Option<Nim> { | 131 | pub fn prim(name: &str) -> Option<Nim> { |
| 91 | let mapped = match name { | 132 | let mapped = match name { |
| 92 | "i8" => "int8", | 133 | "i8" => "int8", |
| @@ -104,7 +145,10 @@ pub fn prim(name: &str) -> Option<Nim> { | |||
| 104 | "bool" => "bool", | 145 | "bool" => "bool", |
| 105 | "char" => "Rune", | 146 | "char" => "Rune", |
| 106 | "str" | "String" => "string", | 147 | "str" | "String" => "string", |
| 107 | - _ => return None, | 148 | + other => match c_type(other) { |
| 149 | + Some(c) => c, | ||
| 150 | + None => return None, | ||
| 151 | + }, | ||
| 108 | }; | 152 | }; |
| 109 | Some(Nim::Prim(mapped.into())) | 153 | Some(Nim::Prim(mapped.into())) |
| 110 | } | 154 | } |
| @@ -117,6 +161,40 @@ pub fn rejected(name: &str) -> Option<&'static str> { | |||
| 117 | } | 161 | } |
| 118 | } | 162 | } |
| 119 | 163 | ||
| 164 | +/// The Nim type name for a const-qualified C pointer to `t`. | ||
| 165 | +pub fn const_ptr_alias(t: &Nim) -> String { | ||
| 166 | + format!("RsConstPtr{}", t.render().replace(' ', "")) | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +/// The C spelling of a Nim type, for a const-qualified pointer declaration. | ||
| 170 | +pub fn c_spelling(t: &Nim) -> Option<&'static str> { | ||
| 171 | + let Nim::Prim(p) = t else { return None }; | ||
| 172 | + Some(match p.as_str() { | ||
| 173 | + "cchar" => "char", | ||
| 174 | + "cschar" => "signed char", | ||
| 175 | + "cuchar" => "unsigned char", | ||
| 176 | + "cshort" => "short", | ||
| 177 | + "cushort" => "unsigned short", | ||
| 178 | + "cint" => "int", | ||
| 179 | + "cuint" => "unsigned int", | ||
| 180 | + "clong" => "long", | ||
| 181 | + "culong" => "unsigned long", | ||
| 182 | + "clonglong" => "long long", | ||
| 183 | + "culonglong" => "unsigned long long", | ||
| 184 | + "cfloat" => "float", | ||
| 185 | + "cdouble" => "double", | ||
| 186 | + "uint8" => "unsigned char", | ||
| 187 | + "int8" => "signed char", | ||
| 188 | + "uint16" => "unsigned short", | ||
| 189 | + "int16" => "short", | ||
| 190 | + "uint32" => "unsigned int", | ||
| 191 | + "int32" => "int", | ||
| 192 | + "uint64" => "unsigned long long", | ||
| 193 | + "int64" => "long long", | ||
| 194 | + _ => return None, | ||
| 195 | + }) | ||
| 196 | +} | ||
| 197 | + | ||
| 120 | fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> { | 198 | fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> { |
| 121 | match r { | 199 | match r { |
| 122 | syn::ReturnType::Default => Ok(Nim::Unit), | 200 | syn::ReturnType::Default => Ok(Nim::Unit), |
| @@ -215,6 +293,14 @@ pub fn map(t: &Type) -> Result<Nim, String> { | |||
| 215 | Ok(inner) | 293 | Ok(inner) |
| 216 | } | 294 | } |
| 217 | } | 295 | } |
| 296 | + Type::Ptr(p) => { | ||
| 297 | + let inner = Box::new(map(&p.elem)?); | ||
| 298 | + Ok(if matches!(p.mutability, syn::PointerMutability::Mut(_)) { | ||
| 299 | + Nim::Ptr(inner) | ||
| 300 | + } else { | ||
| 301 | + Nim::ConstPtr(inner) | ||
| 302 | + }) | ||
| 303 | + } | ||
| 218 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), | 304 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), |
| 219 | Type::Array(a) => { | 305 | Type::Array(a) => { |
| 220 | let len = match &a.len { | 306 | let len = match &a.len { |
| @@ -309,7 +395,6 @@ pub fn map(t: &Type) -> Result<Nim, String> { | |||
| 309 | 395 | ||
| 310 | fn discriminant(t: &Type) -> &'static str { | 396 | fn discriminant(t: &Type) -> &'static str { |
| 311 | match t { | 397 | match t { |
| 312 | - Type::Ptr(_) => "raw pointer", | ||
| 313 | Type::TraitObject(_) => "trait object", | 398 | Type::TraitObject(_) => "trait object", |
| 314 | Type::Never(_) => "never", | 399 | Type::Never(_) => "never", |
| 315 | Type::Macro(_) => "macro", | 400 | Type::Macro(_) => "macro", |
added
tests/cases/036-extern-c.rs +40 -0 | new file mode 100644 | ||
| @@ -0,0 +1,40 @@ | ||
| 1 | +// An `extern "C"` block declares symbols someone else defines. Nim's | |
| 2 | +// `importc` is the same statement, and both are bound by the C ABI, so the | |
| 3 | +// two declarations describe one symbol rather than one being a translation of | |
| 4 | +// the other. This is what a crate that would otherwise reach for `libc` needs. | |
| 5 | +// | |
| 6 | +// `libc` itself is not transpiled and should not be: it is 129,594 lines of | |
| 7 | +// which 54,544 are constants and 7,660 are declarations like these, against | |
| 8 | +// 121 actual function bodies in the whole crate. Nim reaches the same symbols | |
| 9 | +// natively, so there is nothing to translate. | |
| 10 | +// | |
| 11 | +// The C types must be declared honestly. Nim emits a real C prototype where | |
| 12 | +// Rust does not, so declaring `strlen` as taking `*const u8` is caught by the | |
| 13 | +// C compiler rather than silently linking against a different signature. | |
| 14 | + | |
| 15 | +#[allow(non_camel_case_types)] | |
| 16 | +type c_char = i8; | |
| 17 | +#[allow(non_camel_case_types)] | |
| 18 | +type size_t = usize; | |
| 19 | + | |
| 20 | +extern "C" { | |
| 21 | + fn abs(x: i32) -> i32; | |
| 22 | + fn strlen(s: *const c_char) -> size_t; | |
| 23 | + fn atoi(s: *const c_char) -> i32; | |
| 24 | + fn labs(x: i64) -> i64; | |
| 25 | +} | |
| 26 | + | |
| 27 | +fn cstr(b: &[u8]) -> *const c_char { | |
| 28 | + b.as_ptr() as *const c_char | |
| 29 | +} | |
| 30 | + | |
| 31 | +fn main() { | |
| 32 | + unsafe { | |
| 33 | + println!("{} {} {}", abs(-5), abs(0), abs(7)); | |
| 34 | + println!("{}", labs(-9000000000)); | |
| 35 | + println!("{}", strlen(cstr(b"hello\0"))); | |
| 36 | + println!("{}", strlen(cstr(b"\0"))); | |
| 37 | + println!("{}", atoi(cstr(b"-1234\0"))); | |
| 38 | + println!("{}", atoi(cstr(b"42abc\0"))); | |
| 39 | + } | |
| 40 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | +// An `extern "C"` block declares symbols someone else defines. Nim's | ||
| 2 | +// `importc` is the same statement, and both are bound by the C ABI, so the | ||
| 3 | +// two declarations describe one symbol rather than one being a translation of | ||
| 4 | +// the other. This is what a crate that would otherwise reach for `libc` needs. | ||
| 5 | +// | ||
| 6 | +// `libc` itself is not transpiled and should not be: it is 129,594 lines of | ||
| 7 | +// which 54,544 are constants and 7,660 are declarations like these, against | ||
| 8 | +// 121 actual function bodies in the whole crate. Nim reaches the same symbols | ||
| 9 | +// natively, so there is nothing to translate. | ||
| 10 | +// | ||
| 11 | +// The C types must be declared honestly. Nim emits a real C prototype where | ||
| 12 | +// Rust does not, so declaring `strlen` as taking `*const u8` is caught by the | ||
| 13 | +// C compiler rather than silently linking against a different signature. | ||
| 14 | + | ||
| 15 | +#[allow(non_camel_case_types)] | ||
| 16 | +type c_char = i8; | ||
| 17 | +#[allow(non_camel_case_types)] | ||
| 18 | +type size_t = usize; | ||
| 19 | + | ||
| 20 | +extern "C" { | ||
| 21 | + fn abs(x: i32) -> i32; | ||
| 22 | + fn strlen(s: *const c_char) -> size_t; | ||
| 23 | + fn atoi(s: *const c_char) -> i32; | ||
| 24 | + fn labs(x: i64) -> i64; | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +fn cstr(b: &[u8]) -> *const c_char { | ||
| 28 | + b.as_ptr() as *const c_char | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +fn main() { | ||
| 32 | + unsafe { | ||
| 33 | + println!("{} {} {}", abs(-5), abs(0), abs(7)); | ||
| 34 | + println!("{}", labs(-9000000000)); | ||
| 35 | + println!("{}", strlen(cstr(b"hello\0"))); | ||
| 36 | + println!("{}", strlen(cstr(b"\0"))); | ||
| 37 | + println!("{}", atoi(cstr(b"-1234\0"))); | ||
| 38 | + println!("{}", atoi(cstr(b"42abc\0"))); | ||
| 39 | + } | ||
| 40 | +} | ||