Skip to content

Code snippets

Jason Beetham edited this page Dec 30, 2024 · 29 revisions

24 bit integers

A simple way of doing 24 bit integers.

import std/strutils
type int24 {.packed.} = object
  data {.bitsize: 24.}: int32

proc `+`*(a, b: int24): int24 = int24(data: a.data + b.data)
proc `-`*(a, b: int24): int24 = int24(data: a.data - b.data)
proc `div`*(a, b: int24): int24 = int24(data: a.data div b.data)
proc `*`*(a, b: int24): int24 = int24(data: a.data * b.data)
proc `>`*(a, b: int24): bool = a.data > b.data


proc `$`(i: int24): string = $i.data

proc low(_: typedesc[int24]): int = -8388608
proc high(_: typedesc[int24]): int = 8388607

proc toInt24(i: SomeNumber): int24 =
  let u32 = int32(i)
  if u32 notin int32(int24.low)..int32(int24.high):
    raise (ref RangeDefect)(msg: $u32 & " not in int24 range.")
  int24(data: u32)

proc `'i24`(s: static string): int24 =
  when parseInt(s) notin int24.low .. int24.high:
    {.error: $i & " not in int24 range.".}
  int24(data: int32 parseInt(s))


var a = 300'i24
assert sizeof(a) == 3
echo a + 3'i24

Force alignment to a size

import std/macros

macro offsetOfDot(t: typed): untyped = newLit t[^1].getOffset

proc isAligned(t: typedesc[object]): bool =
  for x in t().fields:
    when x.offsetOfDot() mod 64 != 0:
      return false
  true
type Aligned64 = concept type A
  A is object
  isAligned(A)


proc doThing(a: Aligned64) = 
  echo a


type MyType = object
  a: int
  b{.align: 64.}: int

doThing(MyType())

Todo

import std/[tables, strutils]

var todos{.compileTime.}: Table[string, seq[string]]

template todo*(msg: static string) =
  static:
    const info = instantiationInfo()
    discard todos.hasKeyOrPut("unlabelled", newSeq[string]())
    todos["unlabelled"].add "$#($#,$#): $#" % [info.fileName, $info.line, $info.column, msg]
  {.warning: "Todo: " & msg.}
  
template todo*(label, msg: static string) =
  static:
    const info = instantiationInfo()
    discard todos.hasKeyOrPut(label, newSeq[string]())
    todos[label].add "$#($#,$#): $#" % [info.fileName, $info.line, $info.column, msg]
  {.warning: "Todo: " & msg.}
  
  
proc emitTodos(): string {.compileTime.} =
  for entry, msgs in todos:
    result.add entry
    result.add "\n"
    for msg in msgs:
      result.add "  "
      result.add msg
      result.add "\n"

      

proc test() = todo("unimplemented"): "implement this"
proc test2() = todo("Meeeeh"): "implement this"
proc test3() = todo("Meeeeh"): "Yea really"

todo "Do not forget the whole logic"

      

      
echo emitTodos()

Inheritance overridden procedures

type
  Base = object of RootObj
  ChildA = object of Base
    x, y: int
  ChildB = object of ChildA
    s: string
  ChildC = object of Base
    z: float
  

proc doThing[T: Base](val: T) = echo val

Base().doThing()
ChildA().doThing()
ChildB().doThing()
ChildC().doThing()


proc doThing(c: ChildA) = echo c.x * c.y
proc doThing(c: ChildB) = echo c.s
proc doThing(c: ChildC) =
  doThing(Base(c)) # call the original procedure
  echo c.z

Base().doThing()
ChildA().doThing()
ChildB().doThing()
ChildC().doThing()

Using generic interfaces

type Inputable = concept type I
  getInputDefault(I) is I

proc promptInput[T: Inputable](msg: string, default: T): T =
  # Check for input and if none return default
  echo default

proc promptInput[T: Inputable](msg: string): T =
  mixin getInputDefault
  promptInput(msg, getInputDefault(T))


proc getInputDefault(typ: typedesc[int or float]): typ = default(typ)
proc getInputDefault(typ: typedesc[string]): typ = "hello"


discard promptInput[string]("hello")
# Super boiled down example.
type
  UserPluginBase = ref object of RootObj
    # Base fields used by the implementation
    apiPlugin: ApiPlugin
    someField: int 

  ApiPlugin = ref object # ptr object for you
    userData: pointer
    process: proc(apiPlugin: ApiPlugin) {.cdecl.} 

  ApiEntry = object
    createPlugin: proc(): ApiPlugin {.cdecl.}

