I use annotation macros in C to generate new C code from the AST.
For example in C, where the X macro uses the GNU compiler attribute syntax (supported by GCC and Clang), I do the following:
// Type definition (can access fields from script)
X(script) typedef struct {
int value;
} something_t; // yes I know it violates POSIX to use _t
// Forward declaration (only the interface is needed to generate script bindings)
X(script) X(inc_op) something_t something_inc(const something_t something);
Then I use a code generator using libclang that reads the AST and generates script bindings in C for every AST node annotated with "script".
I also use annotations to automatically generate transparent networking code, save/load functionality etc.
The generated code may contain new structures and functions based on the annotated ones which can be used from other C code afterwards.
Is something like this possible in Nim as well?
I managed to create a macro that creates a wrapper for functions that have the "scriptExport" pragma. It was easier than I expected.
import macros
proc expandFormalParamsForCall(formalParamsNode: NimNode): seq[NimNode] {.compileTime.} =
result = @[]
for i in 1 ..< formalParamsNode.len:
if formalParamsNode[i].kind == nnkIdentDefs:
for j in 0 ..< formalParamsNode[i].len - 2:
result.add(formalParamsNode[i][j])
proc expandFormalParamsForProc(formalParamsNode: NimNode): seq[NimNode] {.compileTime.} =
result = @[]
for i in 0 ..< formalParamsNode.len:
if formalParamsNode[i].kind == nnkIdent:
result.add(formalParamsNode[i])
if formalParamsNode[i].kind == nnkIdentDefs:
for j in 0 ..< formalParamsNode[i].len - 2:
result.add(newNimNode(nnkIdentDefs).add(formalParamsNode[i][j], formalParamsNode[i][^2], newEmptyNode()))
macro scriptExport(definition: untyped): typed =
case definition.kind
of nnkProcDef:
var scriptDefinition = newProc(newIdentNode($definition.name & "_script"), expandFormalParamsForProc(definition.params), newStmtList(
newCall(!"echo", newStrLitNode($definition.name & "_script called")),
newCall(definition.name, expandFormalParamsForCall(definition.params))
))
return newStmtList(definition, scriptDefinition)
else:
error("Only works on procedures")
proc sum(x, y: int): int {.noSideEffect,scriptExport.} =
x + y
echo(sum(1, 2))
echo(sum_script(1, 2))
Still have to do the same for other types etc. as well. From this I can write code that will create bindings etc.
I would really like to answer your question with yes, but I do not entirely understand your question.
Does your own answer mean you already answered the question yourself?
Just one hint from me who already wrote a few more macros.
instead of this:
case definition.kind
of nnkProcDef:
[...]
else:
error([...])
you can do this: definition.expectKind(nnkProcDef)
And for the AST generation I really recommend you to use quote do: as much as possible, even for smaller code sections. The advantage is not only that it is more readable, but also nodes generated with quote do: have positional information, meaning when you get an error in your macro you actually get to know where the error comes from. One thing that you should take care of though when you try this approach is that you need to assign expressions to identifiers first before you can insert them in the ast.
let something = generateSomethingNode([...])
result = quote do:
bananas = `something` * 3
Does your own answer mean you already answered the question yourself?
Yes, the second post describes how I solved it.
instead of this ... you can do this: definition.expectKind(nnkProcDef)
Nice to know this exists, but I want the switch / case / match to generate different code depending on the node-kind that is being passed.
And for the AST generation I really recommend you to use quote do
Never expected there to be a sort of DSL for this. This is really what I needed. So it implicitly creates a template and parses it using getAST. It feels much more like Lisp now.
Thank you for the advice.
Nice to know this exists, but I want the switch / case / match to generate different code depending on the node-kind that is being passed.
Then when you are on the development branch of nim, error has a second optional argument, that allows you to pass a NimNode, so that positional information from that node gets printed on the cosole whe the error occurs.
Then when you are on the development branch of nim, error has a second optional argument, that allows you to pass a NimNode, so that positional information from that node gets printed on the cosole whe the error occurs.
I am using that now. Tnx.
Do you by the way know if Nim supports static constructors? I want to do something like the following:
# Pseudo
type
Matrix[M, N: static[int], T] = object
data: array[M * N, T]
proc newIdentityMatrix[N, T](): Matrix[N, N, T] =
# fill result with zeroes?
for row in 0 .. <N:
for col in 0 .. <N:
result[(row * N) + col] = T(1) # Scalar product identity of type
let x = newIdentityMatrix[4, float]() # Static square matrix set to identity
Tried many things without luck
Do you by the way know if Nim supports static constructors? I want to do something like the following:
type
Matrix[M, N: static[int], T] = object
data: array[M * N, T]
proc newIdentityMatrix(T: typedesc; N: static[int]): Matrix[N, N, T] =
type Out = T
# fill result with zeroes?
for row in 0 .. <N:
for col in 0 .. <N:
result.data[(row * N) + col] = Out(1.0) # Scalar product identity of type
let x = newIdentityMatrix(float, 4) # Static square matrix set to identity
when you want to have play around with the type system, feel free, if you want to have a matrix vector library, I can recommend you to use this library. It aims to be something like the c++ library glm.
The original Glm tries to port GLSL to c++, and this libraries tries to do the same. Nim is just a bit more flexible with the syntax, and I can say that swizzling is supported. But be aware the library while being 100% usable and solid does not all the features that glm offers, but almost everything from GLSL that is portable to nim (functions, type aliases but no texture rasterization).
A bit of history to nim-glm that I think is worth metioning:
Initially I used the library for my own project, but it was not very well tested, and had some design issues. Initially that was ok, because it got me quickly to the point to be productive, but over time the problems became bigger and bigger and the original codebase was almost impossible to improve on (string based code generation). So in a cloak-and-dagger operation I rewrote almost the entire codebase to use generics instead of macros, and I added a lot of missing functionality. Nim-glm still does not have any test coverage that is worth mentioning, but it is used in my projects, and therefore much less likely to be wrong in the implementation. The original author accepted that pull request, but also stated that his is in little interest to maintain that library in the future. The good side is, I will support that library. Maybe at some point it is better to clone from my github repository, but not yet, I push crap that doesn't compile on the master branch.
It is not entirely there yet, for example there are no spline support.
I did something similar to array[M, Vec[N,T] when I wrote my math library in C, but somehow it did not occur to me to do the same in Nim.
The reason that I am looking into Nim (again) is because I ran into limits creating a compute kernel DSL for C. I want to be able to write formulas (using Nim) and output them as OpenCL C, GLSL, SPIR-V using a function or expand them to (Nim) code for CPU execution (using scalar or SIMD etc.) using a macro. Nim seems to be perfect for this.
Krux02: I had a look at your opengl-sandbox code and got most of the examples to compile. Had to modify the font path and remove the regexp code that triggers a conflict between pure/options (nice to know Nim has them :) ) and compiler/options.
Your code really shows the power of what is possible and is not too far from what I want to achieve.
Yes thank you for trying it out. It is nice when a project gets some attention. Some examples are a bit forgotten that they exist, and then when someone tries to compile them I get reminded that they should either be fixed to work, or removed. I decided to leave a comment in there that they are a bit on ice by now. Everything is still under development and nothing is abandoned, I just cant do everything at the same time. The most active example at the moment is the noise_landscape. I recently started to implement portals in there, but they don't work yet. It's actually quite hard to understand what they are when you see them in the current state, but they do interesting.
I would be really happy if you try to do something with that project, I made it so other people can do something with it. But I cannot really recommend using it as a library. I will make breaking changes when I feel the overall product will improve on it. If you add another example that does something interesting though, I might accept your pull request and will maintain it. That is what I did with the neural network example.
But where do I have regexp code? I can't remember using it.
I will be happy to share my project as soon as I get the first parts to work.
A long time ago I created a portal renderer. It used recursion and frustums that extended from each portal. Something like this:
# Pseudo
proc cellRender(cell, frustum):
...
for o in cell.objects:
if frustum.contains(o.bsphere):
case o.type:
of Drawable:
drawableRender(o, frustum)
of Portal:
cellRender(o.cell, o.frustum)
else:
discard
...
cellRender(startingCell, camera.frustum)
The same code can be used for each light as well. The portal and camera should then test for collision to decide if the observer enters an new cell.
About the conflict. The file fancygl.nim imports a regexp module called nre which is part of the Nim standard library. The module nre imports a module called options, but there are 2 modules called options inside the searchPath, compile/options and pure/options. This causes a conflict. I assume this is a bug in the master branch version I am using.