summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xcompiler/ast.nim15
-rwxr-xr-xcompiler/astalgo.nim12
-rwxr-xr-xcompiler/importer.nim10
-rwxr-xr-xcompiler/parser.nim22
-rwxr-xr-xcompiler/passes.nim15
-rw-r--r--compiler/patterns.nim128
-rwxr-xr-xcompiler/renderer.nim23
-rwxr-xr-xcompiler/rodread.nim6
-rwxr-xr-xcompiler/rodwrite.nim4
-rwxr-xr-xcompiler/sem.nim15
-rwxr-xr-xcompiler/semdata.nim19
-rwxr-xr-xcompiler/semexprs.nim1
-rwxr-xr-xcompiler/semstmts.nim15
-rwxr-xr-xcompiler/semtempl.nim27
-rwxr-xr-xcompiler/sigmatch.nim2
-rwxr-xr-xlib/core/macros.nim2
-rwxr-xr-xtodo.txt6
17 files changed, 264 insertions, 58 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index a8176501f..dcf60af6b 100755
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -176,6 +176,7 @@ type
     nkFromStmt,           # a from * import statement
     nkIncludeStmt,        # an include statement
     nkBindStmt,           # a bind statement
+    nkPatternStmt,        # a pattern statement ('as' statement)
     nkCommentStmt,        # a comment statement
     nkStmtListExpr,       # a statement list followed by an expr; this is used
                           # to allow powerful multi-line templates
@@ -246,8 +247,8 @@ type
                       # for interfacing with C++, JS
     sfNamedParamCall, # symbol needs named parameter call syntax in target
                       # language; for interfacing with Objective C
-    sfDiscardable     # returned value may be discarded implicitely
-    sfDestructor      # proc is destructor
+    sfDiscardable,    # returned value may be discarded implicitely
+    sfDestructor,     # proc is destructor
     sfGenSym          # symbol is 'gensym'ed; do not add to symbol table
 
   TSymFlags* = set[TSymFlag]
@@ -689,9 +690,10 @@ const
   genericParamsPos* = 1
   paramsPos* = 2
   pragmasPos* = 3
-  bodyPos* = 4       # position of body; use rodread.getBody() instead!
-  resultPos* = 5
-  dispatcherPos* = 6 # caution: if method has no 'result' it can be position 5!
+  patternPos* = 4    # empty except for term rewriting macros
+  bodyPos* = 5       # position of body; use rodread.getBody() instead!
+  resultPos* = 6
+  dispatcherPos* = 7 # caution: if method has no 'result' it can be position 5!
 
   nkCallKinds* = {nkCall, nkInfix, nkPrefix, nkPostfix,
                   nkCommand, nkCallStrLit}
@@ -1175,6 +1177,9 @@ proc isRoutine*(s: PSym): bool {.inline.} =
   result = s.kind in {skProc, skTemplate, skMacro, skIterator, skMethod,
                       skConverter}
 
+proc hasPattern*(s: PSym): bool {.inline.} =
+  result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty
+
 iterator items*(n: PNode): PNode =
   for i in 0.. <n.len: yield n.sons[i]
 
diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim
index 9da3e21e4..da0de3e94 100755
--- a/compiler/astalgo.nim
+++ b/compiler/astalgo.nim
@@ -834,8 +834,8 @@ proc writeIdNodeTable(t: TIdNodeTable) =
 proc IdNodeTableRawGet(t: TIdNodeTable, key: PIdObj): int = 
   var h: THash
   h = key.id and high(t.data) # start with real hash value
-  while t.data[h].key != nil: 
-    if (t.data[h].key.id == key.id): 
+  while t.data[h].key != nil:
+    if t.data[h].key.id == key.id:
       return h
     h = nextTry(h, high(t.data))
   result = - 1
@@ -845,6 +845,10 @@ proc IdNodeTableGet(t: TIdNodeTable, key: PIdObj): PNode =
   index = IdNodeTableRawGet(t, key)
   if index >= 0: result = t.data[index].val
   else: result = nil
+
+proc IdNodeTableGetLazy*(t: TIdNodeTable, key: PIdObj): PNode =
+  if not isNil(t.data):
+    result = IdNodeTableGet(t, key)
   
 proc IdNodeTableRawInsert(data: var TIdNodePairSeq, key: PIdObj, val: PNode) = 
   var h: THash
@@ -872,6 +876,10 @@ proc IdNodeTablePut(t: var TIdNodeTable, key: PIdObj, val: PNode) =
     IdNodeTableRawInsert(t.data, key, val)
     inc(t.counter)
 
+proc IdNodeTablePutLazy*(t: var TIdNodeTable, key: PIdObj, val: PNode) =
+  if isNil(t.data): initIdNodeTable(t)
+  IdNodeTablePut(t, key, val)
+
 iterator pairs*(t: TIdNodeTable): tuple[key: PIdObj, val: PNode] =
   for i in 0 .. high(t.data):
     if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val)
diff --git a/compiler/importer.nim b/compiler/importer.nim
index 24f7cb5c6..aa6722a32 100755
--- a/compiler/importer.nim
+++ b/compiler/importer.nim
@@ -45,8 +45,8 @@ proc rawImportSymbol(c: PContext, s: PSym) =
   var copy = s # do not copy symbols when importing!
   # check if we have already a symbol of the same name:
   var check = StrTableGet(c.tab.stack[importTablePos], s.name)
-  if (check != nil) and (check.id != copy.id): 
-    if not (s.kind in OverloadableSyms): 
+  if check != nil and check.id != copy.id: 
+    if s.kind notin OverloadableSyms: 
       # s and check need to be qualified:
       Incl(c.AmbiguousSymbols, copy.id)
       Incl(c.AmbiguousSymbols, check.id)
@@ -70,8 +70,10 @@ proc rawImportSymbol(c: PContext, s: PSym) =
           check = NextIdentIter(it, c.tab.stack[importTablePos])
         if e != nil: 
           rawImportSymbol(c, e)
-  elif s.kind == skConverter: 
-    addConverter(c, s)        # rodgen assures that converters are no stubs
+  else:
+    # rodgen assures that converters and patterns are no stubs
+    if s.kind == skConverter: addConverter(c, s)
+    if hasPattern(s): addPattern(c, s)
   
 proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = 
   let ident = lookups.considerAcc(n)
diff --git a/compiler/parser.nim b/compiler/parser.nim
index 33a2b6aae..4623b6eed 100755
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -183,7 +183,7 @@ proc getPrecedence(tok: TToken): int =
     of '?': result = 2
     else: considerAsgn(2)
   of tkDiv, tkMod, tkShl, tkShr: result = 9
-  of tkIn, tkNotIn, tkIs, tkIsNot, tkNot, tkOf: result = 5
+  of tkIn, tkNotIn, tkIs, tkIsNot, tkNot, tkOf, tkAs: result = 5
   of tkDotDot: result = 6
   of tkAnd: result = 4
   of tkOr, tkXor: result = 3
@@ -969,9 +969,18 @@ proc parseBreakOrContinue(p: var TParser, kind: TNodeKind): PNode =
   of tkEof, tkSad, tkDed: addSon(result, ast.emptyNode)
   else: addSon(result, parseSymbol(p))
   
-proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode = 
+proc parseAs(p: var TParser): PNode =
+  result = newNodeP(nkPatternStmt, p)
+  getTok(p)                 # skip `as`
+  if p.tok.tokType == tkColon:
+    eat(p, tkColon)
+    addSon(result, parseStmt(p))
+  else:
+    addSon(result, parseExpr(p))
+  
+proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode =
   result = newNodeP(kind, p)
-  while true: 
+  while true:
     getTok(p)                 # skip `if`, `when`, `elif`
     var branch = newNodeP(nkElifBranch, p)
     optInd(p, branch)
@@ -981,8 +990,8 @@ proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode =
     addSon(branch, parseStmt(p))
     skipComment(p, branch)
     addSon(result, branch)
-    if p.tok.tokType != tkElif: break 
-  if p.tok.tokType == tkElse: 
+    if p.tok.tokType != tkElif: break
+  if p.tok.tokType == tkElse:
     var branch = newNodeP(nkElse, p)
     eat(p, tkElse)
     eat(p, tkColon)
@@ -1176,6 +1185,8 @@ proc parseRoutine(p: var TParser, kind: TNodeKind): PNode =
   addSon(result, parseParamList(p))
   if p.tok.tokType == tkCurlyDotLe: addSon(result, parsePragma(p))
   else: addSon(result, ast.emptyNode)
+  # empty pattern:
+  addSon(result, ast.emptyNode)
   if p.tok.tokType == tkEquals: 
     getTok(p)
     skipComment(p, result)
@@ -1511,6 +1522,7 @@ proc complexOrSimpleStmt(p: var TParser): PNode =
   of tkWhen: result = parseIfOrWhen(p, nkWhenStmt)
   of tkVar: result = parseSection(p, nkVarSection, parseVariable)
   of tkBind: result = parseBind(p)
+  of tkAs: result = parseAs(p)
   else: result = simpleStmt(p)
   
 proc parseStmt(p: var TParser): PNode = 
diff --git a/compiler/passes.nim b/compiler/passes.nim
index bedcbb16e..c073047af 100755
--- a/compiler/passes.nim
+++ b/compiler/passes.nim
@@ -15,13 +15,8 @@ import
   condsyms, idents, renderer, types, extccomp, math, magicsys, nversion, 
   nimsets, syntaxes, times, rodread, semthreads, idgen
 
-type
-  TPassComm* = object {.pure.} ## communication object between passes
-    optimizers*: TSymSeq       ## filled by semantic pass; used in HLO
-  PPassComm* = ref TPassComm
-  
+type  
   TPassContext* = object of TObject # the pass's context
-    comm*: PPassComm
     fromCache*: bool  # true if created by "openCached"
     
   PPassContext* = ref TPassContext
@@ -85,26 +80,18 @@ proc registerPass(p: TPass) =
   gPasses[gPassesLen] = p
   inc(gPassesLen)
 
-proc newPassComm(): PPassComm =
-  new(result)
-  result.optimizers = @[]
-
 proc openPasses(a: var TPassContextArray, module: PSym, filename: string) = 
-  var comm = newPassComm()
   for i in countup(0, gPassesLen - 1): 
     if not isNil(gPasses[i].open): 
       a[i] = gPasses[i].open(module, filename)
-      if a[i] != nil: a[i].comm = comm
     else: a[i] = nil
   
 proc openPassesCached(a: var TPassContextArray, module: PSym, filename: string, 
                       rd: PRodReader) = 
-  var comm = newPassComm()
   for i in countup(0, gPassesLen - 1): 
     if not isNil(gPasses[i].openCached): 
       a[i] = gPasses[i].openCached(module, filename, rd)
       if a[i] != nil: 
-        a[i].comm = comm
         a[i].fromCache = true
     else:
       a[i] = nil
