mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-05-18 08:09:26 +00:00
allow annotate type with ffi too
This commit is contained in:
parent
d87fe8b104
commit
6c19dba559
@ -8,32 +8,27 @@ declareLibrary("nimtimer")
|
|||||||
type NimTimer = object
|
type NimTimer = object
|
||||||
name: string # set at creation time, read back in each response
|
name: string # set at creation time, read back in each response
|
||||||
|
|
||||||
ffiType:
|
type TimerConfig {.ffi.} = object
|
||||||
type TimerConfig = object
|
name: string
|
||||||
name: string
|
|
||||||
|
|
||||||
ffiType:
|
type EchoRequest {.ffi.} = object
|
||||||
type EchoRequest = object
|
message: string
|
||||||
message: string
|
delayMs: int # how long chronos sleeps before replying
|
||||||
delayMs: int # how long chronos sleeps before replying
|
|
||||||
|
|
||||||
ffiType:
|
type EchoResponse {.ffi.} = object
|
||||||
type EchoResponse = object
|
echoed: string
|
||||||
echoed: string
|
timerName: string # proves that the timer's own state is accessible
|
||||||
timerName: string # proves that the timer's own state is accessible
|
|
||||||
|
|
||||||
ffiType:
|
type ComplexRequest {.ffi.} = object
|
||||||
type ComplexRequest = object
|
messages: seq[EchoRequest]
|
||||||
messages: seq[EchoRequest]
|
tags: seq[string]
|
||||||
tags: seq[string]
|
note: Option[string]
|
||||||
note: Option[string]
|
retries: Maybe[int]
|
||||||
retries: Maybe[int]
|
|
||||||
|
|
||||||
ffiType:
|
type ComplexResponse {.ffi.} = object
|
||||||
type ComplexResponse = object
|
summary: string
|
||||||
summary: string
|
itemCount: int
|
||||||
itemCount: int
|
hasNote: bool
|
||||||
hasNote: bool
|
|
||||||
|
|
||||||
# --- Constructor -----------------------------------------------------------
|
# --- Constructor -----------------------------------------------------------
|
||||||
# Called once from Rust. Creates the FFIContext + NimTimer.
|
# Called once from Rust. Creates the FFIContext + NimTimer.
|
||||||
|
|||||||
@ -10,6 +10,31 @@ when defined(ffiGenBindings):
|
|||||||
# String helpers used by multiple macros
|
# String helpers used by multiple macros
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc registerFfiTypeInfo(typeDef: NimNode): NimNode {.compileTime.} =
|
||||||
|
## Registers the type in ffiTypeRegistry for binding generation and returns
|
||||||
|
## the clean typeDef. Serialization is handled by the generic overloads in serial.nim.
|
||||||
|
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 = typeDef
|
||||||
|
|
||||||
proc capitalizeFirstLetter(s: string): string =
|
proc capitalizeFirstLetter(s: string): string =
|
||||||
## Returns `s` with the first character uppercased.
|
## Returns `s` with the first character uppercased.
|
||||||
if s.len == 0:
|
if s.len == 0:
|
||||||
@ -560,22 +585,29 @@ macro ffiRaw*(prc: untyped): untyped =
|
|||||||
echo result.repr
|
echo result.repr
|
||||||
|
|
||||||
macro ffi*(prc: untyped): untyped =
|
macro ffi*(prc: untyped): untyped =
|
||||||
## Simplified FFI macro — developer writes a clean Nim-idiomatic signature.
|
## Simplified FFI macro — applies to procs or types.
|
||||||
##
|
##
|
||||||
## The annotated proc must:
|
## On a type: `type Foo {.ffi.} = object` registers Foo for binding generation
|
||||||
## - Have a first parameter of the library type (e.g. w: Waku)
|
## and generates ffiSerialize/ffiDeserialize overloads.
|
||||||
## - Optionally have additional Nim-typed parameters
|
|
||||||
## - Return Future[Result[RetType, string]]
|
|
||||||
## - NOT include ctx, callback, or userData in its signature
|
|
||||||
##
|
##
|
||||||
## Example:
|
## On a proc: the annotated proc must have a first parameter of the library type,
|
||||||
|
## optionally additional Nim-typed parameters, and return Future[Result[RetType, string]].
|
||||||
|
## It must NOT include ctx, callback, or userData in its signature.
|
||||||
|
##
|
||||||
|
## Example (type):
|
||||||
|
## type EchoRequest {.ffi.} = object
|
||||||
|
## message: string
|
||||||
|
## delayMs: int
|
||||||
|
##
|
||||||
|
## Example (proc):
|
||||||
## proc mylib_send*(w: MyLib, cfg: SendConfig): Future[Result[string, string]] {.ffi.} =
|
## proc mylib_send*(w: MyLib, cfg: SendConfig): Future[Result[string, string]] {.ffi.} =
|
||||||
## return ok("done")
|
## return ok("done")
|
||||||
##
|
|
||||||
## The macro generates:
|
if prc.kind == nnkTypeDef:
|
||||||
## 1. A named async helper proc (MyLibSendBody) containing the user body
|
var cleanTypeDef = prc.copyNimTree()
|
||||||
## 2. A registerReqFFI call that deserializes cstring args and calls the helper
|
if cleanTypeDef[0].kind == nnkPragmaExpr:
|
||||||
## 3. A C-exported proc with ctx/callback/userData + cstring params
|
cleanTypeDef[0] = cleanTypeDef[0][0]
|
||||||
|
return registerFfiTypeInfo(cleanTypeDef)
|
||||||
|
|
||||||
let procName = prc[0]
|
let procName = prc[0]
|
||||||
let formalParams = prc[3]
|
let formalParams = prc[3]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import std/[json, macros, sequtils, options]
|
import std/[json, macros, options]
|
||||||
import results
|
import results
|
||||||
import ./codegen/meta
|
import ./codegen/meta
|
||||||
|
|
||||||
@ -81,6 +81,9 @@ proc ffiSerialize*[T](x: Option[T]): string =
|
|||||||
else:
|
else:
|
||||||
"null"
|
"null"
|
||||||
|
|
||||||
|
proc ffiSerialize*[T: object](x: T): string =
|
||||||
|
$(%*x)
|
||||||
|
|
||||||
proc ffiDeserialize*[T](s: cstring, _: typedesc[ptr T]): Result[ptr T, string] =
|
proc ffiDeserialize*[T](s: cstring, _: typedesc[ptr T]): Result[ptr T, string] =
|
||||||
try:
|
try:
|
||||||
let address = cast[ptr T](uint(parseJson($s).getBiggestInt()))
|
let address = cast[ptr T](uint(parseJson($s).getBiggestInt()))
|
||||||
@ -120,10 +123,16 @@ proc ffiDeserialize*[T](s: cstring, _: typedesc[Option[T]]): Result[Option[T], s
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
err(e.msg)
|
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 =
|
macro ffiType*(body: untyped): untyped =
|
||||||
## Statement macro applied to a type declaration block.
|
## Statement macro applied to a type declaration block.
|
||||||
## Generates ffiSerialize and ffiDeserialize overloads for each type,
|
## Registers the type in ffiTypeRegistry for binding generation.
|
||||||
## and registers the type in ffiTypeRegistry for binding generation.
|
## Serialization is handled by the generic ffiSerialize/ffiDeserialize overloads.
|
||||||
## Usage:
|
## Usage:
|
||||||
## ffiType:
|
## ffiType:
|
||||||
## type Foo = object
|
## type Foo = object
|
||||||
@ -136,56 +145,21 @@ macro ffiType*(body: untyped): untyped =
|
|||||||
else:
|
else:
|
||||||
typeDef[0]
|
typeDef[0]
|
||||||
|
|
||||||
# Collect field metadata for the codegen registry
|
|
||||||
let typeNameStr = $typeName
|
let typeNameStr = $typeName
|
||||||
var fieldMetas: seq[FFIFieldMeta] = @[]
|
var fieldMetas: seq[FFIFieldMeta] = @[]
|
||||||
# typeDef layout: TypDef[name, genericParams, objectTy]
|
|
||||||
# objectTy layout: ObjectTy[empty, empty, recList]
|
|
||||||
let objTy = typeDef[2]
|
let objTy = typeDef[2]
|
||||||
if objTy.kind == nnkObjectTy and objTy.len >= 3:
|
if objTy.kind == nnkObjectTy and objTy.len >= 3:
|
||||||
let recList = objTy[2]
|
let recList = objTy[2]
|
||||||
if recList.kind == nnkRecList:
|
if recList.kind == nnkRecList:
|
||||||
for identDef in recList:
|
for identDef in recList:
|
||||||
if identDef.kind == nnkIdentDefs:
|
if identDef.kind == nnkIdentDefs:
|
||||||
# identDef: [name1, ..., type, default]
|
|
||||||
let fieldType = identDef[^2]
|
let fieldType = identDef[^2]
|
||||||
var fieldTypeName: string
|
let fieldTypeName =
|
||||||
if fieldType.kind == nnkIdent:
|
if fieldType.kind == nnkIdent: $fieldType
|
||||||
fieldTypeName = $fieldType
|
elif fieldType.kind == nnkPtrTy: "ptr " & $fieldType[0]
|
||||||
elif fieldType.kind == nnkPtrTy:
|
else: fieldType.repr
|
||||||
fieldTypeName = "ptr " & $fieldType[0]
|
|
||||||
else:
|
|
||||||
fieldTypeName = fieldType.repr
|
|
||||||
for i in 0 ..< identDef.len - 2:
|
for i in 0 ..< identDef.len - 2:
|
||||||
let fname = $identDef[i]
|
fieldMetas.add(FFIFieldMeta(name: $identDef[i], typeName: fieldTypeName))
|
||||||
fieldMetas.add(FFIFieldMeta(name: fname, typeName: fieldTypeName))
|
|
||||||
|
|
||||||
ffiTypeRegistry.add(FFITypeMeta(name: typeNameStr, fields: fieldMetas))
|
ffiTypeRegistry.add(FFITypeMeta(name: typeNameStr, fields: fieldMetas))
|
||||||
|
result = body
|
||||||
let serializeProc = quote:
|
|
||||||
proc ffiSerialize*(x: `typeName`): string =
|
|
||||||
$(%*x)
|
|
||||||
|
|
||||||
var assignmentText = ""
|
|
||||||
for field in fieldMetas:
|
|
||||||
if assignmentText.len > 0:
|
|
||||||
assignmentText &= "\n"
|
|
||||||
assignmentText &=
|
|
||||||
" result[\"" & field.name & "\"] = parseJson(ffiSerialize(x." & field.name & "))"
|
|
||||||
let jsonProc = parseStmt(
|
|
||||||
"proc `%`*(x: " & typeNameStr & "): JsonNode =\n var result = newJObject()\n" &
|
|
||||||
assignmentText & "\n return result\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
let importJson = quote:
|
|
||||||
import json
|
|
||||||
let deserializeProc = quote:
|
|
||||||
proc ffiDeserialize*(
|
|
||||||
s: cstring, _: typedesc[`typeName`]
|
|
||||||
): Result[`typeName`, string] =
|
|
||||||
try:
|
|
||||||
ok(parseJson($s).to(`typeName`))
|
|
||||||
except Exception as e:
|
|
||||||
err(e.msg)
|
|
||||||
|
|
||||||
result = newStmtList(importJson, body, serializeProc, jsonProc, deserializeProc)
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user