I have a string hash function and I want static evaluation at compile time. This compiles :
proc hashcalc(s : string) : uint32 =
var hsh : uint32 = 0
for v in s : hsh *= HASHF; hsh += v.uint32
return hsh
template statichash(s : static[string]) : uint32 =
let x = static(hashcalc(s))
x
and (hopefully) it works as expected. No, my question: How can I achieve static evaluation of constant strings e.g.
echo hashcalc("whatever")
directly without the template? Is it possible to advise the compiler : "Don't call the function here, you don't need it at runtime, calculate at compile time? " Could not find a pragma e.g. {. evaluateatcompiletimeifpossible .}
The silly " let x = " construction was needed. When removed, the compiler complained ( colon expected ... )
Two options, depending on who should decide, the caller or the callee:
proc hashcalc(s: string): uint32 =
var hsh: uint32 = 0
for v in s:
hsh *= 5
hsh += v.uint32
return hsh
echo static(hashcalc("whatever"))
proc hashcalc2(s: string): uint32 {.compileTime.} =
var hsh: uint32 = 0
for v in s:
hsh *= 5
hsh += v.uint32
return hsh
echo hashcalc2("whatever")
the nph formatting, you get for free :)
Yeah. Thanks. This works for me :
proc qhash(s : string) : uint32 =
const HASHF = 147
var hav : uint32 = 0
for v in s : hav *= HASHF; hav += v.uint32
return hav
macro hasta( tx : untyped) : untyped =
if tx.kind == nnkStrLit :
let vint = qhash(tx.strVal)
result = newIntLitNode(vint.int)
else :
#let result = quote do :
# qhash(tx)
result = newIntLitNode(0)
The second branch in the macro is a fake one : I could not convince the compiler to accept the tx as a parameter.
macro that inspects the AST and checks for a string literal.
this approach fails to compute the hash for constants and other compile-time constructs like hashCalc(static("a" & "b")) .
I'm not understanding the need for the complexity of templates or macros here. Can't we just call the hashcalc in a const context to get both compiletime and not-runtime?
let HASHF = 5'u32 # unspecified
func hashcalc(s : string) : uint32 =
var hsh = 0'u32
for v in s:
hsh *= HASHF
hsh += v.uint32
return hsh
const iv = hashcalc("Hell! oh, whirrled.")
echo iv
Well, what I really want is something like this :
const HASHF = 147'u32
proc hashcalc(s: string): uint32 =
for v in s:
result = result * HASHF + v.uint32
macro myhope(appfun : typed, expr : typed) : untyped =
let mexpr = buildapp(appfun,expr) # apply appfun to expr
let (kind,preresult) = tryconsteval(mexpr) # evaluate at compiletime
# if it fails, kind will be nnkNone, otherwise kind will be a LitNode
if kind != nnkNone : result = preresult # expected result in a literal, e.g. IntLitNode
else : result = mexpr # gives back the runtime representation otherwise
template hasta (s : static[string]) : uint32 =
let x = myhope(hashcalc,s)
x
So, hasta becomes a template using macro myhope now. Is this achievable? Of course, buildapp and tryconsteval need to be some clever functions working on NimNodes. Someone has already done this? ( I dare to ask...)I'm not understanding the need
You can, but that introduces overhead for the caller to decide if the evaluation context should be compile-time or not. With {.compileTime.} and overloading on static (like hashcalc2) in my example, the caller always gets the compile-time version iff the parameters are decidable at compile time, without the extra const variable.
Well, if you want to evaluate everything at compile time if possible:
const HASHF = 147'u32
proc hashcalc0(s: string): uint32 =
for v in s:
result = result * HASHF + v.uint32
template hashcalc(s: string): uint32 =
when compiles(static hashcalc0(s)):
(static hashcalc0(s))
else:
hashcalc0(s)
echo hashcalc "a" & "b" # evals at comptime
proc foo(): string =
echo "comptime"
return "hi"
echo hashcalc foo() # evals at comptime (!)
var x = "hi"
echo hashcalc x # evals at runtime
But it's a recipe for making a mess, you'll end up running hashcalc at compile time when you mean to do it at runtime and vice versa. It's especially problematic if the expression has a side effect, like foo above...
Personally I'd just add a separate staticHashcalc proc (shorten to your liking) with a static string param, and call that when I expect hashcalc to run at comptime. If the compiler complains, add a static to the expression at the callsite. Fin.
I,m sorry, but the compiler does not accept static together with the compileTime pragma, at least not in my ( good old) Nim v1.6. So, function overloading does not work in this particular case. But, believe it or not, the following works :
import strutils
import std/macros
const HASHF : uint32 = 147
proc hashcalc(s : string) : uint32 =
echo s
var hav : uint32 = 0
for v in s : hav *= HASHF; hav += v.uint32
return hav
template hasta(s : static[string]) : uint32 =
let x = static(hashcalc(s))
x
proc hasta(s : string) : uint32 =
echo "seen by runtime"
echo s
var hav : uint32 = 0
for v in s : hav *= HASHF; hav += v.uint32
return hav
proc evidence(tx : string) =
const s = splitWhiteSpace("There is always hope with Nim")
echo s
let x = hasta s[5]
let y = hasta s[1]
let z = hasta tx
echo x," ",y," ",z
evidence("fantastic!")
I've now implemented a macro that performs both compile-time and runtime evaluation based on a "static" input. I'm using "quote do," but something's missing. I don't have access to the left side of the macro call. Silly workaround: I've introduced a new let-value. Technically, I want to call the continuation with either a value or an expression. Can any clever Nimmer give me a hint? Thanks.
Code:
import std/macros
proc concs (a : string, b : string) : string =
result = a & " " & b
proc getsval(a : Nimnode) : string =
result = concs(a.strVal,"compiletime")
macro stateval(a : typed) =
let lv = ident("localvalue")
if a.kind == nnkStrLit :
let va = getsval(a)
let vb = newStrLitNode(va)
result = quote do :
let `lv` = `vb`
else :
let vc = newStrLitNode("runtime")
result = quote do :
let `lv` = concs(`a`,`vc`)
proc run(s : string) =
block :
stateval (s & " alice")
echo localvalue
block :
stateval ("bob" & " alice")
echo localvalue
run("tom")
The hash macro is now ready. It performs a compiletime evaluation. If this isn't possible, it prepares a runtime evaluation.
import std/macros
import std/strutils
const HASHF : uint32 = 147
proc evhint32(s : string) : uint32 =
for v in s : result *= HASHF; result += v.uint32
macro hint32(a : typed ) : uint32 =
if a.kind == nnkStrLit :
let c = evhint32(a.strVal)
echo "compiletime value of (",a, ") is ", c.toHex
result = newLit(c)
else :
echo "runtime evaluation"
result = quote do :
evhint32(`a`)
proc run(s : string) =
echo hint32("alice" & " bob").toHex
echo hint32(s).toHex
run("alice" & " bob")
Thank you all for your help.
Nice progress! Eventually, you might run into:
proc run2(s: static string) =
echo hint32(s).toHex
run2("alice")
This is an example that could be evaluated at compile time but with the macro approach gets evaluated at runtime - this is because the "constant" value being passed hint32 it is no longer a string literal (nnkStrLit), but a compile-time string variable (which has a different node kind).
In fact, with the macro approach, you would have to enumerate all possible combinations of things that are evaluated at compile time, a near-impossible task since these include function calls - macros are difficult to write and maintain for this reason, and the {.compileTime.}-based approaches deal with this and other cases you might encounter.
Nim does not accept static[T] with {.compileTime.} combined. You can overload the function with static , but the function call will be generated anyways. To avoid this, {.inline.} could help. I have the template alternative to offer. The template overloads the function with static[T] and the compiler accepts it. See
import std/macros
import std/strutils
const HASHF : uint32 = 147
# the discarded function does not work
discard """
proc evhint32(s : static[string]) : uint32 {.compileTime.} =
for v in s : result *= HASHF; result += v.uint32
"""
#overloaded function, overloaded as a template, follows :
template evhint32(s : static[string]) : uint32 =
echo "static ev. "
let x = static(evhint32(s))
x
proc evhint32(s : string) : uint32 =
for v in s : result *= HASHF; result += v.uint32
macro hint32(a : typed ) : uint32 =
if a.kind == nnkStrLit :
let c = evhint32(a.strVal)
echo "compiletime value of (",a, ") is ", c.toHex
result = newLit(c)
else :
echo "runtime evaluation"
result = quote do :
evhint32(`a`)
proc run(s : static[string], nons : string) =
echo hint32("alice" & " bob").toHex
echo hint32(s).toHex
echo evhint32(s).toHex
echo evhint32(nons).toHex
run("alice" & " bob","tom")
but the function call will be generated anyways.
How are you checking this?
In general, one way is to look at the C code - here's the C code for my example - you can see that when called with a compile-time constant, it computes the hash at compile-time, and when called with a runtime value, it does it at runtime.
colontmpD_ = _ZN7dollars7dollar_E6uInt32(((NU32)11313694));
if (NIM_UNLIKELY(*nimErr_)) goto LA1_;
T2_[0] = colontmpD_;
echoBinSafe(T2_, 1);
colontmpD__2 = _ZN7dollars7dollar_E6uInt32(((NU32)11313694));
if (NIM_UNLIKELY(*nimErr_)) goto LA1_;
T3_[0] = colontmpD__2;
echoBinSafe(T3_, 1);
v__testit_u30 = TM__8qVWOq10n9bbKER8oGDqhuw_3;
T5_ = (NU32)0;
T5_ = _ZN6testit9hashcalc2E6string(v__testit_u30);
if (NIM_UNLIKELY(*nimErr_)) goto LA1_;
colontmpD__3 = _ZN7dollars7dollar_E6uInt32(T5_);
A bit tedious to add those overload wrappers indeed, so if a macro is to be written, it should be to generate the two overloads.
Your Code, @Arne :
proc hashcalc(s: string): uint32 =
var hsh: uint32 = 0
for v in s:
hsh *= 5
hsh += v.uint32
return hsh
echo static(hashcalc("whatever"))
proc hashcalc2(s: static string): uint32 {.compileTime.} =
hashcalc(s)
proc hashcalc2(s: string): uint32 =
hashcalc(s)
echo hashcalc2("whatever")
var v = "test"
echo hashcalc2(v)
and the compiler reports this :
/Env/limlex/bforum.nim(17, 6) Error: request to generate code for .compileTime proc: hashcalc2
IDK if there is a special compiler flag to resolve the issue.