diff --git a/compiler/patterns.nim b/compiler/patterns.nim
new file mode 100644
index 000000000..51bb1fb69
--- /dev/null
+++ b/compiler/patterns.nim
@@ -0,0 +1,128 @@
+#
+#
+#           The Nimrod Compiler
+#        (c) Copyright 2012 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements the pattern matching features for term rewriting
+## macro support.
+
+import ast, astalgo, types, semdata, sigmatch, msgs, idents
+
+type
+  TPatternContext = object
+    owner: PSym
+    mapping: TIdNodeTable  # maps formal parameters to nodes
+    c: PContext
+  PPatternContext = var TPatternContext
+
+proc matches(c: PPatternContext, p, n: PNode): bool
+proc checkConstraints(c: PPatternContext, p, n: PNode): bool =
+  # XXX create a new mapping here? --> need use cases
+  result = matches(c, p, n)
+
+proc canonKind(n: PNode): TNodeKind =
+  ## nodekind canonilization for pattern matching
+  result = n.kind
+  case result
+  of nkCallKinds: result = nkCall
+  of nkStrLit..nkTripleStrLit: result = nkStrLit
+  of nkFastAsgn: result = nkAsgn
+  else: nil
+
+proc sameKinds(a, b: PNode): bool {.inline.} =
+  result = a.kind == b.kind or a.canonKind == b.canonKind
+
+proc sameTrees(a, b: PNode): bool =
+  if sameKinds(a, b):
+    case a.kind
+    of nkSym: result = a.sym == b.sym
+    of nkIdent: result = a.ident.id == b.ident.id
+    of nkCharLit..nkInt64Lit: result = a.intVal == b.intVal
+    of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal
+    of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal
+    of nkEmpty, nkNilLit: result = true
+    of nkType: result = sameTypeOrNil(a.typ, b.typ)
+    else:
+      if sonsLen(a) == sonsLen(b):
+        for i in countup(0, sonsLen(a) - 1):
+          if not sameTrees(a.sons[i], b.sons[i]): return
+        result = true
+
+proc inSymChoice(sc, x: PNode): bool =
+  if sc.kind in {nkOpenSymChoice, nkClosedSymChoice}:
+    for i in 0.. <sc.len:
+      if sc.sons[i].sym == x.sym: return true
+
+proc checkTypes(c: PPatternContext, p: PSym, n: PNode): bool =
+  # XXX tyVarargs is special here; lots of other special cases
+  if isNil(n.typ):
+    result = p.typ.kind == tyStmt
+  else:
+    result = sigmatch.argtypeMatches(c.c, p.typ, n.typ)
+
+proc matches(c: PPatternContext, p, n: PNode): bool =
+  # XXX special treatment: statement list,
+  # ignore comments, nkPar, hidden conversions
+  # f(..X) ~> how can 'X' stand for all remaining parameters? -> introduce
+  # a new local node kind (alias of nkReturnToken or something)
+  if p.kind == nkSym and p.sym.kind == skParam and p.sym.owner == c.owner:
+    var pp = IdNodeTableGetLazy(c.mapping, p.sym)
+    if pp != nil:
+      # check if we got the same pattern (already unified):
+      result = matches(c, pp, n)
+    elif checkTypes(c, p.sym, n) and 
+        (p.sym.ast == nil or checkConstraints(c, p.sym.ast, n)):
+      IdNodeTablePutLazy(c.mapping, p.sym, n)
+      result = true
+  elif n.kind == nkSym and inSymChoice(p, n):
+    result = true
+  elif n.kind == nkSym and n.sym.kind == skConst:
+    # try both:
+    if sameTrees(p, n): result = true
+    elif matches(c, p, n.sym.ast):
+      result = true
+  elif sameKinds(p, n):
+    case p.kind
+    of nkSym: result = p.sym == n.sym
+    of nkIdent: result = p.ident.id == n.ident.id
+    of nkCharLit..nkInt64Lit: result = p.intVal == n.intVal
+    of nkFloatLit..nkFloat64Lit: result = p.floatVal == n.floatVal
+    of nkStrLit..nkTripleStrLit: result = p.strVal == n.strVal
+    of nkEmpty, nkNilLit, nkType: result = true
+    else:
+      if sonsLen(p) == sonsLen(n):
+        for i in countup(0, sonsLen(p) - 1):
+          if not matches(c, p.sons[i], n.sons[i]): return
+        result = true
+
+# writeln(X, a); writeln(X, b); --> writeln(X, a, b)
+
+proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
+  ## returns a tree to semcheck if the rule triggered; nil otherwise
+  var ctx: TPatternContext
+  ctx.owner = s
+  ctx.c = c
+  # we perform 'initIdNodeTable' lazily for performance
+  if matches(ctx, s.ast.sons[patternPos], n):
+    # each parameter should have been bound; we simply setup a call and
+    # let semantic checking deal with the rest :-)
+    # this also saves type checking if we allow for type checking errors
+    # as in 'system.compiles' and simply discard the results. But an error
+    # may have been desired in the first place! Meh, it's good enough for
+    # a first implementation:
+    result = newNodeI(nkCall, n.info)
+    result.add(newSymNode(s, n.info))
+    let params = s.typ.n
+    for i in 1 .. < params.len:
+      let param = params.sons[i].sym
+      let x = IdNodeTableGetLazy(ctx.mapping, param)
+      # couldn't bind parameter:
+      if isNil(x): 
+        echo "couldn't bind ", param.name.s
+        return nil
+      result.add(x)
+
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index b6b34287a..7ceabaa33 100755
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -586,6 +586,16 @@ proc gwhile(g: var TSrcGen, n: PNode) =
   gcoms(g)                    # a good place for comments
   gstmts(g, n.sons[1], c)
 
+proc gpattern(g: var TSrcGen, n: PNode) = 
+  var c: TContext
+  put(g, tkAs, "as")
+  putWithSpace(g, tkColon, ":")
+  initContext(c)
+  if longMode(n) or (lsub(n.sons[0]) + g.lineLen > maxLineLen): 
+    incl(c.flags, rfLongMode)
+  gcoms(g)                    # a good place for comments
+  gstmts(g, n.sons[0], c)
+
 proc gpragmaBlock(g: var TSrcGen, n: PNode) = 
   var c: TContext
   gsub(g, n.sons[0])
@@ -656,20 +666,20 @@ proc gproc(g: var TSrcGen, n: PNode) =
     put(g, tkSymbol, renderDefinitionName(n.sons[namePos].sym))
   else:
     gsub(g, n.sons[namePos])
-  gsub(g, n.sons[1])
-  gsub(g, n.sons[2])
-  gsub(g, n.sons[3])
+  gsub(g, n.sons[genericParamsPos])
+  gsub(g, n.sons[paramsPos])
+  gsub(g, n.sons[pragmasPos])
   if renderNoBody notin g.flags:
-    if n.sons[4].kind != nkEmpty: 
+    if n.sons[bodyPos].kind != nkEmpty:
       put(g, tkSpaces, Space)
       putWithSpace(g, tkEquals, "=")
       indentNL(g)
       gcoms(g)
       dedent(g)
       initContext(c)
-      gstmts(g, n.sons[4], c)
+      gstmts(g, n.sons[bodyPos], c)
       putNL(g)
-    else: 
+    else:
       indentNL(g)
       gcoms(g)
       dedent(g)
@@ -1035,6 +1045,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   of nkCaseStmt, nkRecCase: gcase(g, n)
   of nkMacroStmt: gmacro(g, n)
   of nkTryStmt: gtry(g, n)
+  of nkPatternStmt: gpattern(g, n)
   of nkForStmt, nkParForStmt: gfor(g, n)
   of nkBlockStmt, nkBlockExpr: gblock(g, n)
   of nkStaticStmt: gstaticStmt(g, n)
diff --git a/compiler/rodread.nim b/compiler/rodread.nim
index 7e8584955..7c924511f 100755
--- a/compiler/rodread.nim
+++ b/compiler/rodread.nim
@@ -11,7 +11,7 @@
 #
 # Reading and writing binary files are really hard to debug. Therefore we use
 # a "creative" text/binary hybrid format. ROD-files are more efficient
-# to process because symbols are can be loaded on demand.
+# to process because symbols can be loaded on demand.
 # 
 # A ROD file consists of:
 #
@@ -65,10 +65,12 @@
 #    semantic checking:
 #    CONVERTERS:id id\n   # symbol ID
 #
+#    This is a misnomer now; it's really a "load unconditionally" section as
+#    it is also used for pattern templates.
+#
 #  - a list of all (private or exported) methods because they are needed for
 #    correct dispatcher generation:
 #    METHODS: id id\n   # symbol ID
-#
 #  - an AST section that contains the module's AST:
 #    INIT(
 #    idx\n  # position of the node in the DATA section
diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim
index 9af4349ab..75e8a02a7 100755
--- a/compiler/rodwrite.nim
+++ b/compiler/rodwrite.nim
@@ -357,10 +357,10 @@ proc symStack(w: PRodWriter): int =
           add(w.compilerProcs, ' ')
           encodeVInt(s.id, w.compilerProcs)
           add(w.compilerProcs, rodNL)
-        if s.kind == skConverter: 
+        if s.kind == skConverter or hasPattern(s):
           if w.converters.len != 0: add(w.converters, ' ')
           encodeVInt(s.id, w.converters)
-        elif s.kind == skMethod and sfDispatcher notin s.flags:
+        if s.kind == skMethod and sfDispatcher notin s.flags:
           if w.methods.len != 0: add(w.methods, ' ')
           encodeVInt(s.id, w.methods)
       elif IiTableGet(w.imports.tab, s.id) == invalidKey: 
diff --git a/compiler/sem.nim b/compiler/sem.nim
index d92c1657d..8d0e4e168 100755
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -15,7 +15,7 @@ import
   magicsys, parser, nversion, nimsets, semfold, importer,
   procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch,
   semthreads, intsets, transf, evals, idgen, aliases, cgmeth, lambdalifting,
-  evaltempl
+  evaltempl, patterns
 
 proc semPass*(): TPass
 # implementation
@@ -102,6 +102,19 @@ proc semConstExpr(c: PContext, n: PNode): PNode =
     return n
   result = evalTypedExpr(c, e)
 
