nim-ffi/ffi/serial.nim
2026-05-03 16:23:00 +02:00

166 lines
4.3 KiB
Nim

import std/[json, macros, options]
import results
import ./codegen/meta
proc ffiSerialize*(x: string): string =
$(%*x)
proc ffiSerialize*(x: cstring): string =
if x.isNil:
"null"
else:
ffiSerialize($x)
proc ffiSerialize*(x: int): string =
$x
proc ffiSerialize*(x: int32): string =
$x
proc ffiSerialize*(x: bool): string =
if x: "true" else: "false"
proc ffiSerialize*(x: float): string =
$(%*x)
proc ffiSerialize*(x: pointer): string =
$cast[uint](x)
proc ffiDeserialize*(s: cstring, _: typedesc[string]): Result[string, string] =
try:
let node = parseJson($s)
if node.kind != JString:
return err("expected JSON string")
ok(node.getStr())
except Exception as e:
err(e.msg)
proc ffiDeserialize*(s: cstring, _: typedesc[int]): Result[int, string] =
try:
ok(int(parseJson($s).getBiggestInt()))
except Exception as e:
err(e.msg)
proc ffiDeserialize*(s: cstring, _: typedesc[int32]): Result[int32, string] =
try:
ok(int32(parseJson($s).getBiggestInt()))
except Exception as e:
err(e.msg)
proc ffiDeserialize*(s: cstring, _: typedesc[bool]): Result[bool, string] =
try:
ok(parseJson($s).getBool())
except Exception as e:
err(e.msg)
proc ffiDeserialize*(s: cstring, _: typedesc[float]): Result[float, string] =
try:
ok(parseJson($s).getFloat())
except Exception as e:
err(e.msg)
proc ffiDeserialize*(s: cstring, _: typedesc[pointer]): Result[pointer, string] =
try:
let address = cast[pointer](uint(parseJson($s).getBiggestInt()))
ok(address)
except Exception as e:
err(e.msg)
proc ffiSerialize*[T](x: ptr T): string =
$cast[uint](x)
proc ffiSerialize*[T](x: seq[T]): string =
var arr = newJArray()
for item in x:
arr.add(parseJson(ffiSerialize(item)))
result = $arr
proc ffiSerialize*[T](x: Option[T]): string =
if x.isSome:
ffiSerialize(x.get)
else:
"null"
proc ffiSerialize*[T: object](x: T): string =
$(%*x)
proc ffiDeserialize*[T](s: cstring, _: typedesc[ptr T]): Result[ptr T, string] =
try:
let address = cast[ptr T](uint(parseJson($s).getBiggestInt()))
ok(address)
except Exception as e:
err(e.msg)
proc ffiDeserialize*[T](s: cstring, _: typedesc[seq[T]]): Result[seq[T], string] =
try:
let node = parseJson($s)
if node.kind != JArray:
return err("expected JSON array")
var resultSeq: seq[T] = @[]
for item in node:
let itemJson = $item
let parsed = ffiDeserialize(itemJson.cstring, typedesc[T])
if parsed.isOk:
resultSeq.add(parsed.get)
else:
return err(parsed.error)
ok(resultSeq)
except Exception as e:
err(e.msg)
proc ffiDeserialize*[T](s: cstring, _: typedesc[Option[T]]): Result[Option[T], string] =
try:
let node = parseJson($s)
if node.kind == JNull:
ok(none(T))
else:
let itemJson = $node
let parsed = ffiDeserialize(itemJson.cstring, typedesc[T])
if parsed.isOk:
ok(some(parsed.get))
else:
err(parsed.error)
except Exception as e:
err(e.msg)
proc ffiDeserialize*[T: object](s: cstring, _: typedesc[T]): Result[T, string] =
try:
ok(parseJson($s).to(T))
except Exception as e:
err(e.msg)
macro ffiType*(body: untyped): untyped =
## Statement macro applied to a type declaration block.
## Registers the type in ffiTypeRegistry for binding generation.
## Serialization is handled by the generic ffiSerialize/ffiDeserialize overloads.
## Usage:
## ffiType:
## type Foo = object
## field: int
let typeSection = body[0]
let typeDef = typeSection[0]
let typeName =
if typeDef[0].kind == nnkPostfix:
typeDef[0][1]
else:
typeDef[0]
let typeNameStr = $typeName
var fieldMetas: seq[FFIFieldMeta] = @[]
let objTy = typeDef[2]
if objTy.kind == nnkObjectTy and objTy.len >= 3:
let recList = objTy[2]
if recList.kind == nnkRecList:
for identDef in recList:
if identDef.kind == nnkIdentDefs:
let fieldType = identDef[^2]
let fieldTypeName =
if fieldType.kind == nnkIdent: $fieldType
elif fieldType.kind == nnkPtrTy: "ptr " & $fieldType[0]
else: fieldType.repr
for i in 0 ..< identDef.len - 2:
fieldMetas.add(FFIFieldMeta(name: $identDef[i], typeName: fieldTypeName))
ffiTypeRegistry.add(FFITypeMeta(name: typeNameStr, fields: fieldMetas))
result = body