/cdecl

Nim helper for using C Macros

Primary LanguageNim

See full docs at docs or source on github at elcritch/cdecl.

Small library for macros to handle various edge cases for Nim syntax. These are mostly edge case syntax handlers or tricky C Macro interfacings. The goal is to implement them as generically and well unit tested as possible.

Current macros includes:

  • cdecls: Macros to help using C macros that declare variables
    • cdeclmacro
  • applies: Macros that unpack arguments from various forms and calls functions
    • unpackObjectArgs: macro to splat an object to position arguments
    • unpackObjectArgFields: macro to splat an object to keyword arguments
    • unpackLabelsAsArgs: turn labels to named arguments
  • bitfields: Macros for making bitfield style accessor
    • bitfields: create bitfield accessors for hardware registers using any int type

You can see various usages in the tests folder.

Helper to apply all fields of an object as named paramters.

type AddObj = object
  a*: int
  b*: int

proc add(a, b: int): int = result = a + b

let args = AddObj(a: 1, b: 2) let res = unpackObjectArgs(add, args) assert res == 3

Helper to transform labels as named arguments to a function. Labels are regular Nim syntax for calling procs but are transformed to parameter names:

proc foo(name: string = "buzz", a, b: int) =
  echo name, ":", " a: ", $a, " b: ", $b

template Foo(blk: varargs[untyped]) = ## create a new template to act YAML like API unpackLabelsAsArgs(foo, blk)

Foo: name: "buzz" a: 11 b: 22

Will call foo(name="buzz",a=11,b=22) and print:

buzz: a: 11 b: 22

Macro helper for wrapping a C macro that declares a new C variable.

It handles emitting the appropriate C code for calling the macro. Additionally it defines a new Nim variable using importc which imports the declared variable.

import cdecl/cdecls
import cdecl/cdeclapi
export cdeclapi # this is needed clients to use the declared apis

proc CDefineVar*(name: CToken, size: static[int]) {. cdeclmacro: "C_MACRO_VARIABLE_DECLARER", cdeclsVar(name -> array[size, int32]).}

CMacroDeclare(myVar, 128, someExternalCVariable) # creates myVar

macro cdeclmacro(name: string; def: untyped)

import macros
import cdecl

proc CDefineVarStackRaw*(name: CToken, size: static[int], otherRaw: CRawStr): array[size, int32] {. cdeclmacro: "C_DEFINE_VAR_ADDITION".}

# Pass a raw string to the C macro: proc runCDefineVarStackRaw() = CDefineVarStackRaw(myVarStackRaw, 5, CRawStr("40+2")) assert myVarStackRaw[0] == 42

macro cdeclmacro

Macro helper for wrapping a C macro that declares a new C variable.

It handles emitting the appropriate C code for calling the macro.

It can define Nim variables using importc to wrap the generated variable. This is done using varName: CToken in the argument list and adding a cdeclsVar(varName -> varType) pragma. The cdeclsVar tells the macro which CToken argument to use and its type.

The macro will pass any extra pragmas to the variable. If the global pragma is passed in the emitted C code will be put in the /*VARSECTION*/ section.

macro cdeclmacro(name: string; def: untyped)

macro cmacrowrapper

pragma for making a c macro wrapper

macro cmacrowrapper(name: string; def: untyped)

type CRawStr

Represents a raw string that gets interpolated into generated C ouput

CRawStr = distinct string

type CLabel

used to represent a C macro "label", an alias for CRawStr

CLabel = CRawStr

type CRawToken

Represents a C token derived from a Nim expression

CRawToken = distinct static[CRawStr]

type CToken

Represents a C token derived from a Nim expression

CToken = distinct static[CRawStr]

macro symbolName

Get a string representation of a Nim symbol

macro symbolName(x: untyped): string

template symbolVal

Turns a CRawStr into a normal string

template symbolVal(x: CRawStr): string

template symbolVal

Turns a CRawStr into a normal string

template symbolVal(x: string): string

macro unpackObjectArgs

Calls callee with fields form object args unpacked as individual arguments.

This is similar to unpackVarargs in std/macros but for call a function using the values from an object

macro unpackObjectArgs(callee: untyped; arg: typed; extras: varargs[untyped]): untyped

macro unpackObjectArgFields

Similar to unpackObjectArgs but with named parameters based on field names.

macro unpackObjectArgFields(callee: untyped; arg: typed;
 extras: varargs[untyped]): untyped

macro unpackLabelsAsArgs

unpacks labels as named arguments.

macro unpackLabelsAsArgs(callee: typed; args: varargs[untyped]): untyped

macro bitfields

Create a new distinct integer type with accessors for bitfields that set and get bits for each field. This is more stable than C-style bitfields (see below).

The basic syntax for a bitfield declarations is:

fieldname: uint8[4..5]
- fieldName is the name of the accessors and produces both
a getter (fieldName) and setter (fieldName=)
- the range 4..5 is the target bit indexes. The ranges are
inclusive meaning 6 ... 6 is 1 bit. Ranges are sorted so you can also use 5 .. 4 to match hardware documentation.
  • The type uint8 is the type that the bits are converted to/from.

Signed types like int8 are supported and do signed shifts to

properly extend the sign. For example:
speed: int8[7..4]

The accessors generated are very simple and what you would generally produce by hand. For example:

bitfields RegConfig(uint16):
    speed: int8[4..2]
  

Generates code similar too:
type
    RegChannel = distinct uint16

proc speed(reg: RegChannel): uint8 = result = uint8(bitsliced(uint16(reg), 4 .. 9)) proc speed=(reg: var RegChannel; x: uint8) = setBitsSlice(uint16(reg), 4 .. 9, x)

This is often preferable to C-style bitfields which Nim does support. C-style bitfields are compiler and architecture dependent and prone to breaking on field alignement, endiannes, and other issues. See https://lwn.net/Articles/478657/

macro bitfields(name, def: untyped)