User-defined pragmas
User-defined pragmas
pragma pragma
The pragma
pragma can be used to declare user-defined pragmas. This is
useful because Nim\'s templates and macros do not affect pragmas.
User-defined pragmas are in a different module-wide scope than all other
symbols. They cannot be imported from a module.
Example:
when appType == "lib":
{.pragma: rtl, exportc, dynlib, cdecl.}
else:
{.pragma: rtl, importc, dynlib: "client.dll", cdecl.}
proc p*(a, b: int): int {.rtl.} =
result = a+b
In the example, a new pragma named rtl
is introduced that either
imports a symbol from a dynamic library or exports the symbol for
dynamic library generation.
Custom annotations
It is possible to define custom typed pragmas. Custom pragmas do not
affect code generation directly, but their presence can be detected by
macros. Custom pragmas are defined using templates annotated with pragma
pragma
:
template dbTable(name: string, table_space: string = "") {.pragma.}
template dbKey(name: string = "", primary_key: bool = false) {.pragma.}
template dbForeignKey(t: typedesc) {.pragma.}
template dbIgnore {.pragma.}
Consider this stylized example of a possible Object Relation Mapping (ORM) implementation:
const tblspace {.strdefine.} = "dev" # switch for dev, test and prod environments
type
User {.dbTable("users", tblspace).} = object
id {.dbKey(primary_key = true).}: int
name {.dbKey"full_name".}: string
is_cached {.dbIgnore.}: bool
age: int
UserProfile {.dbTable("profiles", tblspace).} = object
id {.dbKey(primary_key = true).}: int
user_id {.dbForeignKey: User.}: int
read_access: bool
write_access: bool
admin_acess: bool
In this example, custom pragmas are used to describe how Nim objects are mapped to the schema of the relational database. Custom pragmas can have zero or more arguments. In order to pass multiple arguments use one of template call syntaxes. All arguments are typed and follow standard overload resolution rules for templates. Therefore, it is possible to have default values for arguments, pass by name, varargs, etc.
Custom pragmas can be used in all locations where ordinary pragmas can be specified. It is possible to annotate procs, templates, type and variable definitions, statements, etc.
The macros module includes helpers which can be used to simplify custom
pragma access hasCustomPragma
, getCustomPragmaVal
. Please consult
the macros module documentation for details. These macros
are not magic, everything they do can also be achieved by walking the
AST of the object representation.
More examples with custom pragmas:
-
Better serialization/deserialization control:
-
Adopting type for gui inspector in a game engine:
Macro pragmas
All macros and templates can also be used as pragmas. They can be attached to routines (procs, iterators, etc), type names, or type expressions. The compiler will perform the following simple syntactic transformations:
This is translated to:
This is translated to:
This is translated to a call to the schema
macro with a nnkTypeDef
AST node capturing both the left-hand side and right-hand side of the
definition. The macro can return a potentially modified nnkTypeDef
tree which will replace the original row in the type section.
When multiple macro pragmas are applied to the same definition, the compiler will apply them consequently from left to right. Each macro will receive as input the output of the previous one.