# Api destroy function
proc destroy[T](plugin: ApiPlugin) {.cdecl.} =
  mixin destroy
  let userPlugin = cast[T](plugin.userData)
  userPlugin.destroy() # User defined destroy function
  GcUnref(userPlugin)

# Api process function
proc process[T](plugin: ApiPlugin) {.cdecl.} =
  mixin process
  let userPlugin = cast[T](plugin.userData)
  userPlugin.process() # User defined process function

proc createPlugin[T](): ApiPlugin{.cdecl.} =
  mixin setupPlugin
  var userPlugin = T()
  GcRef(userPlugin)
  setUpPlugin(userPlugin)
  userPlugin.apiPlugin = ApiPlugin(
    userData: cast[pointer](userPlugin),
    process: process[T],
  )
  userPlugin.apiPlugin

proc init(_: typedesc[ApiEntry], T: typedesc): ApiEntry =
  ApiEntry(createPlugin: createPlugin[T])

type 
  UserPlugin = ref object of UserPluginBase
    # Any other fields the user wants
    someUserField: int

proc setupPlugin(up: UserPlugin) =
  up.someUserField = 300

proc process(up: UserPlugin) = echo up.someUserField

var entry = ApiEntry.init(UserPlugin)
let plug = entry.createPlugin()
plug.process(plug)

Unify field access on object variant

type MyType = object
  case kind: bool
  of true:
    aVal: int
    modifier: int
  of false:
    bVal: int
    name: string
    

proc `a`(myType: MyType): int =
  case myType.kind
  of true:
    myType.aVal
  of false:
    myType.bVal

proc `a`(myType: var MyType): var int =
  case myType.kind
  of true:
    result = myType.aVal
  of false:
    result = myType.bVal

proc `a=`(myType: var MyType, val: int) =
  case myType.kind
  of true:
    myType.aVal = val
  of false:
    myType.bVal = val

Procedure based introspection

import std/typetraits

proc lastType[T](a: openarray[T]): auto =
  when T is (seq or array):
    default(T).lastType()
  else:
    default(T)

type
  BoxAtom = string or int64 or float
  BoxConcept = concept bc
    lastType(bc) is BoxAtom
  Boxable = BoxAtom or BoxConcept

proc flatten[T](box: Boxable, result: var seq[T]) =
  for entry in box.items:
    when typeof(box[0]) is BoxAtom:
      result.add entry
    else:
      entry.flatten(result)
    
  
proc flatten(box: Boxable): auto =
  result = newSeq[typeof(box.lastType)]()
  box.flatten(result)

var a = @[@[@[10i64, 20, 30], @[1i64, 2, 2]], @[@[1000i64, 3000, 4, 5]]]
echo a.flatten()
proc seqDepth[T: seq](n: T, val: var int) =
  when n[0] is seq:
    inc val
    seqDepth(default(typeof(n[0])), val)
    

proc seqDepth[T: seq](n: typedesc[T]): int =
  inc result
  when (default(T))[0] is seq:
    seqDepth(default(T), result)  

type
  NSeq[Size: static int, T] {.explain.} = concept n, type N
    seqDepth(N) == Size
  

proc get[n: static int; T](sequence: NSeq[n, T], indices: array[n, int]): auto = # Cannot use T must use auto, probably compiler bug
  discard
  


@[10].get([1])
@[@[10]].get([1, 1])
@[@[@[10]]].get([1, 1, 1])
proc rootType[T: not seq](_: typedesc[T]): T = discard

proc rootType[T](_: typedesc[seq[T]]): auto =
  rootType T
    

proc flatten[T, Y](s: seq[T], result: var seq[Y]) =
  when T is Y:
    result.add s
  else:
    for val in s:
      flatten(val, result)

proc flatten[T](s: seq[T]): auto =
  result = newSeq[typeof rootType(seq[T])]()
  flatten(s, result)
  

var a = @[@[@[10, 20, 30], @[4, 5], @[300, 40, 50]], @[@[3, 9, 12, 300]]]
echo a.flatten()

Get proc name

import std/macros

macro getOwner(o: typed): untyped =
  let prc = o.owner
  if not prc.owner.eqIdent "unknown":
    nnkPar.newTree(newLit $prc.owner, newLit $prc)
  else:
    nnkPar.newTree(newLit $prc, newLit"")

template moduleProcName(): untyped =
  type A = distinct void
  getOwner(A)


proc doThing() =
  echo moduleProcName

