Skip to content

Commit 47804a5

Browse files
committed
feat(codegen): shared utils
1 parent 7e3fd96 commit 47804a5

8 files changed

Lines changed: 409 additions & 162 deletions

File tree

ffi/codegen/c.nim

Lines changed: 61 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
## distinctly-named codec emitted by the cbor_helpers template.
1414

1515
import std/[os, strutils, tables, sets]
16-
import ./meta, ./string_helpers, ./c_cpp_common
16+
import ./meta, ./string_helpers, ./c_cpp_common, ./types_ir
1717

1818
## Wire-format C type for any Nim `ptr T` / `pointer`. Fixed 64-bit so the CBOR
1919
## payload size is stable regardless of host architecture (mirrors CppPtrType).
@@ -30,81 +30,43 @@ const
3030
PreludeHeaderName* = "nim_ffi_prelude.h"
3131
CborHeaderName* = "nim_ffi_cbor.h"
3232

33-
type LeafInfo = tuple[ok: bool, cType: string, suffix: string, owns: bool]
34-
35-
func leafCType(t: string): LeafInfo =
36-
## Maps a Nim leaf type to its C type, codec suffix and whether a decoded
37-
## value owns heap memory. `ok` is false for composite types (seq/Option/
38-
## user structs), which are monomorphised separately.
39-
case t
40-
of "int", "int64":
41-
(true, "int64_t", "i64", false)
42-
of "int32":
43-
(true, "int32_t", "i32", false)
44-
of "int16":
45-
(true, "int16_t", "i16", false)
46-
of "int8":
47-
(true, "int8_t", "i8", false)
48-
of "uint", "uint64":
49-
(true, "uint64_t", "u64", false)
50-
of "uint32":
51-
(true, "uint32_t", "u32", false)
52-
of "uint16":
53-
(true, "uint16_t", "u16", false)
54-
of "uint8", "byte":
55-
(true, "uint8_t", "u8", false)
56-
of "bool":
57-
(true, "bool", "bool", false)
58-
of "float", "float64":
59-
(true, "double", "f64", false)
60-
of "float32":
61-
(true, "float", "f32", false)
62-
of "pointer":
63-
(true, CPtrType, "u64", false)
64-
of "string", "cstring":
65-
(true, "NimFfiStr", "str", true)
66-
else:
67-
(false, "", "", false)
68-
69-
func cToken(cType: string): string =
70-
## Short PascalCase token used to build monomorphised container names and
71-
## codec-adapter symbols. Composite C type names are already unique C
72-
## identifiers, so they pass through verbatim.
73-
case cType
74-
of "int64_t": "I64"
75-
of "int32_t": "I32"
76-
of "int16_t": "I16"
77-
of "int8_t": "I8"
78-
of "uint64_t": "U64"
79-
of "uint32_t": "U32"
80-
of "uint16_t": "U16"
81-
of "uint8_t": "U8"
82-
of "bool": "Bool"
83-
of "double": "F64"
84-
of "float": "F32"
85-
of "NimFfiStr": "Str"
86-
of "NimFfiBytes": "Bytes"
87-
else: cType
33+
const scalarCInfoTable: array[ScalarKind, tuple[cType, suffix: string]] = [
34+
skBool: ("bool", "bool"),
35+
skI8: ("int8_t", "i8"),
36+
skI16: ("int16_t", "i16"),
37+
skI32: ("int32_t", "i32"),
38+
skI64: ("int64_t", "i64"),
39+
skU8: ("uint8_t", "u8"),
40+
skU16: ("uint16_t", "u16"),
41+
skU32: ("uint32_t", "u32"),
42+
skU64: ("uint64_t", "u64"),
43+
skF32: ("float", "f32"),
44+
skF64: ("double", "f64"),
45+
]
8846

