Skip to content

Instantly share code, notes, and snippets.

@xealits
Last active February 18, 2024 21:23
Show Gist options
  • Save xealits/2c7f1468cff736c129b251317aa2bbda to your computer and use it in GitHub Desktop.
Save xealits/2c7f1468cff736c129b251317aa2bbda to your computer and use it in GitHub Desktop.
Basic macro in NIM
#[
# the example is from https://peterme.net/metaprogramming-and-read-and-maintainability-in-nim.html
# plus I used `quote` instead of direct tree nodes
# it almost works the same way
# except, it seems you cannot declare an empty `enum` under `quote`
]#
import math, strutils
import macros
#import typetraits
#import std/strformat
macro defineCommands(enumName, docarrayName, runnerName,
definitions: untyped): untyped =
var
enumDef = quote do:
type
`enumName` = enum Example = "example" # <-- apparently you cannot declare an empty enum
docstrings = quote do:
const `docarrayName`: array[`enumName`, string] = [
"Docstring for the example enum, just a dummy to match the enum of commands, since the enum cannot be empty"
]
templateArgument = newIdentNode("command")
caseSwitch = quote do:
#case parseEnum[`enumName`](case_arg): # <-- can you just plug case_arg here and below? or it create nodes for both?
case parseEnum[`enumName`](`templateArgument`):
of Example: discard
for i in countup(0, definitions.len - 1, 2):
let
enumInfo = definitions[i]
commandInfo = definitions[i+1]
enumDef[0][2].add nnkEnumFieldDef.newtree(enumInfo[0], enumInfo[1])
docstrings[0][2].add commandInfo[0]
caseSwitch.add nnkOfBranch.newTree(
enumInfo[0],
commandInfo[1])
result = quote do:
`enumDef`
`docstrings`
#template `runnerName`(case_arg: untyped): untyped =
template `runnerName`(`templateArgument`: untyped): untyped =
`caseSwitch`
echo result.repr
# First create a simple "stack" implementation
var stack: seq[float]
proc push[T](stack: var seq[T], value: T) =
stack.add value
proc pop[T](stack: var seq[T]): T =
result = stack[^1]
stack.setLen(stack.len - 1)
# Convenience template to execute an operation over two operands from the stack
template execute[T](stack: var seq[T], operation: untyped): untyped {.dirty.} =
let
a = stack[^1]
b = stack[^2]
stack.setLen(stack.len - 2)
stack.push(operation)
# Then define all our commands using our macro
defineCommands(Commands, docstrings, runCommand):
Plus = "+"; "Adds two numbers":
stack.execute(a + b)
Minus = "-"; "Subtract two numbers":
stack.execute(b - a)
Multiply = "*"; "Multiplies two numbers":
stack.execute(a * b)
Divide = "/"; "Divides two numbers":
stack.execute(b / a)
Pop = "pop"; "Pops a number off the stack":
discard stack.pop
StackSwap = "swap"; "Swaps the two bottom elements on the stack":
let
a = stack[^1]
b = stack[^2]
stack[^1] = b
stack[^2] = a
StackRotate = "rot"; "Rotates the stack one level":
stack.insert(stack.pop, 0)
Help = "help"; "Lists all the commands with documentation":
echo "Commands:"
for command in Commands:
echo "\t", command, "\t", docstrings[command]
Exit = "exit"; "Exits the program":
quit 0
# Program main loop, read input from stdin, run our template to parse the
# command and run the corresponding operation. if that fails try to push it as
# a number. Print out our "stack" for every iteration of the loop
while true:
for command in stdin.readLine.split(" "):
try:
runCommand(command)
except:
stack.push parseFloat(command)
echo stack, " - ", command
@xealits
Copy link
Author

xealits commented Feb 18, 2024

That's a nice example from Peter. Not only it is a practical and basic pattern for metaprograming, it may lead to a nice implementation of SoA data structures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment