Last active
February 18, 2024 21:23
-
-
Save xealits/2c7f1468cff736c129b251317aa2bbda to your computer and use it in GitHub Desktop.
Basic macro in NIM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[ | |
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.