summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md3
-rw-r--r--compiler/ast.nim1
-rw-r--r--compiler/pragmas.nim112
-rw-r--r--compiler/semexprs.nim3
-rw-r--r--compiler/semstmts.nim3
-rw-r--r--compiler/semtempl.nim5
-rw-r--r--doc/manual/pragmas.txt67
-rw-r--r--lib/core/macros.nim54
-rw-r--r--tests/pragmas/custom_pragma.nim5
-rw-r--r--tests/pragmas/tcustom_pragma.nim43
10 files changed, 254 insertions, 42 deletions
diff --git a/changelog.md b/changelog.md
index 21ab2b87a..993923e5c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -190,3 +190,6 @@ let
 
 - Added support for casting between integers of same bitsize in VM (compile time and nimscript).
   This allow to among other things to reinterpret signed integers as unsigned.
+- Pragmas now support call syntax, for example: ``{.exportc"myname".}`` and ``{.exportc("myname").}``
+- Custom pragmas are now supported using pragma ``pragma``, please see language manual for details
+
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 54c33a038..69f2eb1c7 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -305,6 +305,7 @@ const
   sfEscapes* = sfProcvar              # param escapes
   sfBase* = sfDiscriminant
   sfIsSelf* = sfOverriden             # param is 'self'
+  sfCustomPragma* = sfRegister        # symbol is custom pragma template
 
 const
   # getting ready for the future expr/stmt merge
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index bdaecf91d..b6229796f 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -17,6 +17,7 @@ import
 const
   FirstCallConv* = wNimcall
   LastCallConv* = wNoconv
+  nkPragmaCallKinds = {nkExprColonExpr, nkCall, nkCallStrLit}
 
 const
   procPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl,
@@ -29,7 +30,7 @@ const
   converterPragmas* = procPragmas
   methodPragmas* = procPragmas+{wBase}-{wImportCpp}
   templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty,
-    wDelegator, wExportNims, wUsed}
+    wDelegator, wExportNims, wUsed, wPragma}
   macroPragmas* = {FirstCallConv..LastCallConv, wImmediate, wImportc, wExportc,
     wNodecl, wMagic, wNosideeffect, wCompilerProc, wCore, wDeprecated, wExtern,
     wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wDelegator,
@@ -74,7 +75,7 @@ proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode =
   let p = procAst[pragmasPos]
   if p.kind == nkEmpty: return nil
   for it in p:
-    if it.kind == nkExprColonExpr and it[0].kind == nkIdent and
+    if it.kind in nkPragmaCallKinds and it.len == 2 and it[0].kind == nkIdent and
         it[0].ident.id == ord(name):
       return it[1]
 
@@ -89,7 +90,7 @@ proc pragmaAsm*(c: PContext, n: PNode): char =
   if n != nil:
     for i in countup(0, sonsLen(n) - 1):
       let it = n.sons[i]
-      if it.kind == nkExprColonExpr and it.sons[0].kind == nkIdent:
+      if it.kind in nkPragmaCallKinds and it.len == 2 and it.sons[0].kind == nkIdent:
         case whichKeyword(it.sons[0].ident)
         of wSubsChar:
           if it.sons[1].kind == nkCharLit: result = chr(int(it.sons[1].intVal))
@@ -151,7 +152,7 @@ proc newEmptyStrNode(n: PNode): PNode {.noinline.} =
   result.strVal = ""
 
 proc getStrLitNode(c: PContext, n: PNode): PNode =
-  if n.kind != nkExprColonExpr:
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
     localError(n.info, errStringLiteralExpected)
     # error correction:
     result = newEmptyStrNode(n)
@@ -168,7 +169,7 @@ proc expectStrLit(c: PContext, n: PNode): string =
   result = getStrLitNode(c, n).strVal
 
 proc expectIntLit(c: PContext, n: PNode): int =
-  if n.kind != nkExprColonExpr:
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
     localError(n.info, errIntLiteralExpected)
   else:
     n.sons[1] = c.semConstExpr(c, n.sons[1])
@@ -177,7 +178,7 @@ proc expectIntLit(c: PContext, n: PNode): int =
     else: localError(n.info, errIntLiteralExpected)
 
 proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string =
-  if n.kind == nkExprColonExpr: result = expectStrLit(c, n)
+  if n.kind in nkPragmaCallKinds: result = expectStrLit(c, n)
   else: result = defaultStr
 
 proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) =
