mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-05-06 10:19:33 +00:00
allow annotate type with ffi too
This commit is contained in:
parent
9524108474
commit
47f3422057
@ -8,32 +8,27 @@ declareLibrary("nimtimer")
|
||||
type NimTimer = object
|
||||
name: string # set at creation time, read back in each response
|
||||
|
||||
ffiType:
|
||||
type TimerConfig = object
|
||||
name: string
|
||||
type TimerConfig {.ffi.} = object
|
||||
name: string
|
||||
|
||||
ffiType:
|
||||
type EchoRequest = object
|
||||
message: string
|
||||
delayMs: int # how long chronos sleeps before replying
|
||||
type EchoRequest {.ffi.} = object
|
||||
message: string
|
||||
delayMs: int # how long chronos sleeps before replying
|
||||
|
||||
ffiType:
|
||||
type EchoResponse = object
|
||||
echoed: string
|
||||
timerName: string # proves that the timer's own state is accessible
|
||||
type EchoResponse {.ffi.} = object
|
||||
echoed: string
|
||||
timerName: string # proves that the timer's own state is accessible
|
||||
|
||||
ffiType:
|
||||
type ComplexRequest = object
|
||||
messages: seq[EchoRequest]
|
||||
tags: seq[string]
|
||||
note: Option[string]
|
||||
retries: Maybe[int]
|
||||
type ComplexRequest {.ffi.} = object
|
||||
messages: seq[EchoRequest]
|
||||
tags: seq[string]
|
||||
note: Option[string]
|
||||
retries: Maybe[int]
|
||||
|
||||
ffiType:
|
||||
type ComplexResponse = object
|
||||
summary: string
|
||||
itemCount: int
|
||||
hasNote: bool
|
||||
type ComplexResponse {.ffi.} = object
|
||||
summary: string
|
||||
itemCount: int
|
||||
hasNote: bool
|
||||
|
||||
# --- Constructor -----------------------------------------------------------
|
||||
# Called once from Rust. Creates the FFIContext + NimTimer.
|
||||
|
||||
@ -10,6 +10,31 @@ when defined(ffiGenBindings):
|
||||
# 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 =
|
||||
## Returns `s` with the first character uppercased.
|
||||
if s.len == 0:
|
||||
@ -560,22 +585,29 @@ macro ffiRaw*(prc: untyped): untyped =
|
||||
echo result.repr
|
||||
|
||||
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:
|
||||
## - Have a first parameter of the library type (e.g. w: Waku)
|
||||
## - Optionally have additional Nim-typed parameters
|
||||
## - Return Future[Result[RetType, string]]
|
||||
## - NOT include ctx, callback, or userData in its signature
|
||||
## On a type: `type Foo {.ffi.} = object` registers Foo for binding generation
|
||||
## and generates ffiSerialize/ffiDeserialize overloads.
|
||||
##
|
||||
## 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.} =
|
||||
## return ok("done")
|
||||
##
|
||||
## The macro generates:
|
||||
## 1. A named async helper proc (MyLibSendBody) containing the user body
|
||||
## 2. A registerReqFFI call that deserializes cstring args and calls the helper
|
||||
## 3. A C-exported proc with ctx/callback/userData + cstring params
|
||||
|
||||
if prc.kind == nnkTypeDef:
|
||||
var cleanTypeDef = prc.copyNimTree()
|
||||
if cleanTypeDef[0].kind == nnkPragmaExpr:
|
||||
cleanTypeDef[0] = cleanTypeDef[0][0]
|
||||
return registerFfiTypeInfo(cleanTypeDef)
|
||||
|
||||
let procName = prc[0]
|
||||
let formalParams = prc[3]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import std/[json, macros, sequtils, options]
|
||||
import std/[json, macros, options]
|
||||
import results
|
||||
import ./codegen/meta
|
||||
|
||||
@ -81,6 +81,9 @@ proc ffiSerialize*[T](x: Option[T]): string =
|
||||
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()))
|
||||
@ -120,10 +123,16 @@ proc ffiDeserialize*[T](s: cstring, _: typedesc[Option[T]]): Result[Option[T], s
|
||||
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.
|
||||
## Generates ffiSerialize and ffiDeserialize overloads for each type,
|
||||
## and registers the type in ffiTypeRegistry for binding generation.
|
||||
## Registers the type in ffiTypeRegistry for binding generation.
|
||||
## Serialization is handled by the generic ffiSerialize/ffiDeserialize overloads.
|
||||
## Usage:
|
||||
## ffiType:
|
||||
## type Foo = object
|
||||
@ -136,56 +145,21 @@ macro ffiType*(body: untyped): untyped =
|
||||
else:
|
||||
typeDef[0]
|
||||
|
||||
# Collect field metadata for the codegen registry
|
||||
let typeNameStr = $typeName
|
||||
var fieldMetas: seq[FFIFieldMeta] = @[]
|
||||
# typeDef layout: TypDef[name, genericParams, objectTy]
|
||||
# objectTy layout: ObjectTy[empty, empty, recList]
|
||||
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:
|
||||
# identDef: [name1, ..., type, default]
|
||||
let fieldType = identDef[^2]
|
||||
var fieldTypeName: string
|
||||
if fieldType.kind == nnkIdent:
|
||||
fieldTypeName = $fieldType
|
||||
elif fieldType.kind == nnkPtrTy:
|
||||
fieldTypeName = "ptr " & $fieldType[0]
|
||||
else:
|
||||
fieldTypeName = fieldType.repr
|
||||
let fieldTypeName =
|
||||
if fieldType.kind == nnkIdent: $fieldType
|
||||
elif fieldType.kind == nnkPtrTy: "ptr " & $fieldType[0]
|
||||
else: fieldType.repr
|
||||
for i in 0 ..< identDef.len - 2:
|
||||
let fname = $identDef[i]
|
||||
fieldMetas.add(FFIFieldMeta(name: fname, typeName: fieldTypeName))
|
||||
fieldMetas.add(FFIFieldMeta(name: $identDef[i], typeName: fieldTypeName))
|
||||
|
||||
ffiTypeRegistry.add(FFITypeMeta(name: typeNameStr, fields: fieldMetas))
|
||||
|
||||
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)
|
||||
result = body
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user