src/genit

Author:Don-Duong Quach
License:MIT
Version:0.18.0

Source

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.