remove the old tester

This commit is contained in:
Araq 2014-02-02 00:45:47 +01:00
commit efcbaa965e
21 changed files with 523 additions and 348 deletions

View file

@ -336,27 +336,53 @@ type
tyConst, tyMutable, tyVarargs, tyConst, tyMutable, tyVarargs,
tyIter, # unused tyIter, # unused
tyProxy # used as errornous type (for idetools) 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
tyAnd, tyOr, tyNot # boolean type classes such as `string|int`,`not seq`, 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.
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)
tyAnd, tyOr, tyNot #\
# boolean type classes such as `string|int`,`not seq`,
# `Sortable and Enumable`, etc # `Sortable and Enumable`, etc
tyAnything # a type class matching any type tyAnything #\
# a type class matching any type
tyStatic # a value known at compile type (the underlying type is .base) tyStatic #\
# a value known at compile type (the underlying type is .base)
tyFromExpr # This is a type representing an expression that depends tyFromExpr #\
# This is a type representing an expression that depends
# on generic parameters (the exprsesion is stored in t.n) # on generic parameters (the exprsesion is stored in t.n)
# It will be converted to a real type only during generic # It will be converted to a real type only during generic
# instantiation and prior to this it has the potential to # instantiation and prior to this it has the potential to
# be any type. # 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 const
tyPureObject* = tyTuple tyPureObject* = tyTuple
GcTypeKinds* = {tyRef, tySequence, tyString} GcTypeKinds* = {tyRef, tySequence, tyString}
@ -364,8 +390,9 @@ const
tyUnknownTypes* = {tyError, tyFromExpr} tyUnknownTypes* = {tyError, tyFromExpr}
tyTypeClasses* = {tyTypeClass, tyBuiltInTypeClass, tyCompositeTypeClass, tyTypeClasses* = {tyBuiltInTypeClass, tyCompositeTypeClass,
tyParametricTypeClass, tyAnd, tyOr, tyNot, tyAnything} tyUserTypeClass, tyUserTypeClassInst,
tyAnd, tyOr, tyNot, tyAnything}
tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyStatic, tyExpr} + tyTypeClasses tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyStatic, tyExpr} + tyTypeClasses

View file

@ -87,7 +87,7 @@ proc getUniqueType*(key: PType): PType =
gCanonicalTypes[k] = key gCanonicalTypes[k] = key
result = key result = key
of tyTypeDesc, tyTypeClasses, tyGenericParam, of tyTypeDesc, tyTypeClasses, tyGenericParam,
tyFromExpr, tyStatic: tyFromExpr, tyStatic, tyFieldAccessor:
internalError("GetUniqueType") internalError("GetUniqueType")
of tyGenericInst, tyDistinct, tyOrdinal, tyMutable, tyConst, tyIter: of tyGenericInst, tyDistinct, tyOrdinal, tyMutable, tyConst, tyIter:
result = getUniqueType(lastSon(key)) result = getUniqueType(lastSon(key))

View file

@ -130,7 +130,7 @@ proc mapType(typ: PType): TJSTypeKind =
result = etyObject result = etyObject
of tyNil: result = etyNull of tyNil: result = etyNull
of tyGenericInst, tyGenericParam, tyGenericBody, tyGenericInvokation, of tyGenericInst, tyGenericParam, tyGenericBody, tyGenericInvokation,
tyNone, tyFromExpr, tyForward, tyEmpty, tyNone, tyFromExpr, tyForward, tyEmpty, tyFieldAccessor,
tyExpr, tyStmt, tyStatic, tyTypeDesc, tyTypeClasses: tyExpr, tyStmt, tyStatic, tyTypeDesc, tyTypeClasses:
result = etyNone result = etyNone
of tyProc: result = etyProc of tyProc: result = etyProc

View file