8947
func leafSuffix(cType: string): string =
90-
## Inverse of leafCType's cType→suffix for the leaf codecs the template
91-
## provides; empty string for composite types.
48+
## C type name → leaf codec suffix for the leaf codecs the template provides;
49+
## empty string for composite types. Driven off the shared scalar table so it
50+
## can't drift from the IR's scalar set.
51+
for s in ScalarKind:
52+
if scalarCInfoTable[s].cType == cType:
53+
return scalarCInfoTable[s].suffix
9254
case cType
93-
of "int64_t": "i64"
94-
of "int32_t": "i32"
95-
of "int16_t": "i16"
96-
of "int8_t": "i8"
97-
of "uint64_t": "u64"
98-
of "uint32_t": "u32"
99-
of "uint16_t": "u16"
100-
of "uint8_t": "u8"
101-
of "bool": "bool"
102-
of "double": "f64"
103-
of "float": "f32"
10455
of "NimFfiStr": "str"
10556
of "NimFfiBytes": "bytes"
10657
else: ""
10758

59+
func cToken(cType: string): string =
60+
## Short PascalCase token used to build monomorphised container names and
61+
## codec-adapter symbols. Leaf types reuse their codec suffix (e.g.
62+
## `int64_t`→`I64`); composite C type names are already unique C identifiers,
63+
## so they pass through verbatim.
64+
let suffix = leafSuffix(cType)
65+
if suffix.len > 0:
66+
capitalizeFirstLetter(suffix)
67+
else:
68+
cType
69+
10870
type CTypeReg = object
10971
libName: string ## snake_case symbol prefix, e.g. "my_timer"
11072
libType: string ## PascalCase container-name prefix, e.g. "MyTimer"
@@ -293,44 +255,44 @@ proc emitStructType(reg: var CTypeReg, t: FFITypeMeta) =
293255
reg.codecs.add(body.join("\n"))
294256
reg.owns[t.name] = owns
295257

296-
proc ensureCType(reg: var CTypeReg, nimType: string): tuple[cType: string, owns: bool] =
297-
let t = nimType.strip()
298-
if t.startsWith("ptr ") or t == "pointer":
299-
return (CPtrType, false)
300-
let leaf = leafCType(t)
301-
if leaf.ok:
302-
return (leaf.cType, leaf.owns)
303-
304-
let seqInner = genericInnerType(t, "seq[")
305-
if seqInner.len > 0:
306-
let inner = seqInner.strip()
307-
if inner == "byte" or inner == "uint8":
308-
return ("NimFfiBytes", true)
309-
let (elemC, _) = ensureCType(reg, inner)
258+
proc ensureCType(reg: var CTypeReg, t: FFIType): tuple[cType: string, owns: bool] =
259+
## Walks the shared type IR into a C type, monomorphising each distinct
260+
## `seq[T]` / `Option[T]` into its own struct + codec triple on first sight.
261+
case t.kind
262+
of ftPtr:
263+
(CPtrType, false)
264+
of ftScalar:
265+
(scalarCInfoTable[t.scalar].cType, false)
266+
of ftStr:
267+
("NimFfiStr", true)
268+
of ftBytes:
269+
("NimFfiBytes", true)
270+
of ftSeq:
271+
let (elemC, _) = ensureCType(reg, t.elem)
310272
let name = reg.libType & "Seq_" & cToken(elemC)
311273
if name notin reg.emitted:
312274
reg.emitted.incl(name)
313275
emitSeqType(reg, name, elemC)
314-
return (name, true)
315-
316-
var optInner = genericInnerType(t, "Option[")
317-
if optInner.len == 0:
318-
optInner = genericInnerType(t, "Maybe[")
319-
if optInner.len > 0:
320-
let (elemC, elemOwns) = ensureCType(reg, optInner.strip())
276+
(name, true)
277+
of ftOpt:
278+
let (elemC, elemOwns) = ensureCType(reg, t.elem)
321279
let name = reg.libType & "Opt_" & cToken(elemC)
322280
if name notin reg.emitted:
323281
reg.emitted.incl(name)
324282
emitOptType(reg, name, elemC, elemOwns)
325-
return (name, reg.owns.getOrDefault(name, false))
283+
(name, reg.owns.getOrDefault(name, false))
284+
of ftStruct:
285+
let name = t.name
286+
if name notin reg.emitted:
287+
reg.emitted.incl(name)
288+
if name in reg.typeTable:
289+
emitStructType(reg, reg.typeTable[name])
290+
else:
291+
reg.decls.add("/* unknown type referenced: " & name & " */")
292+
(name, reg.owns.getOrDefault(name, false))
326293