@@ -186,7 +187,7 @@ proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) =
 proc processMagic(c: PContext, n: PNode, s: PSym) =
   #if sfSystemModule notin c.module.flags:
   #  liMessage(n.info, errMagicOnlyInSystem)
-  if n.kind != nkExprColonExpr:
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
     localError(n.info, errStringLiteralExpected)
     return
   var v: string
@@ -204,7 +205,7 @@ proc wordToCallConv(sw: TSpecialWord): TCallingConvention =
   result = TCallingConvention(ord(ccDefault) + ord(sw) - ord(wNimcall))
 
 proc isTurnedOn(c: PContext, n: PNode): bool =
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     let x = c.semConstBoolExpr(c, n.sons[1])
     n.sons[1] = x
     if x.kind == nkIntLit: return x.intVal != 0
@@ -223,7 +224,7 @@ proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) =
   else: excl(c.module.flags, flag)
 
 proc processCallConv(c: PContext, n: PNode) =
-  if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent):
+  if n.kind in nkPragmaCallKinds and n.len == 2 and n.sons[1].kind == nkIdent:
     var sw = whichKeyword(n.sons[1].ident)
     case sw
     of FirstCallConv..LastCallConv:
@@ -244,7 +245,7 @@ proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib =
     result.isOverriden = options.isDynlibOverride(path.strVal)
 
 proc expectDynlibNode(c: PContext, n: PNode): PNode =
-  if n.kind != nkExprColonExpr:
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
     localError(n.info, errStringLiteralExpected)
     # error correction:
     result = newEmptyStrNode(n)
@@ -264,7 +265,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) =
     if not lib.isOverriden:
       c.optionStack[^1].dynlib = lib
   else:
-    if n.kind == nkExprColonExpr:
+    if n.kind in nkPragmaCallKinds:
       var lib = getLib(c, libDynamic, expectDynlibNode(c, n))
       if not lib.isOverriden:
         addToLib(lib, sym)
@@ -279,7 +280,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) =
       sym.typ.callConv = ccCDecl
 
 proc processNote(c: PContext, n: PNode) =
-  if (n.kind == nkExprColonExpr) and (sonsLen(n) == 2) and
+  if (n.kind in nkPragmaCallKinds) and (sonsLen(n) == 2) and
       (n.sons[0].kind == nkBracketExpr) and
       (n.sons[0].sons.len == 2) and
       (n.sons[0].sons[1].kind == nkIdent) and
@@ -307,7 +308,7 @@ proc processNote(c: PContext, n: PNode) =
     invalidPragma(n)
 
 proc processOption(c: PContext, n: PNode): bool =
-  if n.kind != nkExprColonExpr: result = true
+  if n.kind notin nkPragmaCallKinds or n.len != 2: result = true
   elif n.sons[0].kind == nkBracketExpr: processNote(c, n)
   elif n.sons[0].kind != nkIdent: result = true
   else:
@@ -355,8 +356,8 @@ proc processOption(c: PContext, n: PNode): bool =
     else: result = true
 
 proc processPush(c: PContext, n: PNode, start: int) =
-  if n.sons[start-1].kind == nkExprColonExpr:
-    localError(n.info, errGenerated, "':' after 'push' not supported")
+  if n.sons[start-1].kind in nkPragmaCallKinds:
+    localError(n.info, errGenerated, "'push' can't have arguments")
   var x = newOptionEntry()
   var y = c.optionStack[^1]
   x.options = gOptions
@@ -381,14 +382,14 @@ proc processPop(c: PContext, n: PNode) =
     c.optionStack.setLen(c.optionStack.len - 1)
 
 proc processDefine(c: PContext, n: PNode) =
-  if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent):
+  if (n.kind in nkPragmaCallKinds and n.len == 2) and (n.sons[1].kind == nkIdent):
     defineSymbol(n.sons[1].ident.s)
     message(n.info, warnDeprecated, "define")
   else:
     invalidPragma(n)
 
 proc processUndef(c: PContext, n: PNode) =
-  if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent):
+  if (n.kind in nkPragmaCallKinds and n.len == 2) and (n.sons[1].kind == nkIdent):
     undefSymbol(n.sons[1].ident.s)
     message(n.info, warnDeprecated, "undef")
   else:
@@ -420,7 +421,7 @@ proc processCompile(c: PContext, n: PNode) =
       localError(n.info, errStringLiteralExpected)
       result = ""
 