@ -239,7 +239,8 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode =
localError(n.info, errXExpectsTypeOrValue, opToStr[m]) localError(n.info, errXExpectsTypeOrValue, opToStr[m])
else: else:
n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType}) 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 case typ.kind
of tySequence, tyString, tyOpenArray, tyVarargs: of tySequence, tyString, tyOpenArray, tyVarargs:
n.typ = getSysType(tyInt) n.typ = getSysType(tyInt)
@ -247,7 +248,7 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode =
n.typ = typ.sons[0] # indextype n.typ = typ.sons[0] # indextype
of tyInt..tyInt64, tyChar, tyBool, tyEnum, tyUInt8, tyUInt16, tyUInt32: of tyInt..tyInt64, tyChar, tyBool, tyEnum, tyUInt8, tyUInt16, tyUInt32:
# do not skip the range! # do not skip the range!
n.typ = n.sons[1].typ.skipTypes(abstractVar) n.typ = n.sons[1].typ.skipTypes(abstractVar + {tyFieldAccessor})
of tyGenericParam: of tyGenericParam:
# prepare this for resolving in semtypinst: # prepare this for resolving in semtypinst:
# we must use copyTree here in order to avoid creating a cycle # 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[1].typ != nil and n[1].typ.kind == tyTypeDesc and
n[2].kind in {nkStrLit..nkTripleStrLit, nkType} 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}: if n[2].kind in {nkStrLit..nkTripleStrLit}:
case n[2].strVal.normalize case n[2].strVal.normalize
@ -321,24 +322,13 @@ proc isOpImpl(c: PContext, n: PNode): PNode =
t.callConv == ccClosure and t.callConv == ccClosure and
tfIterator in t.flags)) tfIterator in t.flags))
else: else:
var match: bool var t2 = n[2].typ.skipTypes({tyTypeDesc})
let t2 = n[2].typ let lifted = liftParamType(c, skType, newNodeI(nkArgList, n.info),
case t2.kind t2, ":anon", n.info)
of tyTypeClasses: if lifted != nil: t2 = lifted
var m: TCandidate var m: TCandidate
initCandidate(c, m, t2) initCandidate(c, m, t2)
match = matchUserTypeClass(c, m, emptyNode, t2, t1) != nil let match = typeRel(m, t2, t1) != isNone
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)
result = newIntNode(nkIntLit, ord(match)) result = newIntNode(nkIntLit, ord(match))
result.typ = n.typ result.typ = n.typ
@ -948,6 +938,13 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
let foundTyp = makeTypeDesc(c, rawTyp) let foundTyp = makeTypeDesc(c, rawTyp)
return newSymNode(copySym(tParam.sym).linkTo(foundTyp), n.info) return newSymNode(copySym(tParam.sym).linkTo(foundTyp), n.info)
return 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: else:
# echo "TYPE FIELD ACCESS" # echo "TYPE FIELD ACCESS"
# debug ty # debug ty

View file

@ -252,8 +252,7 @@ proc evalIs(n, a: PNode): PNode =
else: else:
# XXX semexprs.isOpImpl is slightly different and requires a context. yay. # XXX semexprs.isOpImpl is slightly different and requires a context. yay.
let t2 = n[2].typ let t2 = n[2].typ
var match = if t2.kind == tyTypeClass: true var match = sameType(t1, t2)
else: sameType(t1, t2)
result = newIntNode(nkIntLit, ord(match)) result = newIntNode(nkIntLit, ord(match))
result.typ = n.typ result.typ = n.typ

View file

@ -1247,7 +1247,7 @@ proc semStmtList(c: PContext, n: PNode): PNode =
if n.sons[i].typ == enforceVoidContext or usesResult(n.sons[i]): if n.sons[i].typ == enforceVoidContext or usesResult(n.sons[i]):
voidContext = true voidContext = true
n.typ = enforceVoidContext n.typ = enforceVoidContext
if i != last or voidContext: if i != last or voidContext or c.inTypeClass > 0:
discardCheck(c, n.sons[i]) discardCheck(c, n.sons[i])
else: else:
n.typ = n.sons[i].typ n.typ = n.sons[i].typ

View file

