I'm working on an extension to prologue that provides an "Admin area" with simple CRUD-HTML pages for every model you register via a specific proc.
As part of my writing the extension, I'm needing to loop over a compile-time seq of strings in a way that unrolls the loop, similar to fieldPairs.
If you're curious as to why I need that, scroll to the bottom of the post.
Anyway, every time I loop over seq[static string] the individual members are given to me as string aka it loops at runtime without unrolling the loop.
Therefore the question: Is there any way to do this? Is there maybe a simple fix for the macro in 2) ?
These are the things I tried already:
const a = @["la", "le", "li", "lo", "lu"]
#normal for loop
for s in a:
echo s is static # false
#iterating via indices
for i in a.low..a.high:
echo a[i] is static # false
#normal for loop but made static
static:
for s in a:
echo s is static # false
import std/macros
macro staticUnroll*(x: ForLoopStmt): untyped =
## Also known as `static for` in some other languages.
expectKind x, nnkForStmt
var varIndex: NimNode
if x.len == 3: discard
elif x.len == 4: varIndex = x[^4]
else: doAssert false, $x.len
result = newStmtList()
let
body = x[^1]
elems = x[^2]
varName = x[^3]
for i, ai in elems[1]:
if varIndex == nil:
result.add quote do:
template impl(`varName`) {.gensym.} = `body`
impl(`ai`)
else:
let i2 = newLit(i)
result.add quote do:
template impl(`varIndex`, `varName`) {.gensym.} = `body`
impl(`i2`, `ai`)
for s in staticUnroll(a):
echo s is static # Never gets executed
This does not works as elems gets filled with the symbol "a", not the contents of "a" which is the compile-time seq.
Somehow that leads to the for loop never getting executed.
import unroll
unroll for s in a:
echo s is static
This fails with
staticUnroll(a)
stack trace: (most recent call last)
unrolled.nim(127, 14) unroll
unrolled.nim(98, 25) unrollFor
unrolled.nim(64, 5) unrollForImpl
/home/philipp/dev/playground/src/playground.nim(38, 1) template/generic instantiation of `unroll` from here
/home/philipp/.nimble/pkgs/unrolled-0.1.0/unrolled.nim(64, 5) Error: unhandled exception: cannot unroll: neither a slice nor a dot expression 'items(a)' [ValueError]
You can skip reading this paragraph, I merely wrote it in case somebody wants to understand why I can't do this without unrolling the loop.
As stated, I'm working on an extension to prologue that provides an "Admin area" with simple CRUD-HTML pages for every model you register via a specific proc.
The API looks roughly like this:
import prologue
import someModuleWithTheCreatureType
proc addCrudRoutes*[T: Model](
app: var Prologue,
modelType: typedesc[T],
) =
... lots of complicated things
var app = newApp()
app.addCrudRoutes(Creature) # Creature as an example type
For that to work, I enforce that the types passed to addCrudRoutes are models where all foreign-key fields have an int-type and annotated with an fk-pragma (normally in norm the field would have the type of the related Model instead of int).
The pragma-value is the type of the related Model.
Example:
import norm/[model, pragmas]
type Campaign* = ref object of Model
name*: string
type Creature*= ref object of Model
name*: string = ""
campaign* {.fk: Campaign.}: int64
As part of one of the CRUD pages, I now want to query all foreign-key relationships of a given model.
Less abstractly: In a proc call of addCrudRoutes[Creature] I want to be able to fetch all entries of Campaign.
All I need for that is a "dummy-instance" of Campaign.
Something along the lines of new(Campaign) at runtime already suffices.
But I have to do that in a generic fashion since I can not know the specific Models addCrudRoutes will be called with, aka I do not know that it's specifically the Model Creature I'm getting..
My current approach is:
const a = @["la", "le", "li", "lo", "lu"]
proc recurse(a: static openArray; i: static int) =
echo a[i] is static
echo a[i]
when i < a.high:
a.recurse i + 1
a.recurse 0
Without fancy syntax:
import macros
macro unrollSeq(x: static seq[string], name, body: untyped) =
result = newStmtList()
for a in x:
result.add(newBlockStmt(newStmtList(
newConstStmt(name, newLit(a)),
copy body
)))
const a = @["la", "le", "li", "lo", "lu"]
unrollSeq(a, s):
echo s is static
echo s
Generalized, using a nested macro:
import macros
template unroll(iter, name0, body0: untyped): untyped =
macro unrollImpl(name, body) =
result = newStmtList()
for a in iter:
result.add(newBlockStmt(newStmtList(
newConstStmt(name, newLit(a)),
copy body
)))
unrollImpl(name0, body0)
const a = @["la", "le", "li", "lo", "lu"]
unroll(a, s):
echo s is static
echo s
Should work for any iterator over anything with a newLit defined.
I have been using this for years:
https://github.com/mratsim/constantine/blob/93654d5/helpers/static_for.nim
import std/macros
proc replaceNodes(ast: NimNode, what: NimNode, by: NimNode): NimNode =
# Replace "what" ident node by "by"
proc inspect(node: NimNode): NimNode =
case node.kind:
of {nnkIdent, nnkSym}:
if node.eqIdent(what):
return by
return node
of nnkEmpty:
return node
of nnkLiterals:
return node
else:
var rTree = node.kind.newTree()
for child in node:
rTree.add inspect(child)
return rTree
result = inspect(ast)
macro staticFor*(idx: untyped{nkIdent}, start, stopEx: static int, body: untyped): untyped =
result = newStmtList()
for i in start ..< stopEx:
result.add nnkBlockStmt.newTree(
ident("unrolledIter_" & $idx & $i),
body.replaceNodes(idx, newLit i)
)