Author: | Don-Duong Quach |
---|---|
License: | MIT |
Version: | 0.18.0 |
This module has a macro gen which implements a small DSL for inline code generation. gen acts like an anonymous, dirty, template/macro for generating lots of similar repetitive code.
Arguments are subsituted into a body of code using the following rules.
Rules
genit processes nnkAccQuoted NimNodes, i.e. indentifiers surrounded by backticks, to generate substitutions into blocks of code. If the resulting accQuoted identifier is valid without backticks, then the backticks are removed. The accQuoted identifiers are also flattened if they're nested.
Named Arguments
Named arguments are local to the gen call and substituted into the body.
Example:
import src/genit gen(r = Radial, c = Component): # produces: var `r c`: float # var RadialComponent: float doAssert typeof(RadialComponent) is float
Unnamed Arguments
Unnamed arguments repeat the body and replaces the item identifier, it, with the argument.
Example:
import src/genit gen red, green, blue: # produces: var `it Val`: int # var # redVal: int # greenVal: int # blueVal: int doAssert typeof(redVal) is int doAssert typeof(greenVal) is int doAssert typeof(blueVal) is int
Global Named Arguments
Global named arguments can be assigned with the := operator.
Example:
import src/genit #[ # runnableExamples doesn't like `:=`, but the following works gen(c := Component) gen a: var `it c`: int = 1 gen b: var `c it`: int = 2 doAssert aComponent == 1 doAssert ComponentB == 2 gen(c := Comp) gen d: var `it c`:int = 3 doAssert dComp == 3 ]# discard
Index
The index for unnamed arguments can be accessed using the % operator.
Example:
import src/genit gen red, green, blue: # produces var it = %it # var # red = 0 # green = 1 # bluel = 2 doAssert red == 0 doAssert green == 1 doAssert blue == 2
Rename it
The item identifier, it, can be renamed using a named argument.
Example:
import src/genit gen(it = this, a, b): # produces: var this = 1 # var # a = 1 # b = 1 doAssert a == 1 doAssert b == 1
Tuple Arguments
Each unnamed argument can be a tuple, and its parts can be accessed by indexing like a regular tuple. If the indexed tuple is an l-value, it must be surrounded by accent quotes to be legal Nim.
If an unnnamed argument is indexed but it is not a tuple, it will be "duplicated", so you can mix tuple and non-tuple arguments.
If the tuple index is part of a larger expression, e.g. dot expression, accent quoting will run genit's parser on it. Accent quoting will also interrupt multiple indexing.
Example:
import src/genit gen (first, 1), (second, 2), (third, 3): # produces: var `it[0]` = it[1] # var # first = 1 # second = 2 # third = 3 doAssert first == 1 doAssert second == 2 doAssert third == 3 gen w, a, s, d, (shift, run): let `^it[0]` = $$it[1] doAssert W == "w" doAssert Shift == "run" type State = object foo: int bar: int var s = State(foo: 10, bar: 100) gen (val, foo), bar: let `it[0]` = s.`it[1]` # accent quote it[1] to bypass nim parsing doAssert val == 10 doAssert bar == 100 # multiple tuple indexing with duplication var a = [1, 2, 3] var b = ['a', 'b', 'c'] gen (ap, a), b: let `it[0] t` = it[1][0].addr doAssert apt[] == a doAssert bt[] == b # interrupt tuple indexing gen (ap, a), b: let `it[0] p` = `it[1]`[0].addr doAssert app[] == 1 doAssert bp[] == 'a'
Stringify
The $$, stringify, operator turns an identifier into a string.
Example:
import src/genit gen(l = Label, name, age): # produces var `it l` = $$it & $$l # var # nameLabel = "nameLabel" # ageLabel = "ageLabel" doAssert nameLabel == "nameLabel" doAssert ageLabel == "ageLabel"
Lowercase
The - operator will lowercase the first letter of an identifier.
Example:
import src/genit gen Red, Green, Blue: var it = $$ -it doAssert Red == "red" doAssert Green == "green" doAssert Blue == "blue"
Uppercase
The / operator will capitalize all the letters of an identifier.
Example:
import src/genit gen red, green, blue: var `^it` = $$ /it doAssert Red == "RED" doAssert Green == "GREEN" doAssert Blue == "BLUE"
Capitalize
The ^ operator will capitalize the first letter of an identifier.
Example:
import src/genit gen red, green, blue: var `it` = $$ ^it doAssert red == "Red" doAssert green == "Green" doAssert blue == "Blue"
Expansion
The ~ operator will expand an array, seq, or tuple, when in the body.
Example:
import src/genit type Color = enum none = 0 red = 1 green = 2 blue = 3 let color = blue gen(c = [none, red, green, blue]): var varVal = gen(~c): case color: of it: %it let letVal = gen(~c): case varVal: of %it: it else: none doAssert varVal == 3 doAssert letVal == blue
Type Fields
Passing in a type or symbol prefixed with the fields operator, +, will pass in the type's fields as unnamed items. Internally this uses genWith and supports Enum, Object, Tuple, Array, and Range types.
Example:
import src/genit type ColorIndex = enum none = -1 red = 1 green = 2 blue = 3 gen +ColorIndex: var `^it[0]` = ($$it[0], it[1]) doAssert None == ("none", -1) doAssert Red == ("red", 1) doAssert Green == ("green", 2) doAssert Blue == ("blue", 3) type Color = object r, g, b: uint8 var c: Color gen +Color: c.it = 255'u8 doAssert c.r == 255'u8 doAssert c.g == 255'u8 doAssert c.b == 255'u8 type Vec3 = tuple[x, y: float32, z: float32] var l = (10f, 20f, 30f) # tuple with no named fields var r:Vec3 = (0f, 0f, 0f) gen +l: # tuple constructor returns 0, 1, 2 r[it] = l[it] doAssert r.x == 10f doAssert r.z == 30f r.x = 1f r.z = 3f var res:Vec3 gen +r: # Vec3 res.it = r.it doAssert res.x == 1f doAssert res.z == 3f var v4:array[4, float32] = [1'f32, 0.5, 0.25, 1] var v3:array[1..3, byte] gen +v3: v3[it] = (v4[it - 1] * 255.99f).byte doAssert v3[1] == 255 doAssert v3[2] == 127 doAssert v3[3] == 63
Multiple Statements and Nesting
When using multiple statements in a gen macro, each statement will be produced at least once even if there are no arguments. And each statement will be produced once for each unnamed argument using the item identifier, unless its a special construct like a case statement or type definition. It's better to split the gen calls for clarity if possible.
Example:
import src/genit gen(c = Component): gen(red, green, blue): type RGB = object `it c`: float var color = RGB(`red c`:1.0, `green c`:0.0, `blue c`:1.0) doAssert color.redComponent == 1
Using with Templates
We can give templates macro-like powers without having to write macros! Set the return type to untyped if you're going to use the template in an expression. Mark the template with the {.dirty.} pragma to avoid early binding of symbols.
Example:
import src/genit template joinSymToString(arg1, arg2: untyped): untyped {.dirty.} = gen (arg1, arg2): $$it[0] & " " & $$it[1] let msg:string = joinSymToString(hi, [5]) doAssert msg == "hi [5]"
Special Constructs
Some Nim constructs like case statements and type definitions: objects, enum, tuple, etc. need special handling. Some might not be implemented currently.
Example:
import src/genit gen Free, Carried, FlyingUp, FlyingBack: type BoxState = enum `bs it` doAssert ord(bsFlyingBack) == 3 type Color = enum Red Green Blue No var color1 = Green proc getColor(color: Color): (int, int, int) = gen (Red, (255, 0, 0)), (Green, (0, 255, 0)), (Blue, (0, 0, 255)): case color: of `it[0]`: it[1] else: (0, 0, 0) var index1 = getColor(color1) doAssert index1 == (0, 255, 0) gen(c = Component): gen(red, green, blue): type RGB = object `it c`: float var color = RGB(`red c`:1.0, `green c`:0.0, `blue c`:1.0) doAssert color.redComponent == 1
Debugging with print
To see what gen produces, wrap it with the print macro.
Example:
import src/genit var sum = 0 print: gen(1, 2, 3): sum += it doAssert sum == 6 gen(1, 2, 3): sum += it doAssert sum == 12
Macros
macro debug(body: untyped): untyped
macro gen(va: varargs[untyped]): untyped
- Implements the DSL.
macro genWith(x: typed; args: varargs[untyped]): untyped
- Calls gen with the fields of the variable or type passed in; supports enum, object, and tuple. Used by the fields operator, +, with gen on a symbol.
macro print(body: untyped): untyped