@ -710,6 +710,11 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
result = addImplicitGeneric(result) result = addImplicitGeneric(result)
of tyGenericInst: 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): for i in 1 .. (paramType.sons.len - 2):
var lifted = liftingWalk(paramType.sons[i]) var lifted = liftingWalk(paramType.sons[i])
if lifted != nil: if lifted != nil:
@ -731,7 +736,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
allowMetaTypes = true) allowMetaTypes = true)
result = liftingWalk(expanded) result = liftingWalk(expanded)
of tyTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot: of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot:
result = addImplicitGeneric(copyType(paramType, getCurrOwner(), true)) result = addImplicitGeneric(copyType(paramType, getCurrOwner(), true))
of tyExpr: of tyExpr:
@ -923,7 +928,7 @@ proc freshType(res, prev: PType): PType {.inline.} =
proc semTypeClass(c: PContext, n: PNode, prev: PType): PType = proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
# if n.sonsLen == 0: return newConstraint(c, tyTypeClass) # if n.sonsLen == 0: return newConstraint(c, tyTypeClass)
result = newOrPrevType(tyTypeClass, prev, c) result = newOrPrevType(tyUserTypeClass, prev, c)
result.n = n result.n = n
let let

View file

@ -361,6 +361,9 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
elif t.sonsLen > 0: elif t.sonsLen > 0:
result = makeTypeDesc(cl.c, replaceTypeVarsT(cl, t.sons[0])) result = makeTypeDesc(cl.c, replaceTypeVarsT(cl, t.sons[0]))
of tyUserTypeClass:
result = t
of tyGenericInst: of tyGenericInst:
result = instCopyType(cl, t) result = instCopyType(cl, t)
for i in 1 .. <result.sonsLen: for i in 1 .. <result.sonsLen:

View file

@ -140,7 +140,7 @@ proc sumGeneric(t: PType): int =
result = ord(t.kind == tyGenericInvokation) result = ord(t.kind == tyGenericInvokation)
for i in 0 .. <t.len: result += t.sons[i].sumGeneric for i in 0 .. <t.len: result += t.sons[i].sumGeneric
break break
of tyGenericParam, tyExpr, tyStatic, tyStmt, tyTypeDesc, tyTypeClass: break of tyGenericParam, tyExpr, tyStatic, tyStmt, tyTypeDesc: break
else: return 0 else: return 0
proc complexDisambiguation(a, b: PType): int = proc complexDisambiguation(a, b: PType): int =
@ -399,6 +399,70 @@ proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
else: else:
result = isNone 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 = proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
# typeRel can be used to establish various relationships between types: # 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 result = isNone
assert(f != nil) assert(f != nil)
if f.kind == tyExpr:
put(c.bindings, f, aOrig)
return isGeneric
assert(aOrig != nil) assert(aOrig != nil)
# var and static arguments match regular modifier-free types # 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: else:
return isNone return isNone
of tyUserTypeClass, tyUserTypeClassInst:
considerPreviousT:
result = matchUserTypeClass(c.c, c, f, a)
if result == isGeneric:
put(c.bindings, f, a)
of tyCompositeTypeClass: of tyCompositeTypeClass:
considerPreviousT: considerPreviousT:
if typeRel(c, f.sons[1], a) != isNone: if typeRel(c, f.sons[1], a) != isNone:
@ -759,7 +834,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
else: else:
return isNone return isNone
of tyGenericParam, tyTypeClass: of tyGenericParam:
var x = PType(idTableGet(c.bindings, f)) var x = PType(idTableGet(c.bindings, f))
if x == nil: if x == nil:
if c.calleeSym != nil and c.calleeSym.kind == skType and 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] else: a.sons[0]
result = typeRel(c, prev.sons[0], toMatch) result = typeRel(c, prev.sons[0], toMatch)
of tyExpr, tyStmt: of tyStmt:
result = isGeneric result = isGeneric
of tyProxy: of tyProxy:
@ -904,57 +979,6 @@ proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType,
result.typ = getInstantiatedType(c, arg, m, base(f)) result.typ = getInstantiatedType(c, arg, m, base(f))
m.baseTypeMatch = true 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, proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
argSemantized, argOrig: PNode): PNode = argSemantized, argOrig: PNode): PNode =
var var
@ -975,25 +999,9 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
argType = arg.typ argType = arg.typ
var var
r: TTypeRelation
a = if c.inTypeClass > 0: argType.skipTypes({tyTypeDesc}) a = if c.inTypeClass > 0: argType.skipTypes({tyTypeDesc})
else: argType 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) r = typeRel(m, f, a)
case r case r

