summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2014-02-02 00:45:47 +0100
committerAraq <rumpf_a@web.de>2014-02-02 00:45:47 +0100
commitefcbaa965e84588c8ff9ed5a62b79820dd5ea5ca (patch)
treee64c2adccb071a06e6be6a0103a1c6433236bb98
parentd29aa4c5ac6950a9b8c53bedeb9dd0dd9b4f64a2 (diff)
parentf9c2ec8d9238afaff285b2ba286f8c5534b05eec (diff)
downloadNim-efcbaa965e84588c8ff9ed5a62b79820dd5ea5ca.tar.gz
remove the old tester
-rw-r--r--compiler/ast.nim59
-rw-r--r--compiler/ccgutils.nim2
-rw-r--r--compiler/jsgen.nim2
-rw-r--r--compiler/semexprs.nim39
-rw-r--r--compiler/semfold.nim3
-rw-r--r--compiler/semstmts.nim2
-rw-r--r--compiler/semtypes.nim11
-rw-r--r--compiler/semtypinst.nim5
-rw-r--r--compiler/sigmatch.nim148
-rw-r--r--compiler/transf.nim2
-rw-r--r--compiler/types.nim55
-rw-r--r--compiler/vm.nim2
-rw-r--r--config/nimdoc.cfg2
-rw-r--r--config/nimdoc.tex.cfg2
-rw-r--r--devel/logging.nim210
-rw-r--r--doc/lib.txt3
-rw-r--r--lib/packages/docutils/highlite.nim8
-rw-r--r--lib/pure/algorithm.nim33
-rw-r--r--lib/pure/logging.nim267
-rw-r--r--tests/stdlib/talgorithm.nim14
-rw-r--r--web/nimrod.ini2
21 files changed, 523 insertions, 348 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 0e351a31a..7138b5f52 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -336,26 +336,52 @@ type
     tyConst, tyMutable, tyVarargs, 
     tyIter, # unused
     tyProxy # used as errornous type (for idetools)
-    tyTypeClass
-    tyParametricTypeClass # structured similarly to tyGenericInst
-                          # lastSon is the body of the type class
     
-    tyBuiltInTypeClass # Type such as the catch-all object, tuple, seq, etc
+    tyBuiltInTypeClass #\
+      # Type such as the catch-all object, tuple, seq, etc
     
-    tyCompositeTypeClass # 
+    tyUserTypeClass #\
+      # the body of a user-defined type class
+
+    tyUserTypeClassInst #\
+      # Instance of a parametric user-defined type class.
+      # Structured similarly to tyGenericInst.
+      # tyGenericInst represents concrete types, while
+      # this is still a "generic param" that will bind types
+      # and resolves them during sigmatch and instantiation.
     
-    tyAnd, tyOr, tyNot # boolean type classes such as `string|int`,`not seq`,
-                       # `Sortable and Enumable`, etc
+    tyCompositeTypeClass #\
+      # Type such as seq[Number]
+      # The notes for tyUserTypeClassInst apply here as well 
+      # sons[0]: the original expression used by the user.
+      # sons[1]: fully expanded and instantiated meta type
+      # (potentially following aliases)
     
-    tyAnything # a type class matching any type
+    tyAnd, tyOr, tyNot #\
+      # boolean type classes such as `string|int`,`not seq`,
+      # `Sortable and Enumable`, etc
     
-    tyStatic   # a value known at compile type (the underlying type is .base)
+    tyAnything #\
+      # a type class matching any type
     
-    tyFromExpr # This is a type representing an expression that depends
-               # on generic parameters (the exprsesion is stored in t.n)
-               # It will be converted to a real type only during generic
-               # instantiation and prior to this it has the potential to
-               # be any type.
+    tyStatic #\
+      # a value known at compile type (the underlying type is .base)
+    
+    tyFromExpr #\
+      # This is a type representing an expression that depends
+      # on generic parameters (the exprsesion is stored in t.n)
+      # It will be converted to a real type only during generic
+      # instantiation and prior to this it has the potential to
+      # be any type.
+
+    tyFieldAccessor #\
+      # Expressions such as Type.field (valid in contexts such
+      # as the `is` operator and magics like `high` and `low`).
+      # Could be lifted to a single argument proc returning the
+      # field value.
+      # sons[0]: type of containing object or tuple
+      # sons[1]: field type
+      # .n: nkDotExpr storing the field name
 
 const
   tyPureObject* = tyTuple
@@ -364,8 +390,9 @@ const
 
   tyUnknownTypes* = {tyError, tyFromExpr}
 
-  tyTypeClasses* = {tyTypeClass, tyBuiltInTypeClass, tyCompositeTypeClass,
-                    tyParametricTypeClass, tyAnd, tyOr, tyNot, tyAnything}
+  tyTypeClasses* = {tyBuiltInTypeClass, tyCompositeTypeClass,
+                    tyUserTypeClass, tyUserTypeClassInst,
+                    tyAnd, tyOr, tyNot, tyAnything}
 
   tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyStatic, tyExpr} + tyTypeClasses
  
diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim
index fe349174f..1129ecbef 100644
--- a/compiler/ccgutils.nim
+++ b/compiler/ccgutils.nim
@@ -87,7 +87,7 @@ proc getUniqueType*(key: PType): PType =
       gCanonicalTypes[k] = key
       result = key
   of tyTypeDesc, tyTypeClasses, tyGenericParam,
-     tyFromExpr, tyStatic:
+     tyFromExpr, tyStatic, tyFieldAccessor:
     internalError("GetUniqueType")
   of tyGenericInst, tyDistinct, tyOrdinal, tyMutable, tyConst, tyIter:
     result = getUniqueType(lastSon(key))
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 82c45059c..c0fc4131a 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -130,7 +130,7 @@ proc mapType(typ: PType): TJSTypeKind =
     result = etyObject
   of tyNil: result = etyNull
   of tyGenericInst, tyGenericParam, tyGenericBody, tyGenericInvokation,
-     tyNone, tyFromExpr, tyForward, tyEmpty,
+     tyNone, tyFromExpr, tyForward, tyEmpty, tyFieldAccessor,
      tyExpr, tyStmt, tyStatic, tyTypeDesc, tyTypeClasses:
     result = etyNone
   of tyProc: result = etyProc
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 432443dc6..a384c41fd 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -239,7 +239,8 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode =
     localError(n.info, errXExpectsTypeOrValue, opToStr[m])
   else: 
     n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType})