327-
if t notin reg.emitted:
328-
reg.emitted.incl(t)
329-
if t in reg.typeTable:
330-
emitStructType(reg, reg.typeTable[t])
331-
else:
332-
reg.decls.add("/* unknown type referenced: " & t & " */")
333-
(t, reg.owns.getOrDefault(t, false))
294+
proc ensureCType(reg: var CTypeReg, nimType: string): tuple[cType: string, owns: bool] =
295+
ensureCType(reg, parseFFIType(nimType))
334296

335297
proc reqTypeMeta(p: FFIProcMeta): FFITypeMeta =
336298
## Synthesises the per-proc Req struct as an FFITypeMeta so it flows through
@@ -347,7 +309,7 @@ func paramByValue(nimType: string, ridesAsPtr: bool): bool =
347309
## aggregates (seq, Option, user structs) pass by const pointer.
348310
if ridesAsPtr:
349311
return true
350-
leafCType(nimType.strip()).ok
312+
parseFFIType(nimType).kind in {ftScalar, ftStr, ftPtr}
351313

352314
proc cReturnType(reg: var CTypeReg, p: FFIProcMeta): string =
353315
if p.returnRidesAsPtr():

ffi/codegen/c_cpp_common.nim

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,6 @@
66
import std/strutils
77
import ./meta, ./string_helpers
88

9-
proc genericInnerType*(typeName, prefix: string): string =
10-
## Inner type of a single-parameter generic written `Prefix[Inner]`, e.g.
11-
## `genericInnerType("seq[int]", "seq[")` → `"int"`. Empty string when
12-
## `typeName` is not of that shape.
13-
if typeName.startsWith(prefix) and typeName.endsWith("]"):
14-
let start = prefix.len
15-
let lastIndex = typeName.len - 2
16-
return typeName[start .. lastIndex]
17-
return ""
18-
199
proc stripLibPrefix*(procName, libName: string): string =
2010
## Drops the `<lib>_` prefix from an exported C symbol, e.g.
2111
## `stripLibPrefix("timer_echo", "timer")` → `"echo"`.

ffi/codegen/cpp.nim

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
## the Nim-side cbor_serial codec on the wire — both ends speak RFC 8949).
55

66
import std/[os, strutils]
7-
import ./meta, ./string_helpers, ./c_cpp_common
7+
import ./meta, ./string_helpers, ./c_cpp_common, ./types_ir
88

99
## Wire-format C++ type used for any Nim `ptr T` / `pointer`. Fixed 64-bit so
1010
## the CBOR payload size is stable regardless of host architecture.
@@ -21,35 +21,37 @@ const
2121
ContextRuleOf5Tpl = staticRead("templates/cpp/context_rule_of_5.hpp.tpl")
2222
CMakeListsTpl = staticRead("templates/cpp/CMakeLists.txt.tpl")
2323

24+
func cppScalar(s: ScalarKind): string =
25+
case s
26+
of skBool: "bool"
27+
of skI8: "int8_t"
28+
of skI16: "int16_t"
29+
of skI32: "int32_t"
30+
of skI64: "int64_t"
31+
of skU8: "uint8_t"
32+
of skU16: "uint16_t"
33+
of skU32: "uint32_t"
34+
of skU64: "uint64_t"
35+
of skF32: "float"
36+
of skF64: "double"
37+
38+
func cppSeq(elem: string): string =
39+
"std::vector<" & elem & ">"
40+
41+
func cppOpt(elem: string): string =
42+
"std::optional<" & elem & ">"
43+
44+
const cppMap = NativeTypeMap(
45+
scalar: cppScalar,
46+
str: "std::string",
47+
bytes: "std::vector<uint8_t>",
48+
ptrType: CppPtrType,
49+
seqOf: cppSeq,
50+
optOf: cppOpt,
51+
) ## structName omitted: C++ uses the user type name verbatim
52+
2453
proc nimTypeToCpp*(typeName: string): string =
25-
let trimmed = typeName.strip()
26-
if trimmed.startsWith("ptr "):
27-
return CppPtrType
28-
else:
29-
let seqInner = genericInnerType(trimmed, "seq[")
30-
if seqInner.len > 0:
31-
return "std::vector<" & nimTypeToCpp(seqInner) & ">"
32-
let optionInner = genericInnerType(trimmed, "Option[")
33-
if optionInner.len > 0:
34-
return "std::optional<" & nimTypeToCpp(optionInner) & ">"
35-
let maybeInner = genericInnerType(trimmed, "Maybe[")
36-
if maybeInner.len > 0:
37-
return "std::optional<" & nimTypeToCpp(maybeInner) & ">"
38-
case trimmed
39-
of "string", "cstring": "std::string"
40-
of "int", "int64": "int64_t"
41-
of "int32": "int32_t"
42-
of "int16": "int16_t"
43-
of "int8": "int8_t"
44-
of "uint", "uint64": "uint64_t"
45-
of "uint32": "uint32_t"
46-
of "uint16": "uint16_t"
47-
of "uint8", "byte": "uint8_t"
48-
of "bool": "bool"
49-
of "float", "float32": "float"
50-
of "float64": "double"
51-
of "pointer": CppPtrType
52-
else: trimmed
54+
renderNative(cppMap, parseFFIType(typeName))
5355

5456
proc emitStructCborCodec(
5557
lines: var seq[string], structName: string, fields: seq[(string, string)]

ffi/codegen/rust.nim

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,46 @@
22
## Generates a complete Rust crate that uses CBOR (ciborium) on the wire.
33

44
import std/[os, strutils, sequtils]
5-
import ./meta, ./string_helpers
5+
import ./meta, ./string_helpers, ./types_ir
66

77
## Wire-format Rust type used for any Nim `ptr T` / `pointer`. Fixed 64-bit so
88
## the CBOR payload size is stable regardless of host architecture (mirrors
99
## CppPtrType in cpp.nim).
1010
const RustPtrType* = "u64"
1111

12+
func rustScalar(s: ScalarKind): string =
13+
case s
14+
of skBool: "bool"
15+
of skI8: "i8"
16+
of skI16: "i16"
17+
of skI32: "i32"
18+
of skI64: "i64"
19+
of skU8: "u8"
20+
of skU16: "u16"
21+
of skU32: "u32"
22+
of skU64: "u64"
23+
of skF32: "f32"
24+
of skF64: "f64"
25+
26+
func rustSeq(elem: string): string =
27+
"Vec<" & elem & ">"
28+
29+
func rustOpt(elem: string): string =
30+
"Option<" & elem & ">"
31+
32+
const rustMap = NativeTypeMap(
33+
scalar: rustScalar,
34+
str: "String",
35+
bytes: "Vec<u8>",
36+
ptrType: RustPtrType,
37+
seqOf: rustSeq,
38+
optOf: rustOpt,
39+
structName: capitalizeFirstLetter,
40+
)
41+
1242
proc nimTypeToRust*(typeName: string): string =
1343
## Maps Nim type names to Rust type names, including generics.
14-
let t = typeName.strip()
15-
if t.startsWith("seq[") and t.endsWith("]"):
16-
return "Vec<" & nimTypeToRust(t[4 .. ^2]) & ">"
17-
if t.startsWith("Option[") and t.endsWith("]"):
18-
return "Option<" & nimTypeToRust(t[7 .. ^2]) & ">"
19-
if t.startsWith("Maybe[") and t.endsWith("]"):
20-
return "Option<" & nimTypeToRust(t[6 .. ^2]) & ">"
21-
case t
22-
of "string", "cstring":
23-
"String"
24-
of "int", "int64":
25-
"i64"
26-
of "int32":
27-
"i32"
28-
of "bool":
29-
"bool"
30-
of "float", "float64":
31-
"f64"
32-
of "pointer":
33-
RustPtrType
34-
else:
35-
capitalizeFirstLetter(t)
44+
renderNative(rustMap, parseFFIType(typeName))
3645

3746
proc deriveLibName*(procs: seq[FFIProcMeta]): string =
3847
## Extracts the common prefix before the first `_` from proc names.

ffi/codegen/string_helpers.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ proc camelToSnakeCase*(s: string): string =
2525
first = false
2626
return snake
2727

28-
proc capitalizeFirstLetter*(s: string): string =
28+
func capitalizeFirstLetter*(s: string): string =
2929
## Returns `s` with its first rune uppercased; the rest is left unchanged.
3030
## e.g. "abc" → "Abc", "" → "", "Abc" → "Abc"
3131
if s.len == 0:

0 commit comments

Comments
 (0)