I have the following macro that should generate parameters for a variadic parameter call to the C-function "ydb_lock_s". The problem is that the for loop never runs. As i understand that the information is the runtime length of "names". Is there a way to handle this? I asked ChatGTP, Claude, etc. got tons of sulutions but neither worked. Because i have up to 35 parameters, the current code is always repeating the same stuff over hundreds of lines.
macro ydbLockDbVariadicMacro(timeout: culonglong; names: typed; subs: typed, tptoken:uint64): untyped =
result = newCall(ident("ydbLock_s"))
result.add newCall(ident("culonglong"), timeout)
result.add newCall(ident("cint"), newDotExpr(names, ident("len")))
for i in 0..<names.len:
result.add newCall(ident("addr"), newTree(nnkBracketExpr, names, newLit(i)))
result.add newCall(ident("cint"), newDotExpr(newTree(nnkBracketExpr, subs, newLit(i)), ident("len")))
result.add newCall(ident("addr"), newTree(nnkBracketExpr, newTree(nnkBracketExpr, subs, newLit(i)), newLit(0)))
echo "Generated code: ", result.repr
Although your `names` parameter is typed as untyped, I suspect its underlying type is still NimNode.
len(NimNode) returns the number of child nodes, but if names is just an Ident (identifier node), it has no child nodes (and `len(names)` will return 0).
You are right. Too less information.
I implement a wrapper around a C-Api for the YottaDB NoSQL database.
One of the C API-Calls is to lock a "Global Variable" is 'ydb_lock_s' with a signature of:
int ydb_lock_s(unsigned long long timeout_nsec, int namecount, ...);
The nim equivalent is
proc ydb_lock_s*(timeout_nsec: culonglong; namecount: cint): cint {.cdecl, varargs, importc: "ydb_lock_s".}
To set the locks i use a seq[seq[string]] to define the Globals ^LL(x,y,z,...)
@[
@["^LL","HAUS", "1"], @["^LL","HAUS", "2"], @["^LL","HAUS", "3"],
]
For the API to work, the variables need to be passed to a ydb_buffer_t structure:
struct_ydb_buffer_t* {.pure, inheritable, bycopy.} = object
len_alloc*: cuint
len_used*: cuint
buf_addr*: cstring
ydb_buffer_t* = struct_ydb_buffer_t
The current nim implementation that calls the yottadb-Api looks like:
proc ydb_lock_db_variadic(timeout: culonglong, names: seq[ydb_buffer_t], subs: seq[seq[ydb_buffer_t]], tptoken: uint64 = 0): cint =
check()
if names.len == 0:
rc = ydbLock_s(timeout, names.len.cint) # release all locks
elif names.len == 1:
rc = ydbLock_s(timeout, names.len.cint, addr names[0], subs.len.cint, addr subs[0][0])
elif names.len == 2:
rc = ydbLock_s(timeout, names.len.cint,
addr names[0], subs[0].len.cint, addr subs[0][0],
addr names[1], subs[1].len.cint, addr subs[1][0])
elif names.len == 3:
rc = ydbLock_s(timeout, names.len.cint,
addr names[0], subs[0].len.cint, addr subs[0][0],
addr names[1], subs[1].len.cint, addr subs[1][0],
addr names[2], subs[2].len.cint, addr subs[2][0])
Because up to 35 variable parameters can be passed i have implemented more than 1200 lines of code repeating the above code. So the macro or template should eliminate to redundant code. The problem is that only at runtime is clear how many variables need to be passed to the yottadb c-api.
I hope this clarifies the situation a bit. If you need more info or code insight you can look at https://github.com/ljoeckel/nim-yottadb.git
Thank you for your help.
I agree with you. In my previous work, I haven't encountered a case where more than 2-3 locks were necessary. However, it's possible that there are scenarios that require a larger number of locks. YottaDB (aka GT.M and Cache) are databases that have been used in the healthcare and banking sectors for decades. Healthcare in particular has extremely complex applications, e.g. (https://en.wikipedia.org/wiki/VistA).
Since I don't want to change the functionality of the C interface, I need the 35 possible parameters. I would therefore be very interested in a macro/template-based solution. Do you have any suggestions for adapting the existing macro, or is my approach already incorrect?
Like Araq suggested, the best thing would be if there is a version of the function which takes pointer and length. From my short research there doesn't seem to be one though.
I think the best solution would be to copy paste the function: https://github.com/YottaDB/YDB/blob/de12b7b54d5918bd0e7164cb1b387427365af3c7/sr_unix/ydb_lock_s.c#L41 to just create the needed function. It doesn't seem to rely on any static functions or variables so you probably don't have to recompile the library.
A second a bit horrible and not recommended solution is to use a bit of inline assembler to create a varargs call with varying length.
I agree with you. In my previous work, I haven't encountered a case where more than 2-3 locks were necessary. However, it's possible that there are scenarios that require a larger number of locks. YottaDB (aka GT.M and Cache) are databases that have been used in the healthcare and banking sectors for decades. Healthcare in particular has extremely complex applications, e.g. (https://en.wikipedia.org/wiki/VistA).
So where can I find an application that requires to acquire 10 locks or more at the same time? "Extremely complex" doesn't mean that all of a sudden it uses programming patterns that are unheard of anywhere else.
You can't solve a runtime problem with a macro. But variadic functions in C don't need the number of arguments to be known at compile time (as far as I know). Inside the function you basically pop the stack, and the callee can push any number of arguments on the stack.
You have two options to solve your problem:
1.Use libffi to call your function. It allows passing an arbitrary void** pointer to your arguments.
2.Since the number of arguments is passed explicitly in your case, you can safely pass more arguments than this value, and they'll just be ignored (not UB as far as I know). So instead of making 35 calls in a switch, you can make one call, for example (roughly)
proc myProc(args: seq[float]): float =
doAssert args.len <= 35
allArgs: array[35, float]
for i in args.len:
allArgs[i] = args[i]
myVariadicFromC(args.len, allArgs[0], allArgs[1], ..., allArgs[34])
So instead of making 35 calls in a switch, you can make one call, for example (roughly)
That's brilliant!