-  let it = if n.kind == nkExprColonExpr: n.sons[1] else: n
+  let it = if n.kind in nkPragmaCallKinds and n.len == 2: n.sons[1] else: n
   if it.kind == nkPar and it.len == 2:
     let s = getStrLit(c, it, 0)
     let dest = getStrLit(c, it, 1)
@@ -453,7 +454,7 @@ proc pragmaBreakpoint(c: PContext, n: PNode) =
   discard getOptionalStr(c, n, "")
 
 proc pragmaWatchpoint(c: PContext, n: PNode) =
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     n.sons[1] = c.semExpr(c, n.sons[1])
   else:
     invalidPragma(n)
@@ -494,7 +495,7 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
     result = newNode(nkAsmStmt, n.info)
 
 proc pragmaEmit(c: PContext, n: PNode) =
-  if n.kind != nkExprColonExpr:
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
     localError(n.info, errStringLiteralExpected)
   else:
     let n1 = n[1]
@@ -512,12 +513,12 @@ proc pragmaEmit(c: PContext, n: PNode) =
         localError(n.info, errStringLiteralExpected)
 
 proc noVal(n: PNode) =
-  if n.kind == nkExprColonExpr: invalidPragma(n)
+  if n.kind in nkPragmaCallKinds and n.len > 1: invalidPragma(n)
 
 proc pragmaUnroll(c: PContext, n: PNode) =
   if c.p.nestedLoopCounter <= 0:
     invalidPragma(n)
-  elif n.kind == nkExprColonExpr:
+  elif n.kind in nkPragmaCallKinds and n.len == 2:
     var unrollFactor = expectIntLit(c, n)
     if unrollFactor <% 32:
       n.sons[1] = newIntNode(nkIntLit, unrollFactor)
@@ -525,10 +526,11 @@ proc pragmaUnroll(c: PContext, n: PNode) =
       invalidPragma(n)
 
 proc pragmaLine(c: PContext, n: PNode) =
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     n.sons[1] = c.semConstExpr(c, n.sons[1])
     let a = n.sons[1]
     if a.kind == nkPar:
+      # unpack the tuple
       var x = a.sons[0]
       var y = a.sons[1]
       if x.kind == nkExprColonExpr: x = x.sons[1]
@@ -549,7 +551,7 @@ proc pragmaLine(c: PContext, n: PNode) =
 
 proc processPragma(c: PContext, n: PNode, i: int) =
   var it = n.sons[i]
-  if it.kind != nkExprColonExpr: invalidPragma(n)
+  if it.kind notin nkPragmaCallKinds and it.len == 2: invalidPragma(n)
   elif it.sons[0].kind != nkIdent: invalidPragma(n)
   elif it.sons[1].kind != nkIdent: invalidPragma(n)
 
@@ -566,7 +568,7 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) =
       localError(x.info, errGenerated, "invalid type for raises/tags list")
     x.typ = t
 
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     let it = n.sons[1]
     if it.kind notin {nkCurly, nkBracket}:
       processExc(c, it)
@@ -576,7 +578,7 @@ proc pragmaRaisesOrTags(c: PContext, n: PNode) =
     invalidPragma(n)
 
 proc pragmaLockStmt(c: PContext; it: PNode) =
-  if it.kind != nkExprColonExpr:
+  if it.kind notin nkPragmaCallKinds or it.len != 2:
     invalidPragma(it)
   else:
     let n = it[1]
@@ -587,7 +589,7 @@ proc pragmaLockStmt(c: PContext; it: PNode) =
         n.sons[i] = c.semExpr(c, n.sons[i])
 
 proc pragmaLocks(c: PContext, it: PNode): TLockLevel =
-  if it.kind != nkExprColonExpr:
+  if it.kind notin nkPragmaCallKinds or it.len != 2:
     invalidPragma(it)
   else:
     case it[1].kind
@@ -604,7 +606,7 @@ proc pragmaLocks(c: PContext, it: PNode): TLockLevel =
         result = TLockLevel(x)
 
 proc typeBorrow(sym: PSym, n: PNode) =
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     let it = n.sons[1]
     if it.kind != nkAccQuoted:
       localError(n.info, "a type can only borrow `.` for now")
@@ -624,7 +626,7 @@ proc deprecatedStmt(c: PContext; pragma: PNode) =
   if pragma.kind != nkBracket:
     localError(pragma.info, "list of key:value pairs expected"); return
   for n in pragma:
-    if n.kind in {nkExprColonExpr, nkExprEqExpr}:
+    if n.kind in nkPragmaCallKinds and n.len == 2:
       let dest = qualifiedLookUp(c, n[1], {checkUndeclared})
       if dest == nil or dest.kind in routineKinds:
         localError(n.info, warnUser, "the .deprecated pragma is unreliable for routines")
@@ -638,7 +640,7 @@ proc deprecatedStmt(c: PContext; pragma: PNode) =
       localError(n.info, "key:value pair expected")
 
 proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym =
-  if it.kind != nkExprColonExpr:
+  if it.kind notin nkPragmaCallKinds or it.len != 2:
     invalidPragma(it); return
   let n = it[1]
   if n.kind == nkSym:
@@ -655,13 +657,36 @@ proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym =
   else:
     result = qualifiedLookUp(c, n, {checkUndeclared})
 
+proc semCustomPragma(c: PContext, n: PNode): PNode =
+  assert(n.kind in nkPragmaCallKinds + {nkIdent})
+  
+  if n.kind == nkIdent:
+    result = newTree(nkCall, n)
+  elif n.kind == nkExprColonExpr:
+    # pragma: arg -> pragma(arg)
+    result = newTree(nkCall, n[0], n[1])
+  else:
+    result = n
+
+  result = c.semOverloadedCall(c, result, n, {skTemplate}, {})
+  if sfCustomPragma notin result[0].sym.flags:
+    invalidPragma(n)
+
+  if n.kind == nkIdent:
+    result = result[0]
+  elif n.kind == nkExprColonExpr:
+    result.kind = n.kind # pragma(arg) -> pragma: arg
+
 proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
                   validPragmas: TSpecialWords): bool =
   var it = n.sons[i]
-  var key = if it.kind == nkExprColonExpr: it.sons[0] else: it
+  var key = if it.kind in nkPragmaCallKinds and it.len > 1: it.sons[0] else: it
   if key.kind == nkBracketExpr:
     processNote(c, it)
     return
+  elif key.kind notin nkIdentKinds:
+    n.sons[i] = semCustomPragma(c, it)
+    return
   let ident = considerQuotedIdent(key)
   var userPragma = strTableGet(c.userPragmas, ident)
   if userPragma != nil:
@@ -785,7 +810,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
       of wExplain:
         sym.flags.incl sfExplain
       of wDeprecated:
-        if it.kind == nkExprColonExpr: deprecatedStmt(c, it)
+        if it.kind in nkPragmaCallKinds: deprecatedStmt(c, it)
         elif sym != nil: incl(sym.flags, sfDeprecated)
         else: incl(c.module.flags, sfDeprecated)
       of wVarargs:
@@ -864,8 +889,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
         result = true
       of wPop: processPop(c, it)
       of wPragma:
-        processPragma(c, n, i)
-        result = true
+        if not sym.isNil and sym.kind == skTemplate:
+          sym.flags.incl sfCustomPragma
+        else:
+          processPragma(c, n, i)
+          result = true
       of wDiscardable:
         noVal(it)
         if sym != nil: incl(sym.flags, sfDiscardable)
@@ -939,7 +967,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
         elif sym.typ == nil: invalidPragma(it)
         else: sym.typ.lockLevel = pragmaLocks(c, it)
       of wBitsize:
-        if sym == nil or sym.kind != skField or it.kind != nkExprColonExpr:
+        if sym == nil or sym.kind != skField:
           invalidPragma(it)
         else:
           sym.bitsize = expectIntLit(c, it)
@@ -957,7 +985,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
         if sym == nil: invalidPragma(it)
         else: magicsys.registerNimScriptSymbol(sym)
       of wInjectStmt:
-        if it.kind != nkExprColonExpr:
+        if it.kind notin nkPragmaCallKinds or it.len != 2:
           localError(it.info, errExprExpected)
         else:
           it.sons[1] = c.semExpr(c, it.sons[1])
@@ -968,10 +996,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
         else:
           localError(it.info, "'experimental' pragma only valid as toplevel statement")
       of wThis:
-        if it.kind == nkExprColonExpr:
+        if it.kind in nkPragmaCallKinds and it.len == 2:
           c.selfName = considerQuotedIdent(it[1])
-        else:
+        elif it.kind == nkIdent or it.len == 1:
           c.selfName = getIdent("self")
+        else:
+          localError(it.info, "'this' pragma is allowed to have zero or one arguments")
       of wNoRewrite:
         noVal(it)
       of wBase:
@@ -987,7 +1017,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
         else: sym.flags.incl sfUsed
       of wLiftLocals: discard
       else: invalidPragma(it)
-    else: invalidPragma(it)
+    else: 
+      n.sons[i] = semCustomPragma(c, it)
+
 
 proc implicitPragmas*(c: PContext, sym: PSym, n: PNode,
                       validPragmas: TSpecialWords) =
@@ -1015,7 +1047,7 @@ proc hasPragma*(n: PNode, pragma: TSpecialWord): bool =
     return false
 
   for p in n.sons:
-    var key = if p.kind == nkExprColonExpr: p[0] else: p
+    var key = if p.kind in nkPragmaCallKinds and p.len > 1: p[0] else: p
     if key.kind == nkIdent and whichKeyword(key.ident) == pragma:
       return true
 
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 62489bd36..e737f7676 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -951,7 +951,8 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
     else:
       result = semMacroExpr(c, n, n, s, flags)
   of skTemplate:
-    if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0:
+    if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or 
+       sfCustomPragma in sym.flags:
       markUsed(n.info, s, c.graph.usageSym)
       styleCheckUse(n.info, s)
       result = newSymNode(s, n.info)
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 096cf99de..ccddabcbe 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1153,6 +1153,9 @@ proc semProcAnnotation(c: PContext, prc: PNode;
         else:
           localError(prc.info, errOnlyACallOpCanBeDelegator)
       continue
+    elif sfCustomPragma in m.flags:
+      continue # semantic check for custom pragma happens later in semProcAux
+      
     # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and
     # let the semantic checker deal with it:
     var x = newNodeI(nkCall, n.info)
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index f90dff8f1..454dadec0 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -608,7 +608,10 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
   popOwner(c)
   s.ast = n
   result = n
-  if n.sons[bodyPos].kind == nkEmpty:
+  if sfCustomPragma in s.flags:
+    if n.sons[bodyPos].kind != nkEmpty:
+      localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s)
+  elif n.sons[bodyPos].kind == nkEmpty:
     localError(n.info, errImplOfXexpected, s.name.s)
   var proto = searchForProc(c, c.currentScope, s)
   if proto == nil:
diff --git a/doc/manual/pragmas.txt b/doc/manual/pragmas.txt
index 835b6909d..cd26a9448 100644
--- a/doc/manual/pragmas.txt
+++ b/doc/manual/pragmas.txt
@@ -1087,3 +1087,70 @@ In the above example, providing the -d flag causes the symbol
 ``FooBar`` to be overwritten at compile time, printing out 42. If the
 ``-d:FooBar=42`` were to be omitted, the default value of 5 would be
 used.
+
+
+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``:
+
+.. code-block:: nim
+  template dbTable(name: string, table_space: string = nil) {.pragma.}
+  template dbKey(name: string = nil, primary_key: bool = false) {.pragma.}
+  template dbForeignKey(t: typedesc) {.pragma.}
+  template dbIgnore {.pragma.}
+
+
+Consider stylized example of possible Object Relation Mapping (ORM) implementation:
+
+.. code-block:: nim
+  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.
+
+Macros module includes helpers which can be used to simplify custom pragma 
+access `hasCustomPragma`, `getCustomPragmaVal`. Please consult macros module 
+documentation for details. These macros are no magic, they don't do anything 
+you cannot do yourself by walking AST object representation.
+
+More examples with custom pragmas:
+  - Better serialization/deserialization control:
+
+  .. code-block:: nim
+    type MyObj = object
+      a {.dontSerialize.}: int
+      b {.defaultDeserialize: 5.}: int
+      c {.serializationKey: "_c".}: string
+
+  - Adopting type for gui inspector in a game engine:
+
+  .. code-block:: nim
+    type MyComponent = object
+      position {.editable, animatable.}: Vector3
+      alpha {.editRange: [0.0..1.0], animatable.}: float32
+
+
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index b08a2198e..ed9c304fe 100644
--- a/lib/core/macros.nim
+++ b/lib/core/macros.nim
@@ -130,6 +130,7 @@ const
   nnkLiterals* = {nnkCharLit..nnkNilLit}
   nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand,
                    nnkCallStrLit}
+  nnkPragmaCallKinds = {nnkExprColonExpr, nnkCall, nnkCallStrLit}
 
 proc `!`*(s: string): NimIdent {.magic: "StrToIdent", noSideEffect, deprecated.}
   ## constructs an identifier from the string `s`
@@ -1213,6 +1214,59 @@ macro expandMacros*(body: typed): untyped =
   result = getAst(inner(body))
   echo result.toStrLit
 
+proc customPragmaNode(n: NimNode): NimNode =
+  expectKind(n, {nnkSym, nnkDotExpr})
+  if n.kind == nnkSym:
+    let sym = n.symbol.getImpl()
+    sym.expectRoutine()
+    result = sym.pragma
+  elif n.kind == nnkDotExpr:
+    let typDef = getImpl(getTypeInst(n[0]).symbol)
+    typDef.expectKind(nnkTypeDef)
+    typDef[2].expectKind(nnkObjectTy)
+    let recList = typDef[2][2]
+    for identDefs in recList:
+      for i in 0 .. identDefs.len - 3:
+        if identDefs[i].kind == nnkPragmaExpr and 
+           identDefs[i][0].kind == nnkIdent and $identDefs[i][0] == $n[1]:
+          return identDefs[i][1]
+
+macro hasCustomPragma*(n: typed, cp: typed{nkSym}): untyped =
+  ## Expands to `true` if expression `n` which is expected to be `nnkDotExpr`
+  ## has custom pragma `cp`.
+  ##
+  ## .. code-block:: nim
+  ##   template myAttr() {.pragma.}
+  ##   type 
+  ##     MyObj = object
+  ##       myField {.myAttr.}: int
+  ##   var o: MyObj
+  ##   assert(o.myField.hasCustomPragma(myAttr) == 0)
+  let pragmaNode = customPragmaNode(n)
+  for p in pragmaNode:
+    if (p.kind == nnkSym and p == cp) or
+        (p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp):
+      return newLit(true)
+  return newLit(false)
+
+macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped =
+  ## Expands to value of custom pragma `cp` of expression `n` which is expected
+  ## to be `nnkDotExpr`.
+  ##
+  ## .. code-block:: nim
+  ##   template serializationKey(key: string) {.pragma.}
+  ##   type 
+  ##     MyObj = object
+  ##       myField {.serializationKey: "mf".}: int
+  ##   var o: MyObj
+  ##   assert(o.myField.getCustomPragmaVal(serializationKey) == "mf")
+  let pragmaNode = customPragmaNode(n)
+  for p in pragmaNode:
+    if p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp:
+      return p[1]
+  return newEmptyNode()
+
+
 when not defined(booting):
   template emit*(e: static[string]): untyped {.deprecated.} =
     ## accepts a single string argument and treats it as nim code
diff --git a/tests/pragmas/custom_pragma.nim b/tests/pragmas/custom_pragma.nim
new file mode 100644
index 000000000..9e8f51deb
--- /dev/null
+++ b/tests/pragmas/custom_pragma.nim
@@ -0,0 +1,5 @@
+# imported by tcustom_pragmas to test scoping
+
+template serializationKey*(s: string) {.pragma.}
+template defaultValue*(V: typed) {.pragma.}
+template alternativeKey*(s: string = nil, V: typed) {.pragma.}
\ No newline at end of file
diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim
new file mode 100644
index 000000000..a2380522f
--- /dev/null
+++ b/tests/pragmas/tcustom_pragma.nim
@@ -0,0 +1,43 @@
+import macros
+ 
+block:
+  template myAttr() {.pragma.}
+
+  proc myProc():int {.myAttr.} = 2
+  const myAttrIdx = myProc.hasCustomPragma(myAttr)
+  static: 
+    assert(myAttrIdx)
+
+block:
+  template myAttr(a: string) {.pragma.}
+
+  type MyObj = object
+    myField1, myField2 {.myAttr: "hi".}: int
+  var o: MyObj
+  static: 
+    assert o.myField2.hasCustomPragma(myAttr)
+    assert(not o.myField1.hasCustomPragma(myAttr))
+
+import custom_pragma 
+block: # A bit more advanced case
+  type 
+    Subfield = object
+      c {.serializationKey: "cc".}: float
+
+    MySerializable = object
+      a {.serializationKey"asdf", defaultValue: 5.} : int
+      b {.custom_pragma.defaultValue"hello".} : int
+      field: Subfield
+      d {.alternativeKey("df", 5).}: float
+      e {.alternativeKey(V = 5).}: seq[bool] 
+
+  var s: MySerializable
+
+  const aDefVal = s.a.getCustomPragmaVal(defaultValue)
+  static: assert(aDefVal == 5)
+
+  const aSerKey = s.a.getCustomPragmaVal(serializationKey)
+  static: assert(aSerKey == "asdf")
+
+  const cSerKey = getCustomPragmaVal(s.field.c, serializationKey)
+  static: assert(cSerKey == "cc")