-    var typ = skipTypes(n.sons[1].typ, abstractVarRange+{tyTypeDesc})
+    var typ = skipTypes(n.sons[1].typ, abstractVarRange +
+                                       {tyTypeDesc, tyFieldAccessor})
     case typ.kind
     of tySequence, tyString, tyOpenArray, tyVarargs: 
       n.typ = getSysType(tyInt)
@@ -247,7 +248,7 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode =
       n.typ = typ.sons[0] # indextype
     of tyInt..tyInt64, tyChar, tyBool, tyEnum, tyUInt8, tyUInt16, tyUInt32: 
       # do not skip the range!
-      n.typ = n.sons[1].typ.skipTypes(abstractVar)
+      n.typ = n.sons[1].typ.skipTypes(abstractVar + {tyFieldAccessor})
     of tyGenericParam:
       # prepare this for resolving in semtypinst:
       # we must use copyTree here in order to avoid creating a cycle
@@ -306,7 +307,7 @@ proc isOpImpl(c: PContext, n: PNode): PNode =
     n[1].typ != nil and n[1].typ.kind == tyTypeDesc and
     n[2].kind in {nkStrLit..nkTripleStrLit, nkType}
   
-  let t1 = n[1].typ.skipTypes({tyTypeDesc})
+  let t1 = n[1].typ.skipTypes({tyTypeDesc, tyFieldAccessor})
 
   if n[2].kind in {nkStrLit..nkTripleStrLit}:
     case n[2].strVal.normalize
@@ -321,24 +322,13 @@ proc isOpImpl(c: PContext, n: PNode): PNode =
                                         t.callConv == ccClosure and 
                                         tfIterator in t.flags))
   else:
-    var match: bool
-    let t2 = n[2].typ
-    case t2.kind
-    of tyTypeClasses:
-      var m: TCandidate
-      initCandidate(c, m, t2)
-      match = matchUserTypeClass(c, m, emptyNode, t2, t1) != nil
-    of tyOrdinal:
-      var m: TCandidate
-      initCandidate(c, m, t2)
-      match = isOrdinalType(t1)
-    of tySequence, tyArray, tySet:
-      var m: TCandidate
-      initCandidate(c, m, t2)
-      match = typeRel(m, t2, t1) != isNone
-    else:
-      match = sameType(t1, t2)
- 
+    var t2 = n[2].typ.skipTypes({tyTypeDesc})
+    let lifted = liftParamType(c, skType, newNodeI(nkArgList, n.info),
+                               t2, ":anon", n.info)
+    if lifted != nil: t2 = lifted
+    var m: TCandidate
+    initCandidate(c, m, t2)
+    let match = typeRel(m, t2, t1) != isNone
     result = newIntNode(nkIntLit, ord(match))
 
   result.typ = n.typ
@@ -948,6 +938,13 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
             let foundTyp = makeTypeDesc(c, rawTyp)
             return newSymNode(copySym(tParam.sym).linkTo(foundTyp), n.info)
       return
+    of tyObject, tyTuple:
+      if ty.n.kind == nkRecList:
+        for field in ty.n.sons:
+          if field.sym.name == i:
+            n.typ = newTypeWithSons(c, tyFieldAccessor, @[ty, field.sym.typ])
+            n.typ.n = copyTree(n)
+            return n
     else:
       # echo "TYPE FIELD ACCESS"
       # debug ty
diff --git a/compiler/semfold.nim b/compiler/semfold.nim
index 78e5cdd5e..4740ddcb3 100644
--- a/compiler/semfold.nim
+++ b/compiler/semfold.nim
@@ -252,8 +252,7 @@ proc evalIs(n, a: PNode): PNode =
   else:
     # XXX semexprs.isOpImpl is slightly different and requires a context. yay.
     let t2 = n[2].typ
-    var match = if t2.kind == tyTypeClass: true
-                else: sameType(t1, t2)
+    var match = sameType(t1, t2)
     result = newIntNode(nkIntLit, ord(match))
   result.typ = n.typ
 
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index a26d89836..caa719c7e 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1247,7 +1247,7 @@ proc semStmtList(c: PContext, n: PNode): PNode =
       if n.sons[i].typ == enforceVoidContext or usesResult(n.sons[i]):
         voidContext = true
         n.typ = enforceVoidContext
-      if i != last or voidContext:
+      if i != last or voidContext or c.inTypeClass > 0:
         discardCheck(c, n.sons[i])
       else:
         n.typ = n.sons[i].typ
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index d5a938a12..408b1b62e 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -710,6 +710,11 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
     result = addImplicitGeneric(result)
   
   of tyGenericInst:
+    if paramType.lastSon.kind == tyUserTypeClass:
+      var cp = copyType(paramType, getCurrOwner(), false)
+      cp.kind = tyUserTypeClassInst
+      return addImplicitGeneric(cp)
+
     for i in 1 .. (paramType.sons.len - 2):
       var lifted = liftingWalk(paramType.sons[i])
       if lifted != nil:
@@ -731,7 +736,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
                                         allowMetaTypes = true)
     result = liftingWalk(expanded)
 
-  of tyTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot:
+  of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot:
     result = addImplicitGeneric(copyType(paramType, getCurrOwner(), true))
   
   of tyExpr:
@@ -866,7 +871,7 @@ proc semGenericParamInInvokation(c: PContext, n: PNode): PType =
 proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = 
   result = newOrPrevType(tyGenericInvokation, prev, c)
   addSonSkipIntLit(result, s.typ)
- 
+
   template addToResult(typ) =
     if typ.isNil:
       internalAssert false
@@ -923,7 +928,7 @@ proc freshType(res, prev: PType): PType {.inline.} =
 
 proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
   # if n.sonsLen == 0: return newConstraint(c, tyTypeClass)
-  result = newOrPrevType(tyTypeClass, prev, c)
+  result = newOrPrevType(tyUserTypeClass, prev, c)
   result.n = n
 
   let
diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim
index 9c6421315..1158335a8 100644
--- a/compiler/semtypinst.nim
+++ b/compiler/semtypinst.nim
@@ -360,7 +360,10 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
       if tfUnresolved in t.flags: result = result.base
     elif t.sonsLen > 0:
       result = makeTypeDesc(cl.c, replaceTypeVarsT(cl, t.sons[0]))
-  
+ 
+  of tyUserTypeClass:
+    result = t
+
   of tyGenericInst:
     result = instCopyType(cl, t)
     for i in 1 .. <result.sonsLen:
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index bb70e0d6b..d269e9e69 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -140,7 +140,7 @@ proc sumGeneric(t: PType): int =
       result = ord(t.kind == tyGenericInvokation)
       for i in 0 .. <t.len: result += t.sons[i].sumGeneric
       break
-    of tyGenericParam, tyExpr, tyStatic, tyStmt, tyTypeDesc, tyTypeClass: break
+    of tyGenericParam, tyExpr, tyStatic, tyStmt, tyTypeDesc: break
     else: return 0
 
 proc complexDisambiguation(a, b: PType): int =
@@ -399,6 +399,70 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
   else:
     result = isNone
 
+proc matchUserTypeClass*(c: PContext, m: var TCandidate,
+                         ff, a: PType): TTypeRelation =
+  #if f.n == nil:
+  #  let r = typeRel(m, f, a)
+  #  return if r == isGeneric: arg else: nil
+
+  var body = ff.skipTypes({tyUserTypeClassInst})
+
+  # var prev = PType(idTableGet(m.bindings, f))
+  # if prev != nil:
+  #   if sameType(prev, a): return arg
+  #   else: return nil
+
+  # pushInfoContext(arg.info)
+  openScope(c)
+  inc c.inTypeClass
+
+  finally:
+    dec c.inTypeClass
+    closeScope(c)
+
+  if ff.kind == tyUserTypeClassInst:
+    for i in 1 .. <(ff.len - 1):
+      var
+        typeParamName = ff.base.sons[i-1].sym.name
+        typ = ff.sons[i]
+        param = newSym(skType, typeParamName, body.sym, body.sym.info)
+        
+      param.typ = makeTypeDesc(c, typ)
+      addDecl(c, param)
+
+  for param in body.n[0]:
+    var
+      dummyName: PNode
+      dummyType: PType
+    
+    if param.kind == nkVarTy:
+      dummyName = param[0]
+      dummyType = makeVarType(c, a)
+    else:
+      dummyName = param
+      dummyType = a
+
+    internalAssert dummyName.kind == nkIdent
+    var dummyParam = newSym(skType, dummyName.ident, body.sym, body.sym.info)
+    dummyParam.typ = dummyType
+    addDecl(c, dummyParam)
+
+  var checkedBody = c.semTryExpr(c, copyTree(body.n[3]), bufferErrors = false)
+  m.errors = bufferedMsgs
+  clearBufferedMsgs()
+  if checkedBody == nil: return isNone
+
+  if checkedBody.kind == nkStmtList:
+    for stmt in checkedBody:
+      case stmt.kind
+      of nkReturnStmt: discard
+      of nkTypeSection: discard
+      of nkConstDef: discard
+      else: discard
+    
+  return isGeneric
+  # put(m.bindings, f, a)
+
 proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
   # typeRel can be used to establish various relationships between types:
   #
@@ -418,6 +482,11 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
 
   result = isNone
   assert(f != nil)
+  
+  if f.kind == tyExpr:
+    put(c.bindings, f, aOrig)
+    return isGeneric
+
   assert(aOrig != nil)
 
   # var and static arguments match regular modifier-free types
@@ -751,6 +820,12 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       else:
         return isNone
 
+  of tyUserTypeClass, tyUserTypeClassInst:
+    considerPreviousT:
+      result = matchUserTypeClass(c.c, c, f, a)
+      if result == isGeneric:
+        put(c.bindings, f, a)
+
   of tyCompositeTypeClass:
     considerPreviousT:
       if typeRel(c, f.sons[1], a) != isNone:
@@ -759,7 +834,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       else:
         return isNone
 
-  of tyGenericParam, tyTypeClass:
+  of tyGenericParam:
     var x = PType(idTableGet(c.bindings, f))
     if x == nil:
       if c.calleeSym != nil and c.calleeSym.kind == skType and
@@ -822,7 +897,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
                     else: a.sons[0]
       result = typeRel(c, prev.sons[0], toMatch)
   
-  of tyExpr, tyStmt:
+  of tyStmt:
     result = isGeneric
   
   of tyProxy:
@@ -904,57 +979,6 @@ proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType,
       result.typ = getInstantiatedType(c, arg, m, base(f))
     m.baseTypeMatch = true
 
-proc matchUserTypeClass*(c: PContext, m: var TCandidate,
-                         arg: PNode, f, a: PType): PNode =
-  if f.n == nil:
-    let r = typeRel(m, f, a)
-    return if r == isGeneric: arg else: nil
- 
-  var prev = PType(idTableGet(m.bindings, f))
-  if prev != nil:
-    if sameType(prev, a): return arg
-    else: return nil
-
-  # pushInfoContext(arg.info)
-  openScope(c)
-  inc c.inTypeClass
-
-  finally:
-    dec c.inTypeClass
-    closeScope(c)
-
-  for param in f.n[0]:
-    var
-      dummyName: PNode
-      dummyType: PType
-    
-    if param.kind == nkVarTy:
-      dummyName = param[0]
-      dummyType = makeVarType(c, a)
-    else:
-      dummyName = param
-      dummyType = a
-
-    internalAssert dummyName.kind == nkIdent
-    var dummyParam = newSym(skType, dummyName.ident, f.sym, f.sym.info)
-    dummyParam.typ = dummyType
-    addDecl(c, dummyParam)
-
-  for stmt in f.n[3]:
-    var e = c.semTryExpr(c, copyTree(stmt), bufferErrors = false)
-    m.errors = bufferedMsgs
-    clearBufferedMsgs()
-    if e == nil: return nil
-
-    case e.kind
-    of nkReturnStmt: discard
-    of nkTypeSection: discard
-    of nkConstDef: discard
-    else: discard
-  
-  result = arg
-  put(m.bindings, f, a)
-
 proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
                         argSemantized, argOrig: PNode): PNode =
   var
@@ -975,25 +999,9 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
       argType = arg.typ
  
   var