doThing()
echo moduleProcName

Check if top level

import std/macros

macro isTopLevelImpl(o: typed): untyped =
  newLit o.owner.symKind == nskModule

template isTopLevel(): bool =
  type A = distinct void
  isTopLevelImpl(A)

type MyType = object
when isTopLevel():
  export MyType

Iterate a group of iterables together

import std/macros

macro joinIters(body: ForLoopStmt) =
  result = newStmtList()
  for x in body[^2][1..^1]:
    result.add nnkForStmt.newTree(body[0..^3] & @[x] & body[^1])
  result = newBlockStmt(ident"joinLoop", result)

iterator myIter: int = yield 132

for x in joinIters([10, 20, 30],  0..4, myIter()):
  echo x

Enum Indexed Array

type MyEnum = enum A, B, C
var a: array[MyEnum, int]
a[A] = 300
a[B] = 400
a[C] = 500

Check if a module has a proc

template hasProc(module, name: untyped, typ: typedesc[proc]): untyped = compiles(typ(module.name))

import std/strutils

static:
  assert hasProc(strutils, contains, proc(a, b: string): bool)
  assert not hasProc(strutils, contains, proc(a, b: int))

Delayed imports

# module b
import a
proc doThing*(myType: MyType) = echo myType.x * myType.y

# module a
type MyType* = object
  x*, y*: int
import b

doThing(MyType(x: 300, y: 200))

Extracting comments

import std/macros
proc hello() =
  ## prints a hello world

proc doThing[T]() =
  ## Does other stuff
  ## Maybe even flies
  
macro getHelp(t: proc): untyped =
  var comments: string
  let impl = t.getImpl
  if impl[^1].kind == nnkCommentStmt:
    comments = impl[^1].strVal
  else:
    for x in t.getImpl[^1]:
      if x.kind == nnkCommentStmt:
        comments.add x.strVal & "\n"
      else:
        break
  newCall("echo", newLit(comments))

getHelp(hello)
getHelp(doThing[int])

Unpack objects with optional fields

import std/macros

macro makeTypedesc(s: static seq[string]): untyped =
  result = nnkTupleConstr.newTree()
  for x in s:
    result.add parseExpr(x)

proc getUnpackTypedescs(val: typedesc[object], fieldsToUnpack: static openarray[string]): seq[string] {.compileTime.} =
  for name, field in val().fieldPairs:
    if name in fieldsToUnpack:
      result.add $typeof(field)

proc getUnpackTypedescs(val: typedesc[object]): seq[string] {.compileTime.} =
  for name, field in val().fieldPairs:
    result.add $typeof(field)

var added {.compileTime.}: int
proc unpack(val: object, fieldsToUnpack: static openarray[string] = @[]): auto =
  result =
    when fieldsToUnpack.len > 0:
      default makeTypeDesc getUnpackTypedescs(typeof(val), fieldsToUnpack)
    else:
      default makeTypeDesc static(getUnpackTypeDescs(typeof(val)))
  
  for name, field in val.fieldPairs:
    when fieldsToUnpack.len == 0:
      result[added] = field
      static: inc added
    elif name in fieldsToUnpack:
      result[added] = field
      static: inc added
  static: added = 0
      
  

import std/tables
type Note = object
  title, msg: string
  b: seq[string]
  c: Table[float, seq[(int, float)]]
  


var a = Note(title: "Hello", msg: "World")
echo a.unpack()
echo a.unpack(["msg"])
echo a.unpack(["msg", "b", "c"])

Detect variants without macros

{.warningAsError[CaseTransition]: on.}
proc selfAssign[T](v: var T) = v = v

proc isVariant(Obj: typedesc[object]): bool =
  var obj = default Obj
  for name, field in obj.fieldpairs:
    when not compiles(selfAssign field):
      return true
    
type Variant = concept type V
  isVariant(V)

type
  MyType = object
    case x: bool
    of true:
      a: int
    else:
      b: float
      
  MyType2 = object
    x, y, z: int

assert MyType is Variant
assert MyType2 isnot Variant
import std/json
assert JsonNodeObj is Variant

Basic result type

import std/macros
type Error[T, Y] = object
  case isErr: bool
  of true:
    errVal: T
  of false:
    val: Y

type UnhandledError = object of CatchableError
    
proc isSuccess[T, Y](err: Error[T, Y]): bool = not err.isErr    

macro ownedByProc(sym: typed): untyped =
  newLit sym.owner.kind == nnkSym and sym.owner.symKind == nskProc

template error[T, Y](result: var Error[T, Y], err: T) =
  result = Error[T, Y](isErr: true, errVal: err)
  proc dummy = discard
  when ownedByProc(dummy):
    return
  

template success[T, Y](result: var Error[T, Y], goodVal: Y) =
  result = Error[T, Y](isErr: false, val: goodVal)
  proc dummy = discard
  when ownedByProc(dummy):
    return

import std/random
proc doThing(): Error[string, int] =
  let val = rand(100)
  if val > 50:
    result.error "Got random chance: " & $val
    echo "Not Printed"
  else:
    result.success val
    echo "Not Printed"

randomize()    
for x in 0..10:
  let a = doThing()
  if a.isSuccess:
    echo "Got a val: ", $a.val
  else:
    echo a.errVal

Get a proc symbol easier

import std/[macros, genasts]

macro procSym(call: typed): untyped = call[0]

macro procOf*(theProcCall: untyped): untyped =
  result = newCall(theProcCall[0])
  for x in theProcCall[1..^1]:
    if x.kind == nnkVarTy:
      let name = genSym(nskVar, "argument")
      result.add:
        genast(typ = x[^1]):
          (
            var argument = default(typ)
            typ
          )
    else:
      result.add newCall("default", x)
  result = newCall(bindSym"procSym", result)
      

      
import std/strutils

var myProc = procOf contains(string, string) 
assert myProc("a", "a")

Convert Tuple to or'd typeclass

import std/[macros, genasts]

macro anyOf(t: typedesc[tuple]): untyped =
  let impl =
    if t.kind == nnkSym:
      t.getImpl
    else:
      t
  result = impl[0]
  for x in impl[1..^1]:
    result = infix(newCall("typeof", result), "or",  newCall("typeof", x))
  result = newCall("typeof", result)  
  
proc doThing(a: anyOf (int, float, string, bool)) = echo a

doThing(100)
doThing(100d)
doThing("hello")
doThing(false)

Basic spread operator

import std/macros

proc `><`[T](val: T): T = T val
macro spread(prc: proc, args: varargs[typed]) =
  result = newCall(prc)
  var foundSpreaded = false
  let paramCount =  prc.getTypeInst[0].len - 1 # return is a parameter
  for arg in args:
    if arg.kind == nnkPrefix and arg[0].eqIdent "><":
      if foundSpreaded:
        error("Cannot spread twice presently", arg)
      foundSpreaded = true
      for i in 0..(paramCount - result.len):
        result.add newCall("[]", arg[1], newLit(i))

    else:
      result.add arg
     
proc doThing(a: string, b: float, c, d, e: int) = 
  echo locals()

var a = @[10, 20, 30, 40, 50]

doThing.spread("hello", 35f, ><a)

More intuitive variant dispatching

import std/[macros, genasts]

type Dispatch[T: static[enum]] = distinct T.typeof


proc findDelim(obj: NimNode): (NimNode, NimNode) =
  for x in obj:
    if x.kind == nnkRecCase:
      return (x[0][0], x[0][1])
    else:
      result = findDelim(x)
      if result[0].kind != nnkNilLit:
        return

macro variantDispatch(obj: object, call: untyped): untyped =
  let (delimName, delimKind) = obj.getTypeImpl.findDelim()
  result = newStmtList()
  let obj =
    if obj.kind != nnkSym:
      let name = genSym(nskLet)
      result.add newLetStmt(name, obj)
      name
    else:
      obj

  let caseStmt = nnkCaseStmt.newTree(nnkDotExpr.newTree(obj, delimName))
  for x in delimKind.getTypeImpl[1..^1]:
    let call = call.copyNimTree()
    call.insert(1, obj)
    call.add:
      genast(x, disp = bindSym"Dispatch", enm = delimKind):
        Dispatch[x](x)
    caseStmt.add nnkOfBranch.newTree(x, call)

  result.add caseStmt

  echo result.repr

type
  Color = enum Red, Green, Blue
  MyType = object
    case kind: Color
    of Red:
      discard
    else:
      discard
  

proc doThing(typ: MyType, _: Dispatch[Red]) = echo "Red"
proc doThing(typ: MyType, _: Dispatch[Green]) = echo "Green"
proc doThing(typ: MyType, _: Dispatch[Blue]) = echo "Blue"



variantDispatch(MyType(kind: Red), doThing())
variantDispatch(MyType(kind: Green), doThing())
variantDispatch(MyType(kind: Blue), doThing())