View file

@ -389,7 +389,7 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
result[0] = transform(c, n.sons[1]) result[0] = transform(c, n.sons[1])
else: else:
result = transform(c, n.sons[1]) result = transform(c, n.sons[1])
of tyGenericParam, tyOrdinal, tyTypeClass: of tyGenericParam, tyOrdinal:
result = transform(c, n.sons[1]) result = transform(c, n.sons[1])
# happens sometimes for generated assignments, etc. # happens sometimes for generated assignments, etc.
else: else:

View file

@ -404,9 +404,10 @@ const
"float", "float32", "float64", "float128", "float", "float32", "float64", "float128",
"uint", "uint8", "uint16", "uint32", "uint64", "uint", "uint8", "uint16", "uint32", "uint64",
"bignum", "const ", "bignum", "const ",
"!", "varargs[$1]", "iter[$1]", "Error Type", "TypeClass", "!", "varargs[$1]", "iter[$1]", "Error Type",
"ParametricTypeClass", "BuiltInTypeClass", "CompositeTypeClass", "BuiltInTypeClass", "UserTypeClass",
"and", "or", "not", "any", "static", "TypeFromExpr"] "UserTypeClassInst", "CompositeTypeClass",
"and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor"]
proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
var t = typ var t = typ
@ -434,11 +435,30 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
of tyStatic: of tyStatic:
internalAssert t.len > 0 internalAssert t.len > 0
result = "static[" & typeToString(t.sons[0]) & "]" result = "static[" & typeToString(t.sons[0]) & "]"
of tyTypeClass: of tyUserTypeClass:
internalAssert t.sym != nil and t.sym.owner != nil internalAssert t.sym != nil and t.sym.owner != nil
return t.sym.owner.name.s return t.sym.owner.name.s
of tyBuiltInTypeClass: 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: of tyAnd:
result = typeToString(t.sons[0]) & " and " & typeToString(t.sons[1]) result = typeToString(t.sons[0]) & " and " & typeToString(t.sons[1])
of tyOr: of tyOr:
@ -448,7 +468,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
of tyExpr: of tyExpr:
internalAssert t.len == 0 internalAssert t.len == 0
result = "expr" result = "expr"
of tyFromExpr: of tyFromExpr, tyFieldAccessor:
result = renderTree(t.n) result = renderTree(t.n)
of tyArray: of tyArray:
if t.sons[0].kind == tyRange: if t.sons[0].kind == tyRange:
@ -546,7 +566,8 @@ proc firstOrd(t: PType): BiggestInt =
else: else:
assert(t.n.sons[0].kind == nkSym) assert(t.n.sons[0].kind == nkSym)
result = t.n.sons[0].sym.position result = t.n.sons[0].sym.position
of tyGenericInst, tyDistinct, tyConst, tyMutable, tyTypeDesc: of tyGenericInst, tyDistinct, tyConst, tyMutable,
tyTypeDesc, tyFieldAccessor:
result = firstOrd(lastSon(t)) result = firstOrd(lastSon(t))
else: else:
internalError("invalid kind for first(" & $t.kind & ')') internalError("invalid kind for first(" & $t.kind & ')')
@ -579,7 +600,8 @@ proc lastOrd(t: PType): BiggestInt =
of tyEnum: of tyEnum:
assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym) assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym)
result = t.n.sons[sonsLen(t.n) - 1].sym.position 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)) result = lastOrd(lastSon(t))
of tyProxy: result = 0 of tyProxy: result = 0
else: else:
@ -875,9 +897,9 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
of tyGenericInvokation, tyGenericBody, tySequence, of tyGenericInvokation, tyGenericBody, tySequence,
tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyArrayConstr, tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyArrayConstr,
tyArray, tyProc, tyConst, tyMutable, tyVarargs, tyIter, tyArray, tyProc, tyConst, tyMutable, tyVarargs, tyIter,
tyOrdinal, tyTypeClasses: tyOrdinal, tyTypeClasses, tyFieldAccessor:
cycleCheck() 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) result = sameChildrenAux(a, b, c) and sameFlags(a, b)
if result and a.kind == tyProc: if result and a.kind == tyProc:
result = ((IgnoreCC in c.flags) or a.callConv == b.callConv) and 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: of tyTypeClasses:
result = true result = true
of tyGenericBody, tyGenericParam, tyGenericInvokation, of tyGenericBody, tyGenericParam, tyGenericInvokation,
tyNone, tyForward, tyFromExpr: tyNone, tyForward, tyFromExpr, tyFieldAccessor:
result = false result = false
of tyNil: of tyNil:
result = kind == skConst result = kind == skConst
@ -1231,8 +1253,15 @@ proc getSize(typ: PType): BiggestInt =
if result < 0: internalError("getSize: " & $typ.kind) if result < 0: internalError("getSize: " & $typ.kind)
proc containsGenericTypeIter(t: PType, closure: PObject): bool = proc containsGenericTypeIter(t: PType, closure: PObject): bool =
result = t.kind in GenericTypes + tyTypeClasses + {tyTypeDesc,tyFromExpr} or if t.kind in GenericTypes + tyTypeClasses + {tyFromExpr}:
t.kind == tyStatic and t.n == nil 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 = proc containsGenericType*(t: PType): bool =
result = iterOverType(t, containsGenericTypeIter, nil) result = iterOverType(t, containsGenericTypeIter, nil)

View file

@ -812,7 +812,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): PNode =
let t1 = regs[rb].typ.skipTypes({tyTypeDesc}) let t1 = regs[rb].typ.skipTypes({tyTypeDesc})
let t2 = c.types[regs[rc].intVal.int] let t2 = c.types[regs[rc].intVal.int]
# XXX: This should use the standard isOpImpl # 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) else: sameType(t1, t2)
regs[ra].intVal = ord(match) regs[ra].intVal = ord(match)
of opcSetLenSeq: of opcSetLenSeq:

View file

@ -76,7 +76,7 @@ span.LongStringLit {color: blue}
span.CharLit {color: blue} span.CharLit {color: blue}
span.EscapeSequence {color: black} span.EscapeSequence {color: black}
span.Operator {color: black} span.Operator {color: black}
span.Punctation {color: black} span.Punctuation {color: black}
span.Comment, span.LongComment {font-style:italic; color: green} span.Comment, span.LongComment {font-style:italic; color: green}
span.RegularExpression {color: DarkViolet} span.RegularExpression {color: DarkViolet}
span.TagStart {color: DarkViolet} span.TagStart {color: DarkViolet}

View file

@ -98,7 +98,7 @@ doc.file = """
\newcommand{\spanCharLit}[1]{#1} \newcommand{\spanCharLit}[1]{#1}
\newcommand{\spanEscapeSequence}[1]{#1} \newcommand{\spanEscapeSequence}[1]{#1}
\newcommand{\spanOperator}[1]{#1} \newcommand{\spanOperator}[1]{#1}
\newcommand{\spanPunctation}[1]{#1} \newcommand{\spanPunctuation}[1]{#1}
\newcommand{\spanComment}[1]{\emph{#1}} \newcommand{\spanComment}[1]{\emph{#1}}
\newcommand{\spanLongComment}[1]{\emph{#1}} \newcommand{\spanLongComment}[1]{\emph{#1}}
\newcommand{\spanRegularExpression}[1]{#1} \newcommand{\spanRegularExpression}[1]{#1}

View file

@ -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", [])

View file

@ -341,6 +341,9 @@ Miscellaneous
* `endians <endians.html>`_ * `endians <endians.html>`_
This module contains helpers that deal with different byte orders. This module contains helpers that deal with different byte orders.
* `logging <logging.html>`_
This module implements a simple logger.
Database support Database support
---------------- ----------------

View file

@ -19,7 +19,7 @@ type
gtEof, gtNone, gtWhitespace, gtDecNumber, gtBinNumber, gtHexNumber, gtEof, gtNone, gtWhitespace, gtDecNumber, gtBinNumber, gtHexNumber,
gtOctNumber, gtFloatNumber, gtIdentifier, gtKeyword, gtStringLit, gtOctNumber, gtFloatNumber, gtIdentifier, gtKeyword, gtStringLit,
gtLongStringLit, gtCharLit, gtEscapeSequence, # escape sequence like \xff gtLongStringLit, gtCharLit, gtEscapeSequence, # escape sequence like \xff
gtOperator, gtPunctation, gtComment, gtLongComment, gtRegularExpression, gtOperator, gtPunctuation, gtComment, gtLongComment, gtRegularExpression,
gtTagStart, gtTagEnd, gtKey, gtValue, gtRawData, gtAssembler, gtTagStart, gtTagEnd, gtKey, gtValue, gtRawData, gtAssembler,
gtPreprocessor, gtDirective, gtCommand, gtRule, gtHyperlink, gtLabel, gtPreprocessor, gtDirective, gtCommand, gtRule, gtHyperlink, gtLabel,
gtReference, gtOther gtReference, gtOther
@ -39,7 +39,7 @@ const
tokenClassToStr*: array[TTokenClass, string] = ["Eof", "None", "Whitespace", tokenClassToStr*: array[TTokenClass, string] = ["Eof", "None", "Whitespace",
"DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber", "DecNumber", "BinNumber", "HexNumber", "OctNumber", "FloatNumber",
"Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit", "Identifier", "Keyword", "StringLit", "LongStringLit", "CharLit",
"EscapeSequence", "Operator", "Punctation", "Comment", "LongComment", "EscapeSequence", "Operator", "Punctuation", "Comment", "LongComment",
"RegularExpression", "TagStart", "TagEnd", "Key", "Value", "RawData", "RegularExpression", "TagStart", "TagEnd", "Key", "Value", "RawData",
"Assembler", "Preprocessor", "Directive", "Command", "Rule", "Hyperlink", "Assembler", "Preprocessor", "Directive", "Command", "Rule", "Hyperlink",
"Label", "Reference", "Other"] "Label", "Reference", "Other"]
@ -258,7 +258,7 @@ proc nimNextToken(g: var TGeneralTokenizer) =
else: inc(pos) else: inc(pos)
of '(', ')', '[', ']', '{', '}', '`', ':', ',', ';': of '(', ')', '[', ']', '{', '}', '`', ':', ',', ';':
inc(pos) inc(pos)
g.kind = gtPunctation g.kind = gtPunctuation
of '\0': of '\0':
g.kind = gtEof g.kind = gtEof
else: else:
@ -473,7 +473,7 @@ proc clikeNextToken(g: var TGeneralTokenizer, keywords: openArray[string],
else: inc(pos) else: inc(pos)
of '(', ')', '[', ']', '{', '}', ':', ',', ';', '.': of '(', ')', '[', ']', '{', '}', ':', ',', ';', '.':
inc(pos) inc(pos)
g.kind = gtPunctation g.kind = gtPunctuation
of '\0': of '\0':
g.kind = gtEof g.kind = gtEof
else: else:

View file

@ -131,3 +131,36 @@ proc sort*[T](a: var openArray[T],
dec(m, s*2) dec(m, s*2)
s = 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

267
lib/pure/logging.nim Normal file
View file

@ -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, [])

View file

@ -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]]

View file

@ -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/asyncio;pure/actors;core/locks;pure/oids;pure/endians;pure/uri"
srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite" srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite"
srcdoc2: "packages/docutils/rst;packages/docutils/rstast" 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/libcurl;pure/md5;wrappers/mysql;wrappers/iup"
webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc"