echo "n" echo "proc string_to_{name}(rname: string): {name} =n const map: seq[progress_specenum_el] = @[repr({elements})]n var b: {name} = low(b)n for a in map:n if a.name == rname:n return bn b=b.succ()n return BAD"
Your first file has an extra space on each echo line after the for loop. Remove that and your program will compile.
However, that is not how you write a macro. You need to generate NimNodes instead of just printing to the screen. I can give an example when I get to my computer.
Hmm, looks like you have quite a bit to understand.
Your macro line is also indented too much, it should be on the same indentation level as the type declaration.
This line:
echo """{name} = enum\n "
needs to be this:
echo "{name} = enum\n "
because Nim will get confused by the first two quotes and treat the rest of the file as a string. See here for more info.
Your are also trying to write string interpolation without the strformat module imported and the fmt call before it. For example, the above should be:
#top of file
import strformat
# ... your other code
echo fmt"{name} = enum" # the \n is unnecessary because echo already ends with a newline by default
# ... your other code
This is because Nim doesn't support string interpolation this way by default and needs a template called fmt that implements it.
So after all those your code then becomes (with some nice formatting for ease of reading):
import strformat # This needs to be imported to use fmt"{variable}" in strings
type
progress_specenum_el = object
name: cstring
value: string
# This needs to be indented and have a star (*) after the macro name to export it to another module
macro generate_specenum*(name: string; elements: openarray[progress_specenum_el]): untyped =
echo fmt"{name} = enum"
echo "BAD,"
for a in elements:
echo fmt"{a.value},"
echo "\n"
echo fmt"proc string_to_{name}(rname: string): {name} ="
echo fmt" const map: seq[progress_specenum_el] = @[repr({elements})]"
echo fmt" var b: {name} = low(b)"
echo fmt" for a in map:"
echo fmt" if a.name == rname:"
echo fmt" return b"
echo fmt" b=b.succ()"
echo fmt" return BAD"
But, this still will not compile because macros interpret some arguments as NimNode instead of what they actually are (not sure if it is a bug or feature), so your openarray[progress_specenum_el] just becomes a NimNode and your for loop cannot iterate over it.
And after that is fixed, your code will run but only print out what you want it to do (and not actually generate anything) because echo simply prints values to the console.
I was trying to interpret what you are trying to do, and it looks like you are trying to make a macro that generates an enum and a proc that uses that enum. That can definitely be done but requires a greater understanding of the language and how macros work. I could give you a solution, but that would hardly help you learn.
A good starting point for macros might be here, since it goes over some simple macros and how to generate code. Feel free to update your code based on this, and post here if you have more trouble finding a solution.
Thanks a lot. I was trying to write macro that generates: enum type and procedure to map value of this enum to related string. I decided to port game based on Freeciv. I'm only developer of this game and introducing a new features takes too long. Freeciv (and my game) is write in C and uses x-includes to generate code on compile time by macros. Given x-includes header takes as „argument” definitions of enum values and related strings by checking in the chain next enum value and related string is defined. It request to name definition in strict way, so this is good example:
#define SPECENUM_VAL0 PLAYER_GREEN
#define SPECENUM_STR0 "Green"
#define SPECENUM_VAL1 PLAYER_RED
#define SPECENUM_STR1 "Red"
.
.
.
#define SPECENUM_VALN PLAYER_COLOR_n
#define SPECENUM_STRN "Some color"
#include "genereate_specenum.h"
I need to understood how macros in nim works to do similar thinks (generate_specenum.h generates enum type and functions translates enum value to related string and vice-versa). I found nim function, which converts enum value to string, but it do similar thing to nim's quote (or quoteStr - I don't remember) or hash/double hash (I don't remember) operator in C preprocesor.I think about using inheritance to achieve this. Nim, as pascal, delivers many method to use countable types, like splices, etc.
I will write type, which contains seq of strings and I will implement method to get string from this sequence by index. Other method will get index of string. One question is: can I cast enum to integer and make integer to be automatically casted to some enum (compiler should cast this automatically). Is there any generic enum type? Exists there some method like get next value of enum?
Enums can be used in the way you describe:
type
MyEnum = enum
VAL1
VAL2
VAL3
echo VAL1
echo VAL1.int
echo 2.MyEnum
for i in VAL1 .. VAL3:
echo i
# Get upper and lower values to iterate
for i in MyEnum.low .. MyEnum.high:
echo i
for i in MyEnum.VAL2 .. MyEnum.high:
echo i
# Can also iterate over the whole thing like this
for i in MyEnum:
echo i
I think this source is outdated: https://flenniken.net/blog/nim-macros/ Compiler complains there's no children field in nim node. When look at official documentation, I found sons field. Should I use sons? Also, I look inside some system file of nim and there's NimNode declared as an reference to Node. Should I use rectangle operator to access NimNode fields? My code now looks like this:
macro get_decl_name(decl: untyped): untyped =
for a in decl.children:
if a is nnkTypeDef:
for c in a.children:
if c is IdentNode:
return c
type
progress_specenum = object
map: seq[string]
var generated: string
macro generate_specenum*(names_of_values: static[seq[string]]; decl: untyped): untyped =
var name = get_decl_name(decl)
var code = fmt"var\n progress_specenum_{name}: progress_specenum = progress_specenum(map: name_of_values)\n"
code &= fmt"proc get_str_repr_of_specenum_{name}(val: {name}): string =\n "
code &= fmt"for a in progress_specenum_{name}.map:\n "
code &= fmt"if a == val: return a\n "
code &= fmt"return nil"
code &= fmt"proc get_val_of_specenum_{name}_from_str(val: string): {name} =\n "
code &= fmt"var curr: {name}\n "
code &= fmt"for a in progress_specenum_{name}.map:\n "
code &= fmt"if a == val: return curr\n "
code &= fmt"succ(curr)\n "
code &= fmt"return BAD"
generated &= code
macro flush_ruleset_rel_code*() =
result = parseStmt(generated)
generated = ""
But compiler complains there's no field children inside NimNode.
Thanks a lot. I was trying to write macro that generates: enum type and procedure to map value of this enum to related string.
It's... a native feature of Nim: https://nim-lang.org/docs/manual.html#types-enumeration-types
This is definitely not how you write a macro :P You should take a look at the Macro tutorial for a hands-on experience with macros.
More learning resources can be found at the learning portal.
Solution was simple and didn't require macro, but I suggest to left this thread open, so we could continue.
type
progress_specenum = object
names: seq[cstring]
progress_specenum_val[eT] = object
meta: ref progress_specenum
val: eT
proc translate_to_enum(a: var progress_specenum; s: cstring): cint = a.names.find(s).cint
proc translate_to_string[eT](a: var progress_specenum_val[eT]) :cstring = a.meta.names[a.val.ord]
I must probably cast type returned from translate_to_enum. Can I place enum type definition inside class definition (like in some languages similar to Java) ?