+proc applyPatterns(c: PContext, n: PNode): PNode =
+  # fast exit:
+  if c.patterns.len == 0: return n
+  result = n
+  # we apply the last pattern first, so that pattern overriding is possible;
+  # however the resulting AST would better not trigger the old rule then
+  # anymore ;-)
+  for i in countdown(<c.patterns.len, 0):
+    let x = applyRule(c, c.patterns[i], result)
+    if not isNil(x):
+      assert x.kind == nkCall
+      result = semExpr(c, x)
+
 include seminst, semcall
 
 proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode = 
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index 0fc5399d2..02768c90b 100755
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -67,6 +67,7 @@ type
     InUnrolledContext*: int    # > 0 if we are unrolling a loop
     InCompilesContext*: int    # > 0 if we are in a ``compiles`` magic
     converters*: TSymSeq       # sequence of converters
+    patterns*: TSymSeq         # sequence of pattern matchers
     optionStack*: TLinkedList
     libs*: TLinkedList         # all libs used by this module
     semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas
@@ -93,7 +94,6 @@ proc newContext*(module: PSym, nimfile: string): PContext
 
 proc lastOptionEntry*(c: PContext): POptionEntry
 proc newOptionEntry*(): POptionEntry
-proc addConverter*(c: PContext, conv: PSym)
 proc newLib*(kind: TLibKind): PLib
 proc addToLib*(lib: PLib, sym: PSym)
 proc makePtrType*(c: PContext, baseType: PType): PType
@@ -158,6 +158,7 @@ proc newContext(module: PSym, nimfile: string): PContext =
   result.friendModule = module
   result.threadEntries = @[]
   result.converters = @[]
+  result.patterns = @[]
   result.filename = nimfile
   result.includedFiles = initIntSet()
   initStrTable(result.userPragmas)
@@ -172,12 +173,18 @@ proc newContext(module: PSym, nimfile: string): PContext =
     assert gGenericsCache == nil
   result.UnknownIdents = initIntSet()
 
-proc addConverter(c: PContext, conv: PSym) = 
-  var L = len(c.converters)
+proc inclSym(sq: var TSymSeq, s: PSym) =
+  var L = len(sq)
   for i in countup(0, L - 1): 
-    if c.converters[i].id == conv.id: return 
-  setlen(c.converters, L + 1)
-  c.converters[L] = conv
+    if sq[i].id == s.id: return 
+  setlen(sq, L + 1)
+  sq[L] = s
+
+proc addConverter*(c: PContext, conv: PSym) =
+  inclSym(c.converters, conv)
+
+proc addPattern*(c: PContext, p: PSym) =
+  inclSym(c.patterns, p)
 
 proc newLib(kind: TLibKind): PLib = 
   new(result)
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 9219cacf6..1bf412a26 100755
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -1591,3 +1591,4 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
     LocalError(n.info, errInvalidExpressionX,
                renderTree(n, {renderNoComments}))
   incl(result.flags, nfSem)
+  result = applyPatterns(c, result)
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 3a8424d9e..84d7b5b45 100755
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1202,6 +1202,16 @@ proc SemStmt(c: PContext, n: PNode): PNode =
     result = semPragmaBlock(c, n)
   of nkStaticStmt:
     result = semStaticStmt(c, n)
+  of nkPatternStmt:
+    let pat = semPatternStmt(c, n)
+    let s = getCurrOwner()
+    if s.kind in routineKinds and s.ast.sons[patternPos].kind == nkEmpty:
+      s.ast.sons[patternPos] = pat
+      c.patterns.add(s)
+    else:
+      LocalError(n.info, errXNotAllowedHere, "'as'")
+    # replace by an empty statement:
+    result = newNodeI(nkNilLit, n.info)
   else: 
     # in interactive mode, we embed the expression in an 'echo':
     if gCmd == cmdInteractive:
@@ -1214,10 +1224,11 @@ proc SemStmt(c: PContext, n: PNode): PNode =
     InternalError(n.info, "SemStmt: result = nil")
     # error correction:
     result = emptyNode
-  else: 
+  else:
     incl(result.flags, nfSem)
+  result = applyPatterns(c, result)
 
-proc semStmtScope(c: PContext, n: PNode): PNode = 
+proc semStmtScope(c: PContext, n: PNode): PNode =
   openScope(c.tab)
   result = semStmt(c, n)
   closeScope(c.tab)
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 440fac6cc..ce5dfe3a2 100755
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -98,10 +98,16 @@ proc replaceIdentBySym(n: var PNode, s: PNode) =
   of nkIdent, nkAccQuoted, nkSym: n = s
   else: illFormedAst(n)
 
+# This code here is the first pass over a template's body. The same code also
+# implements the first pass over a pattern's body:
+
 type
+  TBodyKind = enum
+    bkTemplate, bkPattern
   TemplCtx {.pure, final.} = object
     c: PContext
     toBind: TIntSet
+    bodyKind: TBodyKind
     owner: PSym
 
 proc getIdentNode(c: var TemplCtx, n: PNode): PNode =
@@ -166,10 +172,8 @@ proc semRoutineInTemplBody(c: var TemplCtx, n: PNode, k: TSymKind): PNode =
   else:
     n.sons[namePos] = semTemplBody(c, n.sons[namePos])
   openScope(c)
-  n.sons[genericParamsPos] = semTemplBody(c, n.sons[genericParamsPos])
-  n.sons[paramsPos] = semTemplBody(c, n.sons[paramsPos])
-  n.sons[pragmasPos] = semTemplBody(c, n.sons[pragmasPos])
-  n.sons[bodyPos] = semTemplBodyScope(c, n.sons[bodyPos])
+  for i in genericParamsPos..bodyPos:
+    n.sons[i] = semTemplBody(c, n.sons[i])
   closeScope(c)
 
 proc semTemplBody(c: var TemplCtx, n: PNode): PNode = 
@@ -183,6 +187,8 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
         result = newSymNode(s, n.info)
       elif Contains(c.toBind, s.id):
         result = symChoice(c.c, n, s, scClosed)
+      elif c.bodyKind == bkPattern:
+        result = symChoice(c.c, n, s, scOpen)
       elif s.owner == c.owner and sfGenSym in s.flags:
         # template tmp[T](x: var seq[T]) =
         # var yz: T
@@ -395,6 +401,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
   ctx.toBind = initIntSet()
   ctx.c = c
   ctx.owner = s
+  ctx.bodyKind = bkTemplate
   if sfDirty in s.flags:
     n.sons[bodyPos] = semTemplBodyDirty(ctx, n.sons[bodyPos])
   else:
@@ -415,3 +422,15 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
   else:
     SymTabReplace(c.tab.stack[curScope], proto, s)
 
+proc semPatternStmt(c: PContext, n: PNode): PNode =
+  # not much to do here: We don't replace operators ``$``, ``*``, ``+``,
+  # ``|``, ``~`` as meta operators and strip the leading ``\`` of all
+  # operators.
+  openScope(c.tab)
+  var ctx: TemplCtx
+  ctx.toBind = initIntSet()
+  ctx.c = c
+  ctx.owner = getCurrOwner()
+  ctx.bodyKind = bkPattern
+  result = semTemplBody(ctx, n.sons[0])
+  closeScope(c.tab)
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index a113696fe..1f4c9653e 100755
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -647,7 +647,7 @@ proc ParamTypesMatchAux(c: PContext, m: var TCandidate, f, a: PType,
     inc(m.convMatches)
     result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c)
   of isIntConv:
-    # too lazy to introduce another ``*matches`` field, so we conflate
+    # I'm too lazy to introduce another ``*matches`` field, so we conflate
     # ``isIntConv`` and ``isIntLit`` here:
     inc(m.intConvMatches)
     result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c)
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index 610360fb2..54d242238 100755
--- a/lib/core/macros.nim
+++ b/lib/core/macros.nim
@@ -46,7 +46,7 @@ type
     nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt, 

     nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt,

     nnkDiscardStmt, nnkStmtList, nnkImportStmt, nnkFromStmt, 

-    nnkIncludeStmt, nnkBindStmt,

+    nnkIncludeStmt, nnkBindStmt, nnkPatternStmt,

     nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr, 

     nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy, 

     nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen, 

diff --git a/todo.txt b/todo.txt
index 8c5d172da..a64a5b951 100755
--- a/todo.txt
+++ b/todo.txt
@@ -14,9 +14,6 @@ version 0.9.0
 - the lookup rules for generics really are too permissive; global scope only
   doesn't fly with closures though; for a start add a warning when processing
   generic code
-- fix remaining closure bugs:
-  - test evals.nim with closures
-  - what about macros with closures?
 
 
 Bugs
@@ -47,6 +44,9 @@ version 0.9.XX
 
 - JS gen:
   - fix exception handling
+- fix remaining closure bugs:
+  - test evals.nim with closures
+  - what about macros with closures?
 
 - document 'do' notation
 - allow implicit forward declarations of procs via a pragma (so that the