Custom annotations

It is possible to define custom typed pragmas. Custom pragmas do not effect code generation directly, but their presence can be detected by macros. Custom pragmas are defined using templates annotated with pragma pragma:

  1. template dbTable(name: string, table_space: string = "") {.pragma.}
  2. template dbKey(name: string = "", primary_key: bool = false) {.pragma.}
  3. template dbForeignKey(t: typedesc) {.pragma.}
  4. template dbIgnore {.pragma.}

Consider stylized example of possible Object Relation Mapping (ORM) implementation:

  1. const tblspace {.strdefine.} = "dev" # switch for dev, test and prod environments
  2. type
  3. User {.dbTable("users", tblspace).} = object
  4. id {.dbKey(primary_key = true).}: int
  5. name {.dbKey"full_name".}: string
  6. is_cached {.dbIgnore.}: bool
  7. age: int
  8. UserProfile {.dbTable("profiles", tblspace).} = object
  9. id {.dbKey(primary_key = true).}: int
  10. user_id {.dbForeignKey: User.}: int
  11. read_access: bool
  12. write_access: bool
  13. 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.

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:
  1. type MyObj = object
  2. a {.dontSerialize.}: int
  3. b {.defaultDeserialize: 5.}: int
  4. c {.serializationKey: "_c".}: string
  • Adopting type for gui inspector in a game engine:
  1. type MyComponent = object
  2. position {.editable, animatable.}: Vector3
  3. alpha {.editRange: [0.0..1.0], animatable.}: float32