-    r: TTypeRelation
     a = if c.inTypeClass > 0: argType.skipTypes({tyTypeDesc})
         else: argType
  
-  case fMaybeStatic.kind
-  of tyTypeClass, tyParametricTypeClass:
-    if fMaybeStatic.n != nil:
-      let match = matchUserTypeClass(c, m, arg, fMaybeStatic, a)
-      if match != nil:
-        r = isGeneric
-        arg = match
-      else:
-        r = isNone
-    else:
-      r = typeRel(m, f, a)
-  of tyExpr:
-    r = isGeneric
-    put(m.bindings, f, arg.typ)
-  else:
     r = typeRel(m, f, a)
 
   case r
diff --git a/compiler/transf.nim b/compiler/transf.nim
index cda611005..deb821eff 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -389,7 +389,7 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
       result[0] = transform(c, n.sons[1])
     else: 
       result = transform(c, n.sons[1])
-  of tyGenericParam, tyOrdinal, tyTypeClass:
+  of tyGenericParam, tyOrdinal:
     result = transform(c, n.sons[1])
     # happens sometimes for generated assignments, etc.
   else: 
diff --git a/compiler/types.nim b/compiler/types.nim
index d7310596f..cd703474e 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -404,9 +404,10 @@ const
     "float", "float32", "float64", "float128",
     "uint", "uint8", "uint16", "uint32", "uint64",
     "bignum", "const ",
-    "!", "varargs[$1]", "iter[$1]", "Error Type", "TypeClass",
-    "ParametricTypeClass", "BuiltInTypeClass", "CompositeTypeClass",
-    "and", "or", "not", "any", "static", "TypeFromExpr"]
+    "!", "varargs[$1]", "iter[$1]", "Error Type",
+    "BuiltInTypeClass", "UserTypeClass",
+    "UserTypeClassInst", "CompositeTypeClass",
+    "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor"]
 
 proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
   var t = typ
@@ -434,11 +435,30 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
   of tyStatic:
     internalAssert t.len > 0
     result = "static[" & typeToString(t.sons[0]) & "]"
-  of tyTypeClass:
+  of tyUserTypeClass:
     internalAssert t.sym != nil and t.sym.owner != nil
     return t.sym.owner.name.s
   of tyBuiltInTypeClass:
-    return "TypeClass"
+    result = case t.base.kind:
+      of tyVar: "var"
+      of tyRef: "ref"
+      of tyPtr: "ptr"
+      of tySequence: "seq"
+      of tyArray: "array"
+      of tySet: "set"
+      of tyRange: "range"
+      of tyDistinct: "distinct"
+      of tyProc: "proc"
+      of tyObject: "object"
+      of tyTuple: "tuple"
+      else: (internalAssert(false); "")
+  of tyUserTypeClassInst:
+    let body = t.base
+    result = body.sym.name.s & "["
+    for i in countup(1, sonsLen(t) - 2):
+      if i > 1: add(result, ", ")
+      add(result, typeToString(t.sons[i]))
+    result.add "]"
   of tyAnd:
     result = typeToString(t.sons[0]) & " and " & typeToString(t.sons[1])
   of tyOr:
@@ -448,7 +468,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
   of tyExpr:
     internalAssert t.len == 0
     result = "expr"
-  of tyFromExpr:
+  of tyFromExpr, tyFieldAccessor:
     result = renderTree(t.n)
   of tyArray: 
     if t.sons[0].kind == tyRange: 
@@ -546,7 +566,8 @@ proc firstOrd(t: PType): BiggestInt =
     else: 
       assert(t.n.sons[0].kind == nkSym)
       result = t.n.sons[0].sym.position
-  of tyGenericInst, tyDistinct, tyConst, tyMutable, tyTypeDesc:
+  of tyGenericInst, tyDistinct, tyConst, tyMutable,
+     tyTypeDesc, tyFieldAccessor:
     result = firstOrd(lastSon(t))
   else: 
     internalError("invalid kind for first(" & $t.kind & ')')
@@ -579,7 +600,8 @@ proc lastOrd(t: PType): BiggestInt =
   of tyEnum: 
     assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym)
     result = t.n.sons[sonsLen(t.n) - 1].sym.position
-  of tyGenericInst, tyDistinct, tyConst, tyMutable, tyTypeDesc: 
+  of tyGenericInst, tyDistinct, tyConst, tyMutable,
+     tyTypeDesc, tyFieldAccessor:
     result = lastOrd(lastSon(t))
   of tyProxy: result = 0
   else: 
@@ -875,9 +897,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
   of tyGenericInvokation, tyGenericBody, tySequence,
      tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyArrayConstr,
      tyArray, tyProc, tyConst, tyMutable, tyVarargs, tyIter,
-     tyOrdinal, tyTypeClasses:
+     tyOrdinal, tyTypeClasses, tyFieldAccessor:
     cycleCheck()
-    if a.kind == tyTypeClass and a.n != nil: return a.n == b.n
+    if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n
     result = sameChildrenAux(a, b, c) and sameFlags(a, b)
     if result and a.kind == tyProc:
       result = ((IgnoreCC in c.flags) or a.callConv == b.callConv) and
@@ -1021,7 +1043,7 @@ proc typeAllowedAux(marker: var TIntSet, typ: PType, kind: TSymKind,
   of tyTypeClasses:
     result = true
   of tyGenericBody, tyGenericParam, tyGenericInvokation,
-     tyNone, tyForward, tyFromExpr:
+     tyNone, tyForward, tyFromExpr, tyFieldAccessor:
     result = false
   of tyNil:
     result = kind == skConst
@@ -1231,8 +1253,15 @@ proc getSize(typ: PType): BiggestInt =
   if result < 0: internalError("getSize: " & $typ.kind)
 
 proc containsGenericTypeIter(t: PType, closure: PObject): bool =
-  result = t.kind in GenericTypes + tyTypeClasses + {tyTypeDesc,tyFromExpr} or
-           t.kind == tyStatic and t.n == nil
+  if t.kind in GenericTypes + tyTypeClasses + {tyFromExpr}:
+    return true
+
+  if t.kind == tyTypeDesc:
+    if t.sonsLen == 0: return true
+    if containsGenericTypeIter(t.base, closure): return true
+    return false
+  
+  return t.kind == tyStatic and t.n == nil
 
 proc containsGenericType*(t: PType): bool = 
   result = iterOverType(t, containsGenericTypeIter, nil)
diff --git a/compiler/vm.nim b/compiler/vm.nim
index bc5320d9d..aec76f307 100644
--- a/compiler/vm.nim
+++ b/compiler/vm.nim
@@ -812,7 +812,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): PNode =
       let t1 = regs[rb].typ.skipTypes({tyTypeDesc})
       let t2 = c.types[regs[rc].intVal.int]
       # XXX: This should use the standard isOpImpl
-      let match = if t2.kind == tyTypeClass: true
+      let match = if t2.kind == tyUserTypeClass: true
                   else: sameType(t1, t2)
       regs[ra].intVal = ord(match)
     of opcSetLenSeq:
diff --git a/config/nimdoc.cfg b/config/nimdoc.cfg
index d47dccb63..63a3c30c4 100644
--- a/config/nimdoc.cfg
+++ b/config/nimdoc.cfg
@@ -76,7 +76,7 @@ span.LongStringLit {color: blue}
 span.CharLit {color: blue}
 span.EscapeSequence {color: black}
 span.Operator {color: black}
-span.Punctation {color: black}
+span.Punctuation {color: black}
 span.Comment, span.LongComment {font-style:italic; color: green}
 span.RegularExpression  {color: DarkViolet}
 span.TagStart {color: DarkViolet}
diff --git a/config/nimdoc.tex.cfg b/config/nimdoc.tex.cfg
index 8b59f2ee9..599ede345 100644
--- a/config/nimdoc.tex.cfg
+++ b/config/nimdoc.tex.cfg
@@ -98,7 +98,7 @@ doc.file = """
 \newcommand{\spanCharLit}[1]{#1}
 \newcommand{\spanEscapeSequence}[1]{#1}
 \newcommand{\spanOperator}[1]{#1}
-\newcommand{\spanPunctation}[1]{#1}
+\newcommand{\spanPunctuation}[1]{#1}
 \newcommand{\spanComment}[1]{\emph{#1}}
 \newcommand{\spanLongComment}[1]{\emph{#1}}
 \newcommand{\spanRegularExpression}[1]{#1}
diff --git a/devel/logging.nim b/devel/logging.nim
deleted file mode 100644
index a10478dab..000000000
--- a/devel/logging.nim
+++ /dev/null
@@ -1,210 +0,0 @@
-#
-#
-#            Nimrod's Runtime Library
-#        (c) Copyright 2012 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-## This module implements a simple logger. It is based on the following design:
-## * Runtime log formating is a bug: Sooner or later every log file is parsed.
-## * Keep it simple: If this library does not fullfill your needs, write your 
-##   own. Trying to support every logging feature just leads to bloat.
-## 
-## Format is:: 
-##
-##   DEBUG|INFO|... (2009-11-02 00:00:00)? (Component: )? Message
-##
-## 
-
-import strutils, os, times
-
-type
-  TLevel* = enum  ## logging level
-    lvlAll,       ## all levels active
-    lvlDebug,     ## debug level (and any above) active
-    lvlInfo,      ## info level (and any above) active
-    lvlWarn,      ## warn level (and any above) active
-    lvlError,     ## error level (and any above) active
-    lvlFatal,     ## fatal level (and any above) active
-    lvlNone
-
-const
-  LevelNames*: array [TLevel, string] = [
-    "DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE"
-  ]
-
-  defaultFmtStr = "" ## default string between log level and message per logger
-  verboseFmtStr = "$date $time "
-
-type
-  TLogger* = object of TObject ## abstract logger; the base type of all loggers
-    levelThreshold*: TLevel    ## only messages of level >= levelThreshold 
-                               ## should be processed
-    fmtStr: string ## = defaultFmtStr by default, see substituteLog for $date etc.
-    
-  TConsoleLogger* = object of TLogger ## logger that writes the messages to the
-                                      ## console
-  
-  TFileLogger* = object of TLogger ## logger that writes the messages to a file
-    f: TFile
-  
-  # TODO: implement rolling log, will produce filename.1, filename.2 etc.
-  TRollingFileLogger* = object of TFileLogger ## logger that writes the 
-                                              ## message to a file
-    maxLines: int # maximum number of lines    
-    curLine : int
-    baseName: string # initial filename
-    logFiles: int # how many log files already created, e.g. basename.1, basename.2...
-    
-    
-
-
-proc substituteLog*(frmt: string): string = 
-  ## converts $date to the current date
-  ## converts $time to the current time
-  ## converts $app to getAppFilename()
-  ## converts 
-  result = newStringOfCap(frmt.len + 20)
-  var i = 0
-  while i < frmt.len: 
-    if frmt[i] != '$': 
-      result.add(frmt[i])
-      inc(i)
-    else:
-      inc(i)
-      var v = ""
-      var app = getAppFilename()
-      while frmt[i] in IdentChars: 
-        v.add(toLower(frmt[i]))
-        inc(i)
-      case v
-      of "date": result.add(getDateStr())
-      of "time": result.add(getClockStr())
-      of "app":  result.add(app)
-      of "appdir": result.add(app.splitFile.dir)
-      of "appname": result.add(app.splitFile.name)
-
-
-
-method log*(L: ref TLogger, level: TLevel,
-            frmt: string, args: varargs[string, `$`]) =
-  ## override this method in custom loggers. Default implementation does
-  ## nothing.
-  nil
-  
-method log*(L: ref TConsoleLogger, level: TLevel,
-            frmt: string, args: varargs[string, `$`]) = 
-    Writeln(stdout, LevelNames[level], " ", substituteLog(L.fmtStr), frmt % args)
-
-method log*(L: ref TFileLogger, level: TLevel, 
-            frmt: string, args: varargs[string, `$`]) = 
-    Writeln(L.f, LevelNames[level], " ", substituteLog(L.fmtStr), frmt % args)
-
-proc defaultFilename*(): string = 
-  ## returns the default filename for a logger
-  var (path, name, ext) = splitFile(getAppFilename())
-  result = changeFileExt(path / name & "_" & getDateStr(), "log")
-
-      
-
-
-proc newConsoleLogger*(levelThreshold = lvlAll) : ref TConsoleLogger =
-  new result
-  result.fmtStr = defaultFmtStr
-  result.levelThreshold = levelThreshold
-
-proc newFileLogger*(filename = defaultFilename(), 
-                    mode: TFileMode = fmAppend,
-                    levelThreshold = lvlAll): ref TFileLogger = 
-  new(result)
-  result.levelThreshold = levelThreshold
-  result.f = open(filename, mode)
-  result.fmtStr = defaultFmtStr
-
-# ------
-
-proc readLogLines(logger : ref TRollingFileLogger) = nil
-  #f.readLine # TODO read all lines, update curLine
-
-
-proc newRollingFileLogger*(filename = defaultFilename(), 
-                           mode: TFileMode = fmReadWrite,
-                           levelThreshold = lvlAll,
-                           maxLines = 1000): ref TRollingFileLogger = 
-  new(result)
-  result.levelThreshold = levelThreshold
-  result.fmtStr = defaultFmtStr
-  result.maxLines = maxLines
-  result.f = open(filename, mode)
-  result.curLine = 0
-  
-  # TODO count all number files
-  # count lines in existing filename file
-  # if >= maxLines then rename to next numbered file and create new file
-  
-  #if mode in {fmReadWrite, fmReadWriteExisting}:
-  #  readLogLines(result)
-
-
-
-method log*(L: ref TRollingFileLogger, level: TLevel, 
-            frmt: string, args: varargs[string, `$`]) = 
-  # TODO 
-  # if more than maxlines, then set cursor to zero
-  
-  Writeln(L.f, LevelNames[level], " ", frmt % args)
-
-# --------
-
-var
-  level* = lvlAll  ## global log filter
-  handlers*: seq[ref TLogger] = @[] ## handlers with their own log levels
-
-proc logLoop(level: TLevel, frmt: string, args: varargs[string, `$`]) =
-  for logger in items(handlers): 
-    if level >= logger.levelThreshold:
-      log(logger, level, frmt, args)
-
-template log*(level: TLevel, frmt: string, args: varargs[string, `$`]) =
-  ## logs a message of the given level
-  bind logLoop
-  bind `%`
-  bind logging.Level
-  
-  if level >= logging.Level:
-    logLoop(level, frmt, args)
-
-template debug*(frmt: string, args: varargs[string, `$`]) =
-  ## logs a debug message
-  log(lvlDebug, frmt, args)
-
-template info*(frmt: string, args: varargs[string, `$`]) = 
-  ## logs an info message
-  log(lvlInfo, frmt, args)
-
-template warn*(frmt: string, args: varargs[string, `$`]) = 
-  ## logs a warning message
-  log(lvlWarn, frmt, args)
-
-template error*(frmt: string, args: varargs[string, `$`]) = 
-  ## logs an error message
-  log(lvlError, frmt, args)
-  
-template fatal*(frmt: string, args: varargs[string, `$`]) =  
-  ## logs a fatal error message
-  log(lvlFatal, frmt, args)
-
-
-# --------------
-
-when isMainModule:
-  var L = newConsoleLogger()
-  var fL = newFileLogger("test.log")
-  fL.fmtStr = verboseFmtStr
-  handlers.add(L)
-  handlers.add(fL)
-  info("hello", [])
-  
-
diff --git a/doc/lib.txt b/doc/lib.txt
index ba0cb0a90..3214cdae2 100644
--- a/doc/lib.txt
+++ b/doc/lib.txt
@@ -341,6 +341,9 @@ Miscellaneous
 * `endians <endians.html>`_
   This module contains helpers that deal with different byte orders.
 
+* `logging <logging.html>`_
+  This module implements a simple logger.
+
 
 Database support
 ----------------
diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim
index db7a63928..4ca0c79e0 100644
--- a/lib/packages/docutils/highlite.nim
+++ b/lib/packages/docutils/highlite.nim
@@ -19,7 +19,7 @@ type
     gtEof, gtNone, gtWhitespace, gtDecNumber, gtBinNumber, gtHexNumber, 
     gtOctNumber, gtFloatNumber, gtIdentifier, gtKeyword, gtStringLit, 
     gtLongStringLit, gtCharLit, gtEscapeSequence, # escape sequence like \xff
-    gtOperator, gtPunctation, gtComment, gtLongComment, gtRegularExpression, 
+    gtOperator, gtPunctuation, gtComment, gtLongComment, gtRegularExpression, 
     gtTagStart, gtTagEnd, gtKey, gtValue, gtRawData, gtAssembler, 
     gtPreprocessor, gtDirective, gtCommand, gtRule, gtHyperlink, gtLabel, 
     gtReference, gtOther
@@ -39,7 +39,7 @@ const
   tokenClassToStr*: array[TTokenClass, string] = ["Eof", "None", "Whitespace", 
     "DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber", 
     "Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit", 
-    "EscapeSequence", "Operator", "Punctation", "Comment", "LongComment", 
+    "EscapeSequence", "Operator", "Punctuation", "Comment", "LongComment", 
     "RegularExpression", "TagStart", "TagEnd", "Key", "Value", "RawData", 
     "Assembler", "Preprocessor", "Directive", "Command", "Rule", "Hyperlink", 
     "Label", "Reference", "Other"]
@@ -258,7 +258,7 @@ proc nimNextToken(g: var TGeneralTokenizer) =
           else: inc(pos)
     of '(', ')', '[', ']', '{', '}', '`', ':', ',', ';': 
       inc(pos)
-      g.kind = gtPunctation
+      g.kind = gtPunctuation
     of '\0': 
       g.kind = gtEof
     else: 
@@ -473,7 +473,7 @@ proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string],
         else: inc(pos)
     of '(', ')', '[', ']', '{', '}', ':', ',', ';', '.': 
       inc(pos)
-      g.kind = gtPunctation
+      g.kind = gtPunctuation
     of '\0': 
       g.kind = gtEof
     else: 
diff --git a/lib/pure/algorithm.nim b/lib/pure/algorithm.nim
index df7ae6d17..921c659de 100644
--- a/lib/pure/algorithm.nim
+++ b/lib/pure/algorithm.nim
@@ -131,3 +131,36 @@ proc sort*[T](a: var openArray[T],
       dec(m, s*2)
     s = s*2
 
+proc product*[T](x: openarray[seq[T]]): seq[seq[T]] =
+  ## produces the Cartesian product of the array. Warning: complexity
+  ## may explode.
+  result = @[]
+  if x.len == 0:
+    return
+  if x.len == 1:
+    result = @x
+    return
+  var
+    indexes = newSeq[int](x.len)
+    initial = newSeq[int](x.len)
+    index = 0
+  # replace with newSeq as soon as #853 is fixed
+  var next: seq[T] = @[]
+  next.setLen(x.len)
+  for i in 0..(x.len-1):
+    if len(x[i]) == 0: return
+    initial[i] = len(x[i])-1
+  indexes = initial
+  while true:
+    while indexes[index] == -1:
+      indexes[index] = initial[index]
+      index +=1
+      if index == x.len: return
+      indexes[index] -=1
+    for ni, i in indexes:
+      next[ni] = x[ni][i]
+    var res: seq[T]
+    shallowCopy(res, next)
+    result.add(res)
+    index = 0
+    indexes[index] -=1
diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim
new file mode 100644
index 000000000..8158cbc2a
--- /dev/null
+++ b/lib/pure/logging.nim
@@ -0,0 +1,267 @@
+#
+#
+#            Nimrod's Runtime Library
+#        (c) Copyright 2014 Andreas Rumpf, Dominik Picheta
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements a simple logger. It has been designed to be as simple
+## as possible to avoid bloat, if this library does not fullfill your needs,
+## write your own.
+## 
+## Format strings support the following variables which must be prefixed with
+## the dollar operator (``$``):
+##
+## ============  =======================
+##   Operator     Output
+## ============  =======================
+## $date         Current date
+## $time         Current time
+## $app          ``os.getAppFilename()``
+## ============  =======================
+## 
+##
+## The following example demonstrates logging to three different handlers
+## simultaneously:
+##
+## .. code-block:: nimrod
+##     
+##    var L = newConsoleLogger()
+##    var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
+##    var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
+##    handlers.add(L)
+##    handlers.add(fL)
+##    handlers.add(rL)
+##    info("920410:52 accepted")
+##    warn("4 8 15 16 23 4-- Error")
+##    error("922044:16 SYSTEM FAILURE")
+##    fatal("SYSTEM FAILURE SYSTEM FAILURE")
+
+import strutils, os, times
+
+type
+  TLevel* = enum  ## logging level
+    lvlAll,       ## all levels active
+    lvlDebug,     ## debug level (and any above) active
+    lvlInfo,      ## info level (and any above) active
+    lvlWarn,      ## warn level (and any above) active
+    lvlError,     ## error level (and any above) active
+    lvlFatal,     ## fatal level (and any above) active
+    lvlNone       ## no levels active
+
+const
+  LevelNames*: array [TLevel, string] = [
+    "DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE"
+  ]
+
+  defaultFmtStr* = "" ## default string between log level and message per logger
+  verboseFmtStr* = "$date $time "
+
+type
+  PLogger* = ref object of PObject ## abstract logger; the base type of all loggers
+    levelThreshold*: TLevel    ## only messages of level >= levelThreshold 
+                               ## should be processed
+    fmtStr: string ## = defaultFmtStr by default, see substituteLog for $date etc.
+    
+  PConsoleLogger* = ref object of PLogger ## logger that writes the messages to the
+                                      ## console
+  
+  PFileLogger* = ref object of PLogger ## logger that writes the messages to a file
+    f: TFile
+  
+  PRollingFileLogger* = ref object of PFileLogger ## logger that writes the 
+                                                  ## messages to a file and
+                                                  ## performs log rotation
+    maxLines: int # maximum number of lines    
+    curLine : int
+    baseName: string # initial filename
+    baseMode: TFileMode # initial file mode
+    logFiles: int # how many log files already created, e.g. basename.1, basename.2...
+
+proc substituteLog(frmt: string): string = 
+  ## converts $date to the current date
+  ## converts $time to the current time
+  ## converts $app to getAppFilename()
+  ## converts 
+  result = newStringOfCap(frmt.len + 20)
+  var i = 0
+  while i < frmt.len: 
+    if frmt[i] != '$': 
+      result.add(frmt[i])
+      inc(i)
+    else:
+      inc(i)
+      var v = ""
+      var app = getAppFilename()
+      while frmt[i] in IdentChars: 
+        v.add(toLower(frmt[i]))
+        inc(i)
+      case v
+      of "date": result.add(getDateStr())
+      of "time": result.add(getClockStr())
+      of "app":  result.add(app)
+      of "appdir": result.add(app.splitFile.dir)
+      of "appname": result.add(app.splitFile.name)
+
+method log*(logger: PLogger, level: TLevel,
+            frmt: string, args: varargs[string, `$`]) =
+  ## Override this method in custom loggers. Default implementation does
+  ## nothing.
+  nil
+  
+method log*(logger: PConsoleLogger, level: TLevel,
+            frmt: string, args: varargs[string, `$`]) =
+  ## Logs to the console using ``logger`` only.
+  if level >= logger.levelThreshold:
+    writeln(stdout, LevelNames[level], " ", substituteLog(logger.fmtStr),
+            frmt % args)
+
+method log*(logger: PFileLogger, level: TLevel, 
+            frmt: string, args: varargs[string, `$`]) =
+  ## Logs to a file using ``logger`` only.
+  if level >= logger.levelThreshold:
+    writeln(logger.f, LevelNames[level], " ",
+            substituteLog(logger.fmtStr), frmt % args)
+
+proc defaultFilename*(): string = 
+  ## Returns the default filename for a logger.
+  var (path, name, ext) = splitFile(getAppFilename())
+  result = changeFileExt(path / name, "log")
+
+proc newConsoleLogger*(levelThreshold = lvlAll, fmtStr = defaultFmtStr): PConsoleLogger =
+  ## Creates a new console logger. This logger logs to the console.
+  new result
+  result.fmtStr = fmtStr
+  result.levelThreshold = levelThreshold
+
+proc newFileLogger*(filename = defaultFilename(), 
+                    mode: TFileMode = fmAppend,
+                    levelThreshold = lvlAll,
+                    fmtStr = defaultFmtStr): PFileLogger = 
+  ## Creates a new file logger. This logger logs to a file.
+  new(result)
+  result.levelThreshold = levelThreshold
+  result.f = open(filename, mode)
+  result.fmtStr = fmtStr
+
+# ------
+
+proc countLogLines(logger: PRollingFileLogger): int =
+  result = 0
+  for line in logger.f.lines():
+    result.inc()
+
+proc countFiles(filename: string): int =
+  # Example: file.log.1
+  result = 0
+  let (dir, name, ext) = splitFile(filename)
+  for kind, path in walkDir(dir):
+    if kind == pcFile:
+      let llfn = name & ext & ExtSep
+      if path.extractFilename.startsWith(llfn):
+        let numS = path.extractFilename[llfn.len .. -1]
+        try:
+          let num = parseInt(numS)
+          if num > result:
+            result = num
+        except EInvalidValue: discard
+
+proc newRollingFileLogger*(filename = defaultFilename(), 
+                           mode: TFileMode = fmReadWrite,
+                           levelThreshold = lvlAll,
+                           fmtStr = defaultFmtStr,
+                           maxLines = 1000): PRollingFileLogger =
+  ## Creates a new rolling file logger. Once a file reaches ``maxLines`` lines
+  ## a new log file will be started and the old will be renamed.
+  new(result)
+  result.levelThreshold = levelThreshold
+  result.fmtStr = defaultFmtStr
+  result.maxLines = maxLines
+  result.f = open(filename, mode)
+  result.curLine = 0
+  result.baseName = filename
+  result.baseMode = mode
+  
+  result.logFiles = countFiles(filename)
+  
+  if mode == fmAppend:
+    # We need to get a line count because we will be appending to the file.
+    result.curLine = countLogLines(result)
+
+proc rotate(logger: PRollingFileLogger) =
+  let (dir, name, ext) = splitFile(logger.baseName)
+  for i in countdown(logger.logFiles, 0):
+    let srcSuff = if i != 0: ExtSep & $i else: ""
+    moveFile(dir / (name & ext & srcSuff),
+             dir / (name & ext & ExtSep & $(i+1)))
+
+method log*(logger: PRollingFileLogger, level: TLevel, 
+            frmt: string, args: varargs[string, `$`]) =
+  ## Logs to a file using rolling ``logger`` only.
+  if level >= logger.levelThreshold:
+    if logger.curLine >= logger.maxLines:
+      logger.f.close()
+      rotate(logger)
+      logger.logFiles.inc
+      logger.curLine = 0
+      logger.f = open(logger.baseName, logger.baseMode)
+    
+    writeln(logger.f, LevelNames[level], " ", frmt % args)
+    logger.curLine.inc
+
+# --------
+
+var
+  level* = lvlAll  ## global log filter
+  handlers*: seq[PLogger] = @[] ## handlers with their own log levels
+
+proc logLoop(level: TLevel, frmt: string, args: varargs[string, `$`]) =
+  for logger in items(handlers): 
+    if level >= logger.levelThreshold:
+      log(logger, level, frmt, args)
+
+template log*(level: TLevel, frmt: string, args: varargs[string, `$`]) =
+  ## Logs a message to all registered handlers at the given level.
+  bind logLoop
+  bind `%`
+  bind logging.Level
+  
+  if level >= logging.Level:
+    logLoop(level, frmt, args)
+
+template debug*(frmt: string, args: varargs[string, `$`]) =
+  ## Logs a debug message to all registered handlers.
+  log(lvlDebug, frmt, args)
+
+template info*(frmt: string, args: varargs[string, `$`]) = 
+  ## Logs an info message to all registered handlers.
+  log(lvlInfo, frmt, args)
+
+template warn*(frmt: string, args: varargs[string, `$`]) = 
+  ## Logs a warning message to all registered handlers.
+  log(lvlWarn, frmt, args)
+
+template error*(frmt: string, args: varargs[string, `$`]) = 
+  ## Logs an error message to all registered handlers.
+  log(lvlError, frmt, args)
+  
+template fatal*(frmt: string, args: varargs[string, `$`]) =  
+  ## Logs a fatal error message to all registered handlers.
+  log(lvlFatal, frmt, args)
+
+
+# --------------
+
+when isMainModule:
+  var L = newConsoleLogger()
+  var fL = newFileLogger("test.log", fmtStr = verboseFmtStr)
+  var rL = newRollingFileLogger("rolling.log", fmtStr = verboseFmtStr)
+  handlers.add(L)
+  handlers.add(fL)
+  handlers.add(rL)
+  for i in 0 .. 25:
+    info("hello" & $i, [])
+  
+
diff --git a/tests/stdlib/talgorithm.nim b/tests/stdlib/talgorithm.nim
new file mode 100644
index 000000000..37de1262f
--- /dev/null
+++ b/tests/stdlib/talgorithm.nim
@@ -0,0 +1,14 @@
+import unittest
+import algorithm
+
+suite "product":
+  test "empty input":
+    check product[int](newSeq[seq[int]]()) == newSeq[seq[int]]()
+  test "bit more empty input":
+    check product[int](@[newSeq[int](), @[], @[]]) == newSeq[seq[int]]()
+  test "a simple case of one element":
+    check product(@[@[1,2]]) == @[@[1,2]]
+  test "two elements":
+    check product(@[@[1,2], @[3,4]]) == @[@[2,4],@[1,4],@[2,3],@[1,3]]
+  test "three elements":
+    check product(@[@[1,2], @[3,4], @[5,6]]) == @[@[2,4,6],@[1,4,6],@[2,3,6],@[1,3,6], @[2,4,5],@[1,4,5],@[2,3,5],@[1,3,5]]
diff --git a/web/nimrod.ini b/web/nimrod.ini
index 9af3bc226..71e36dcdc 100644
--- a/web/nimrod.ini
+++ b/web/nimrod.ini
@@ -62,7 +62,7 @@ srcdoc2: "pure/ftpclient;pure/memfiles;pure/subexes;pure/collections/critbits"
 srcdoc2: "pure/asyncio;pure/actors;core/locks;pure/oids;pure/endians;pure/uri"
 srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite"
 srcdoc2: "packages/docutils/rst;packages/docutils/rstast"
-srcdoc2: "packages/docutils/rstgen"
+srcdoc2: "packages/docutils/rstgen;pure/logging"
 
 webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup"
 webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc"