diff options
152 files changed, 3011 insertions, 3096 deletions
diff --git a/changelog.md b/changelog.md new file mode 100644 index 000000000..9f6a83a37 --- /dev/null +++ b/changelog.md @@ -0,0 +1,7 @@ +## v0.18.0 - dd/mm/yyyy + +### Changes affecting backwards compatibility + +- Removed basic2d/basic3d out of the stdlib and into Nimble packages. + These packages deprecated however, use the ``glm``, ``arraymancer``, ``neo`` + or another package. diff --git a/compiler/aliases.nim b/compiler/aliases.nim index 0c836bb24..c0371e159 100644 --- a/compiler/aliases.nim +++ b/compiler/aliases.nim @@ -95,7 +95,7 @@ proc isPartOf*(a, b: PNode): TAnalysisResult = if a.kind == b.kind: case a.kind of nkSym: - const varKinds = {skVar, skTemp, skProc} + const varKinds = {skVar, skTemp, skProc, skFunc} # same symbol: aliasing: if a.sym.id == b.sym.id: result = arYes elif a.sym.kind in varKinds or b.sym.kind in varKinds: diff --git a/compiler/ast.nim b/compiler/ast.nim index f9b43ed13..38cfcf77f 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -221,6 +221,8 @@ type nkGotoState, # used for the state machine (for iterators) nkState, # give a label to a code section (for iterators) nkBreakState, # special break statement for easier code generation + nkFuncDef # a func + TNodeKinds* = set[TNodeKind] type @@ -352,7 +354,7 @@ type tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers tyFloat, tyFloat32, tyFloat64, tyFloat128, tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64, - tyUnused0, tyUnused1, tyUnused2, + tyOptAsRef, tyUnused1, tyUnused2, tyVarargs, tyUnused, tyProxy # used as errornous type (for idetools) @@ -402,14 +404,8 @@ type # 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 + tyOpt + # Builtin optional type tyVoid # now different from tyEmpty, hurray! @@ -534,6 +530,7 @@ type skConst, # a constant skResult, # special 'result' variable skProc, # a proc + skFunc, # a func skMethod, # a method skIterator, # an iterator skConverter, # a type converter @@ -552,7 +549,7 @@ type TSymKinds* = set[TSymKind] const - routineKinds* = {skProc, skMethod, skIterator, + routineKinds* = {skProc, skFunc, skMethod, skIterator, skConverter, skMacro, skTemplate} tfIncompleteStruct* = tfVarargs tfUncheckedArray* = tfVarargs @@ -621,7 +618,7 @@ type mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast, mNewString, mNewStringOfCap, mParseBiggestFloat, mReset, - mArray, mOpenArray, mRange, mSet, mSeq, mVarargs, + mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs, mRef, mPtr, mVar, mDistinct, mVoid, mTuple, mOrdinal, mInt, mInt8, mInt16, mInt32, mInt64, @@ -631,7 +628,7 @@ type mPointer, mEmptySet, mIntSetBaseType, mNil, mExpr, mStmt, mTypeDesc, mVoidType, mPNimrodNode, mShared, mGuarded, mLock, mSpawn, mDeepCopy, mIsMainModule, mCompileDate, mCompileTime, mProcCall, - mCpuEndian, mHostOS, mHostCPU, mAppType, + mCpuEndian, mHostOS, mHostCPU, mBuildOS, mBuildCPU, mAppType, mNaN, mInf, mNegInf, mCompileOption, mCompileOptionArg, mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind, @@ -754,9 +751,9 @@ type TLocFlags* = set[TLocFlag] TLoc* = object k*: TLocKind # kind of location - s*: TStorageLoc + storage*: TStorageLoc flags*: TLocFlags # location's flags - t*: PType # type of location + lode*: PNode # Node where the location came from; can be faked r*: Rope # rope value of location (code generators) dup*: Rope # duplicated location for precise stack scans @@ -934,7 +931,7 @@ type # the poor naming choices in the standard library. const - OverloadableSyms* = {skProc, skMethod, skIterator, + OverloadableSyms* = {skProc, skFunc, skMethod, skIterator, skConverter, skModule, skTemplate, skMacro} GenericTypes*: TTypeKinds = {tyGenericInvocation, tyGenericBody, @@ -957,7 +954,7 @@ const tyTuple, tySequence} NilableTypes*: TTypeKinds = {tyPointer, tyCString, tyRef, tyPtr, tySequence, tyProc, tyString, tyError} - ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, + ExportableSymKinds* = {skVar, skConst, skProc, skFunc, skMethod, skType, skIterator, skMacro, skTemplate, skConverter, skEnumField, skLet, skStub, skAlias} PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, @@ -981,14 +978,14 @@ const nkLiterals* = {nkCharLit..nkTripleStrLit} nkLambdaKinds* = {nkLambda, nkDo} - declarativeDefs* = {nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef} + declarativeDefs* = {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef} procDefs* = nkLambdaKinds + declarativeDefs nkSymChoices* = {nkClosedSymChoice, nkOpenSymChoice} nkStrKinds* = {nkStrLit..nkTripleStrLit} skLocalVars* = {skVar, skLet, skForVar, skParam, skResult} - skProcKinds* = {skProc, skTemplate, skMacro, skIterator, + skProcKinds* = {skProc, skFunc, skTemplate, skMacro, skIterator, skMethod, skConverter} var ggDebug* {.deprecated.}: bool ## convenience switch for trying out things @@ -1264,11 +1261,10 @@ proc newType*(kind: TTypeKind, owner: PSym): PType = proc mergeLoc(a: var TLoc, b: TLoc) = if a.k == low(a.k): a.k = b.k - if a.s == low(a.s): a.s = b.s + if a.storage == low(a.storage): a.storage = b.storage a.flags = a.flags + b.flags - if a.t == nil: a.t = b.t + if a.lode == nil: a.lode = b.lode if a.r == nil: a.r = b.r - #if a.a == 0: a.a = b.a proc newSons*(father: PNode, length: int) = if isNil(father.sons): diff --git a/compiler/canonicalizer.nim b/compiler/canonicalizer.nim index d17d928c8..6972f5acf 100644 --- a/compiler/canonicalizer.nim +++ b/compiler/canonicalizer.nim @@ -130,7 +130,7 @@ proc hashType(c: var MD5Context, t: PType) = c.hashSym body.sym for i in countup(1, sonsLen(t) - 2): c.hashType t.sons[i] - of tyFromExpr, tyFieldAccessor: + of tyFromExpr: c.hashTree(t.n) of tyArray: c.hashTree(t.sons[0].n) diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 7493a50ca..a00e2bc77 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -56,7 +56,7 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc, if d.k == locNone: getTemp(p, typ.sons[0], d) assert(d.t != nil) # generate an assignment to d: var list: TLoc - initLoc(list, locCall, d.t, OnUnknown) + initLoc(list, locCall, d.lode, OnUnknown) list.r = pl genAssignment(p, d, list, {}) # no need for deep copying else: @@ -241,7 +241,7 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = if d.k == locNone: getTemp(p, typ.sons[0], d) assert(d.t != nil) # generate an assignment to d: var list: TLoc - initLoc(list, locCall, d.t, OnUnknown) + initLoc(list, locCall, d.lode, OnUnknown) list.r = callPattern % [op.r, pl, pl.addComma, rawProc] genAssignment(p, d, list, {}) # no need for deep copying else: @@ -437,7 +437,7 @@ proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) = if d.k == locNone: getTemp(p, typ.sons[0], d) assert(d.t != nil) # generate an assignment to d: var list: TLoc - initLoc(list, locCall, d.t, OnUnknown) + initLoc(list, locCall, d.lode, OnUnknown) list.r = pl genAssignment(p, d, list, {}) # no need for deep copying else: @@ -519,7 +519,7 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) = if d.k == locNone: getTemp(p, typ.sons[0], d) assert(d.t != nil) # generate an assignment to d: var list: TLoc - initLoc(list, locCall, nil, OnUnknown) + initLoc(list, locCall, ri, OnUnknown) list.r = pl genAssignment(p, d, list, {}) # no need for deep copying else: diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index f5c793d29..88944aea6 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -157,10 +157,23 @@ proc getStorageLoc(n: PNode): TStorageLoc = result = getStorageLoc(n.sons[0]) else: result = OnUnknown +proc canMove(n: PNode): bool = + # for now we're conservative here: + if n.kind == nkBracket: + # This needs to be kept consistent with 'const' seq code + # generation! + if not isDeepConstExpr(n) or n.len == 0: + if skipTypes(n.typ, abstractVarRange).kind == tySequence: + return true + result = n.kind in nkCallKinds + #if result: + # echo n.info, " optimized ", n + # result = false + proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = - if dest.s == OnStack or not usesNativeGC(): + if dest.storage == OnStack or not usesNativeGC(): linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) - elif dest.s == OnHeap: + elif dest.storage == OnHeap: # location is on heap # now the writer barrier is inlined for performance: # @@ -202,13 +215,13 @@ proc asgnComplexity(n: PNode): int = proc optAsgnLoc(a: TLoc, t: PType, field: Rope): TLoc = assert field != nil result.k = locField - result.s = a.s - result.t = t + result.storage = a.storage + result.lode = lodeTyp t result.r = rdLoc(a) & "." & field proc genOptAsgnTuple(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = let newflags = - if src.s == OnStatic: + if src.storage == OnStatic: flags + {needToCopy} elif tfShallow in dest.t.flags: flags - {needToCopy} @@ -225,7 +238,7 @@ proc genOptAsgnObject(p: BProc, dest, src: TLoc, flags: TAssignmentFlags, t: PNode, typ: PType) = if t == nil: return let newflags = - if src.s == OnStatic: + if src.storage == OnStatic: flags + {needToCopy} elif tfShallow in dest.t.flags: flags - {needToCopy} @@ -250,7 +263,7 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = # (for objects, etc.): if needToCopy notin flags or tfShallow in skipTypes(dest.t, abstractVarRange).flags: - if dest.s == OnStack or not usesNativeGC(): + if dest.storage == OnStack or not usesNativeGC(): useStringh(p.module) linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", @@ -274,18 +287,18 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = of tyRef: genRefAssign(p, dest, src, flags) of tySequence: - if needToCopy notin flags and src.s != OnStatic: + if (needToCopy notin flags and src.storage != OnStatic) or canMove(src.lode): genRefAssign(p, dest, src, flags) else: linefmt(p, cpsStmts, "#genericSeqAssign($1, $2, $3);$n", addrLoc(dest), rdLoc(src), genTypeInfo(p.module, dest.t)) of tyString: - if needToCopy notin flags and src.s != OnStatic: + if (needToCopy notin flags and src.storage != OnStatic) or canMove(src.lode): genRefAssign(p, dest, src, flags) else: - if dest.s == OnStack or not usesNativeGC(): + if dest.storage == OnStack or not usesNativeGC(): linefmt(p, cpsStmts, "$1 = #copyString($2);$n", dest.rdLoc, src.rdLoc) - elif dest.s == OnHeap: + elif dest.storage == OnHeap: # we use a temporary to care for the dreaded self assignment: var tmp: TLoc getTemp(p, ty, tmp) @@ -357,7 +370,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) else: internalError("genAssignment: " & $ty.kind) - if optMemTracker in p.options and dest.s in {OnHeap, OnUnknown}: + if optMemTracker in p.options and dest.storage in {OnHeap, OnUnknown}: #writeStackTrace() #echo p.currLineInfo, " requesting" linefmt(p, cpsStmts, "#memTrackerWrite((void*)$1, $2, $3, $4);$n", @@ -407,11 +420,11 @@ proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) = else: d = s # ``d`` is free, so fill it with ``s`` -proc putDataIntoDest(p: BProc, d: var TLoc, t: PType, r: Rope) = +proc putDataIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope) = var a: TLoc if d.k != locNone: # need to generate an assignment here - initLoc(a, locData, t, OnStatic) + initLoc(a, locData, n, OnStatic) a.r = r if lfNoDeepCopy in d.flags: genAssignment(p, d, a, {}) else: genAssignment(p, d, a, {needToCopy}) @@ -419,14 +432,14 @@ proc putDataIntoDest(p: BProc, d: var TLoc, t: PType, r: Rope) = # we cannot call initLoc() here as that would overwrite # the flags field! d.k = locData - d.t = t + d.lode = n d.r = r -proc putIntoDest(p: BProc, d: var TLoc, t: PType, r: Rope; s=OnUnknown) = +proc putIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope; s=OnUnknown) = var a: TLoc if d.k != locNone: # need to generate an assignment here - initLoc(a, locExpr, t, s) + initLoc(a, locExpr, n, s) a.r = r if lfNoDeepCopy in d.flags: genAssignment(p, d, a, {}) else: genAssignment(p, d, a, {needToCopy}) @@ -434,7 +447,7 @@ proc putIntoDest(p: BProc, d: var TLoc, t: PType, r: Rope; s=OnUnknown) = # we cannot call initLoc() here as that would overwrite # the flags field! d.k = locExpr - d.t = t + d.lode = n d.r = r proc binaryStmt(p: BProc, e: PNode, d: var TLoc, frmt: string) = @@ -456,7 +469,7 @@ proc binaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: string) = assert(e.sons[2].typ != nil) initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - putIntoDest(p, d, e.typ, ropecg(p.module, frmt, [rdLoc(a), rdLoc(b)])) + putIntoDest(p, d, e, ropecg(p.module, frmt, [rdLoc(a), rdLoc(b)])) proc binaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: string) = var a, b: TLoc @@ -464,17 +477,17 @@ proc binaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: string) = assert(e.sons[2].typ != nil) initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - putIntoDest(p, d, e.typ, ropecg(p.module, frmt, [a.rdCharLoc, b.rdCharLoc])) + putIntoDest(p, d, e, ropecg(p.module, frmt, [a.rdCharLoc, b.rdCharLoc])) proc unaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: string) = var a: TLoc initLocExpr(p, e.sons[1], a) - putIntoDest(p, d, e.typ, ropecg(p.module, frmt, [rdLoc(a)])) + putIntoDest(p, d, e, ropecg(p.module, frmt, [rdLoc(a)])) proc unaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: string) = var a: TLoc initLocExpr(p, e.sons[1], a) - putIntoDest(p, d, e.typ, ropecg(p.module, frmt, [rdCharLoc(a)])) + putIntoDest(p, d, e, ropecg(p.module, frmt, [rdCharLoc(a)])) proc binaryArithOverflowRaw(p: BProc, t: PType, a, b: TLoc; frmt: string): Rope = @@ -514,11 +527,11 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = let t = e.typ.skipTypes(abstractRange) if optOverflowCheck notin p.options: let res = opr[m] % [getTypeDesc(p.module, e.typ), rdLoc(a), rdLoc(b)] - putIntoDest(p, d, e.typ, res) + putIntoDest(p, d, e, res) else: let res = binaryArithOverflowRaw(p, t, a, b, if t.kind == tyInt64: prc64[m] else: prc[m]) - putIntoDest(p, d, e.typ, "($#)($#)" % [getTypeDesc(p.module, e.typ), res]) + putIntoDest(p, d, e, "($#)($#)" % [getTypeDesc(p.module, e.typ), res]) proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = const @@ -535,7 +548,7 @@ proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = if optOverflowCheck in p.options: linefmt(p, cpsStmts, "if ($1 == $2) #raiseOverflow();$n", rdLoc(a), intLiteral(firstOrd(t))) - putIntoDest(p, d, e.typ, opr[m] % [rdLoc(a), rope(getSize(t) * 8)]) + putIntoDest(p, d, e, opr[m] % [rdLoc(a), rope(getSize(t) * 8)]) proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = const @@ -593,7 +606,7 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = # BUGFIX: cannot use result-type here, as it may be a boolean s = max(getSize(a.t), getSize(b.t)) * 8 k = getSize(a.t) * 8 - putIntoDest(p, d, e.typ, + putIntoDest(p, d, e, binArithTab[op] % [rdLoc(a), rdLoc(b), rope(s), getSimpleTypeDesc(p.module, e.typ), rope(k)]) @@ -604,10 +617,10 @@ proc genEqProc(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) if a.t.skipTypes(abstractInst).callConv == ccClosure: - putIntoDest(p, d, e.typ, + putIntoDest(p, d, e, "($1.ClP_0 == $2.ClP_0 && $1.ClE_0 == $2.ClE_0)" % [rdLoc(a), rdLoc(b)]) else: - putIntoDest(p, d, e.typ, "($1 == $2)" % [rdLoc(a), rdLoc(b)]) + putIntoDest(p, d, e, "($1 == $2)" % [rdLoc(a), rdLoc(b)]) proc genIsNil(p: BProc, e: PNode, d: var TLoc) = let t = skipTypes(e.sons[1].typ, abstractRange) @@ -644,7 +657,7 @@ proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = assert(e.sons[1].typ != nil) initLocExpr(p, e.sons[1], a) t = skipTypes(e.typ, abstractRange) - putIntoDest(p, d, e.typ, + putIntoDest(p, d, e, unArithTab[op] % [rdLoc(a), rope(getSize(t) * 8), getSimpleTypeDesc(p.module, e.typ)]) @@ -662,7 +675,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) = # message(e.info, warnUser, "CAME HERE " & renderTree(e)) expr(p, e.sons[0], d) if e.sons[0].typ.skipTypes(abstractInst).kind == tyRef: - d.s = OnHeap + d.storage = OnHeap else: var a: TLoc var typ = skipTypes(e.sons[0].typ, abstractInst) @@ -675,25 +688,25 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) = initLocExprSingleUse(p, e.sons[0], a) if d.k == locNone: # dest = *a; <-- We do not know that 'dest' is on the heap! - # It is completely wrong to set 'd.s' here, unless it's not yet + # It is completely wrong to set 'd.storage' here, unless it's not yet # been assigned to. case typ.kind of tyRef: - d.s = OnHeap + d.storage = OnHeap of tyVar: - d.s = OnUnknown + d.storage = OnUnknown if tfVarIsPtr notin typ.flags and p.module.compileToCpp and e.kind == nkHiddenDeref: - putIntoDest(p, d, e.typ, rdLoc(a), a.s) + putIntoDest(p, d, e, rdLoc(a), a.storage) return of tyPtr: - d.s = OnUnknown # BUGFIX! + d.storage = OnUnknown # BUGFIX! else: internalError(e.info, "genDeref " & $typ.kind) elif p.module.compileToCpp: if typ.kind == tyVar and tfVarIsPtr notin typ.flags and e.kind == nkHiddenDeref: - putIntoDest(p, d, e.typ, rdLoc(a), a.s) + putIntoDest(p, d, e, rdLoc(a), a.storage) return if enforceDeref and mt == ctPtrToArray: # we lie about the type for better C interop: 'ptr array[3,T]' is @@ -701,26 +714,26 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) = # See tmissingderef. So we get rid of the deref instead. The codegen # ends up using 'memcpy' for the array assignment, # so the '&' and '*' cancel out: - putIntoDest(p, d, a.t.sons[0], rdLoc(a), a.s) + putIntoDest(p, d, lodeTyp(a.t.sons[0]), rdLoc(a), a.storage) else: - putIntoDest(p, d, e.typ, "(*$1)" % [rdLoc(a)], a.s) + putIntoDest(p, d, e, "(*$1)" % [rdLoc(a)], a.storage) proc genAddr(p: BProc, e: PNode, d: var TLoc) = # careful 'addr(myptrToArray)' needs to get the ampersand: if e.sons[0].typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}: var a: TLoc initLocExpr(p, e.sons[0], a) - putIntoDest(p, d, e.typ, "&" & a.r, a.s) + putIntoDest(p, d, e, "&" & a.r, a.storage) #Message(e.info, warnUser, "HERE NEW &") elif mapType(e.sons[0].typ) == ctArray or isCppRef(p, e.sons[0].typ): expr(p, e.sons[0], d) else: var a: TLoc initLocExpr(p, e.sons[0], a) - putIntoDest(p, d, e.typ, addrLoc(a), a.s) + putIntoDest(p, d, e, addrLoc(a), a.storage) template inheritLocation(d: var TLoc, a: TLoc) = - if d.k == locNone: d.s = a.s + if d.k == locNone: d.storage = a.storage proc genRecordFieldAux(p: BProc, e: PNode, d, a: var TLoc) = initLocExpr(p, e.sons[0], a) @@ -742,7 +755,7 @@ proc genTupleElem(p: BProc, e: PNode, d: var TLoc) = of nkIntLit..nkUInt64Lit: i = int(e.sons[1].intVal) else: internalError(e.info, "genTupleElem") addf(r, ".Field$1", [rope(i)]) - putIntoDest(p, d, tupType.sons[i], r, a.s) + putIntoDest(p, d, e, r, a.storage) proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope; resTyp: ptr PType = nil): PSym = @@ -769,15 +782,14 @@ proc genRecordField(p: BProc, e: PNode, d: var TLoc) = # we found a unique tuple type which lacks field information # so we use Field$i addf(r, ".Field$1", [rope(f.position)]) - putIntoDest(p, d, f.typ, r, a.s) + putIntoDest(p, d, e, r, a.storage) else: var rtyp: PType let field = lookupFieldAgain(p, ty, f, r, addr rtyp) if field.loc.r == nil and rtyp != nil: fillObjectFields(p.module, rtyp) if field.loc.r == nil: internalError(e.info, "genRecordField 3 " & typeToString(ty)) addf(r, ".$1", [field.loc.r]) - putIntoDest(p, d, field.typ, r, a.s) - #d.s = a.s + putIntoDest(p, d, e, r, a.storage) proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) @@ -792,11 +804,11 @@ proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym; if op.magic == mNot: it = it.sons[1] let disc = it.sons[2].skipConv assert(disc.kind == nkSym) - initLoc(test, locNone, it.typ, OnStack) + initLoc(test, locNone, it, OnStack) initLocExpr(p, it.sons[1], u) var o = obj let d = lookupFieldAgain(p, origTy, disc.sym, o) - initLoc(v, locExpr, d.typ, OnUnknown) + initLoc(v, locExpr, disc, OnUnknown) v.r = o v.r.add(".") v.r.add(d.loc.r) @@ -827,11 +839,11 @@ proc genCheckedRecordField(p: BProc, e: PNode, d: var TLoc) = internalError(e.info, "genCheckedRecordField") # generate the checks: genFieldCheck(p, e, r, field, ty) add(r, rfmt(nil, ".$1", field.loc.r)) - putIntoDest(p, d, field.typ, r, a.s) + putIntoDest(p, d, e.sons[0], r, a.storage) else: genRecordField(p, e.sons[0], d) -proc genArrayElem(p: BProc, x, y: PNode, d: var TLoc) = +proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a, b: TLoc initLocExpr(p, x, a) initLocExpr(p, y, b) @@ -853,30 +865,30 @@ proc genArrayElem(p: BProc, x, y: PNode, d: var TLoc) = if idx < firstOrd(ty) or idx > lastOrd(ty): localError(x.info, errIndexOutOfBounds) d.inheritLocation(a) - putIntoDest(p, d, elemType(skipTypes(ty, abstractVar)), - rfmt(nil, "$1[($2)- $3]", rdLoc(a), rdCharLoc(b), first), a.s) + putIntoDest(p, d, n, + rfmt(nil, "$1[($2)- $3]", rdLoc(a), rdCharLoc(b), first), a.storage) -proc genCStringElem(p: BProc, x, y: PNode, d: var TLoc) = +proc genCStringElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a, b: TLoc initLocExpr(p, x, a) initLocExpr(p, y, b) var ty = skipTypes(a.t, abstractVarRange) - if d.k == locNone: d.s = a.s - putIntoDest(p, d, elemType(skipTypes(ty, abstractVar)), - rfmt(nil, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.s) + inheritLocation(d, a) + putIntoDest(p, d, n, + rfmt(nil, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.storage) -proc genOpenArrayElem(p: BProc, x, y: PNode, d: var TLoc) = +proc genOpenArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a, b: TLoc initLocExpr(p, x, a) initLocExpr(p, y, b) # emit range check: if optBoundsCheck in p.options: linefmt(p, cpsStmts, "if ((NU)($1) >= (NU)($2Len_0)) #raiseIndexError();$n", rdLoc(b), rdLoc(a)) # BUGFIX: ``>=`` and not ``>``! - if d.k == locNone: d.s = a.s - putIntoDest(p, d, elemType(skipTypes(a.t, abstractVar)), - rfmt(nil, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.s) + inheritLocation(d, a) + putIntoDest(p, d, n, + rfmt(nil, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.storage) -proc genSeqElem(p: BProc, x, y: PNode, d: var TLoc) = +proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a, b: TLoc initLocExpr(p, x, a) initLocExpr(p, y, b) @@ -892,20 +904,20 @@ proc genSeqElem(p: BProc, x, y: PNode, d: var TLoc) = linefmt(p, cpsStmts, "if ((NU)($1) >= (NU)($2->$3)) #raiseIndexError();$n", rdLoc(b), rdLoc(a), lenField(p)) - if d.k == locNone: d.s = OnHeap + if d.k == locNone: d.storage = OnHeap if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}: a.r = rfmt(nil, "(*$1)", a.r) - putIntoDest(p, d, elemType(skipTypes(a.t, abstractVar)), - rfmt(nil, "$1->data[$2]", rdLoc(a), rdCharLoc(b)), a.s) + putIntoDest(p, d, n, + rfmt(nil, "$1->data[$2]", rdLoc(a), rdCharLoc(b)), a.storage) proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) = var ty = skipTypes(n.sons[0].typ, abstractVarRange + tyUserTypeClasses) if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange) case ty.kind - of tyArray: genArrayElem(p, n.sons[0], n.sons[1], d) - of tyOpenArray, tyVarargs: genOpenArrayElem(p, n.sons[0], n.sons[1], d) - of tySequence, tyString: genSeqElem(p, n.sons[0], n.sons[1], d) - of tyCString: genCStringElem(p, n.sons[0], n.sons[1], d) + of tyArray: genArrayElem(p, n, n.sons[0], n.sons[1], d) + of tyOpenArray, tyVarargs: genOpenArrayElem(p, n, n.sons[0], n.sons[1], d) + of tySequence, tyString: genSeqElem(p, n, n.sons[0], n.sons[1], d) + of tyCString: genCStringElem(p, n, n.sons[0], n.sons[1], d) of tyTuple: genTupleElem(p, n, d) else: internalError(n.info, "expr(nkBracketExpr, " & $ty.kind & ')') @@ -1071,7 +1083,7 @@ proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) = getTypeDesc(p.module, bt)]) #if bt != b.t: # echo "YES ", e.info, " new: ", typeToString(bt), " old: ", typeToString(b.t) - initLoc(dest, locExpr, bt, OnHeap) + initLoc(dest, locExpr, e.sons[2], OnHeap) getIntTemp(p, tmpL) lineCg(p, cpsStmts, "$1 = $2->$3++;$n", tmpL.r, rdLoc(a), lenField(p)) dest.r = rfmt(nil, "$1->data[$2]", rdLoc(a), tmpL.r) @@ -1088,7 +1100,7 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) = var sizeExpr = sizeExpr let typ = a.t var b: TLoc - initLoc(b, locExpr, a.t, OnHeap) + initLoc(b, locExpr, a.lode, OnHeap) let refType = typ.skipTypes(abstractInst) assert refType.kind == tyRef let bt = refType.lastSon @@ -1098,7 +1110,7 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) = let args = [getTypeDesc(p.module, typ), genTypeInfo(p.module, typ), sizeExpr] - if a.s == OnHeap and usesNativeGC(): + if a.storage == OnHeap and usesNativeGC(): # use newObjRC1 as an optimization if canFormAcycle(a.t): linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", a.rdLoc) @@ -1128,8 +1140,8 @@ proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope) = let args = [getTypeDesc(p.module, seqtype), genTypeInfo(p.module, seqtype), length] var call: TLoc - initLoc(call, locExpr, dest.t, OnHeap) - if dest.s == OnHeap and usesNativeGC(): + initLoc(call, locExpr, dest.lode, OnHeap) + if dest.storage == OnHeap and usesNativeGC(): if canFormAcycle(dest.t): linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", dest.rdLoc) else: @@ -1151,7 +1163,7 @@ proc genNewSeqOfCap(p: BProc; e: PNode; d: var TLoc) = let seqtype = skipTypes(e.typ, abstractVarRange) var a: TLoc initLocExpr(p, e.sons[1], a) - putIntoDest(p, d, e.typ, ropecg(p.module, + putIntoDest(p, d, e, ropecg(p.module, "($1)#nimNewSeqOfCap($2, $3)", [ getTypeDesc(p.module, seqtype), genTypeInfo(p.module, seqtype), a.rdLoc])) @@ -1163,7 +1175,7 @@ proc handleConstExpr(p: BProc, n: PNode, d: var TLoc): bool = let t = n.typ discard getTypeDesc(p.module, t) # so that any fields are initialized let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels) - fillLoc(d, locData, t, p.module.tmpBase & rope(id), OnStatic) + fillLoc(d, locData, n, p.module.tmpBase & rope(id), OnStatic) if id == p.module.labels: # expression not found in the cache: inc(p.module.labels) @@ -1205,8 +1217,8 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) = add(tmp2.r, ".") add(tmp2.r, field.loc.r) tmp2.k = locTemp - tmp2.t = field.loc.t - tmp2.s = if isRef: OnHeap else: OnStack + tmp2.lode = it.sons[1] + tmp2.storage = if isRef: OnHeap else: OnStack expr(p, it.sons[1], tmp2) if d.k == locNone: @@ -1214,37 +1226,37 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) = else: genAssignment(p, d, tmp, {}) -proc genSeqConstr(p: BProc, t: PNode, d: var TLoc) = +proc genSeqConstr(p: BProc, n: PNode, d: var TLoc) = var arr: TLoc if d.k == locNone: - getTemp(p, t.typ, d) + getTemp(p, n.typ, d) # generate call to newSeq before adding the elements per hand: - genNewSeqAux(p, d, intLiteral(sonsLen(t))) - for i in countup(0, sonsLen(t) - 1): - initLoc(arr, locExpr, elemType(skipTypes(t.typ, typedescInst)), OnHeap) + genNewSeqAux(p, d, intLiteral(sonsLen(n))) + for i in countup(0, sonsLen(n) - 1): + initLoc(arr, locExpr, n[i], OnHeap) arr.r = rfmt(nil, "$1->data[$2]", rdLoc(d), intLiteral(i)) - arr.s = OnHeap # we know that sequences are on the heap - expr(p, t.sons[i], arr) - gcUsage(t) + arr.storage = OnHeap # we know that sequences are on the heap + expr(p, n[i], arr) + gcUsage(n) -proc genArrToSeq(p: BProc, t: PNode, d: var TLoc) = +proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) = var elem, a, arr: TLoc - if t.sons[1].kind == nkBracket: - t.sons[1].typ = t.typ - genSeqConstr(p, t.sons[1], d) + if n.sons[1].kind == nkBracket: + n.sons[1].typ = n.typ + genSeqConstr(p, n.sons[1], d) return if d.k == locNone: - getTemp(p, t.typ, d) + getTemp(p, n.typ, d) # generate call to newSeq before adding the elements per hand: - var L = int(lengthOrd(t.sons[1].typ)) + var L = int(lengthOrd(n.sons[1].typ)) genNewSeqAux(p, d, intLiteral(L)) - initLocExpr(p, t.sons[1], a) + initLocExpr(p, n.sons[1], a) for i in countup(0, L - 1): - initLoc(elem, locExpr, elemType(skipTypes(t.typ, abstractInst)), OnHeap) + initLoc(elem, locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), OnHeap) elem.r = rfmt(nil, "$1->data[$2]", rdLoc(d), intLiteral(i)) - elem.s = OnHeap # we know that sequences are on the heap - initLoc(arr, locExpr, elemType(skipTypes(t.sons[1].typ, abstractInst)), a.s) + elem.storage = OnHeap # we know that sequences are on the heap + initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.sons[1].typ, abstractInst)), a.storage) arr.r = rfmt(nil, "$1[$2]", rdLoc(a), intLiteral(i)) genAssignment(p, elem, arr, {afDestIsNil, needToCopy}) @@ -1256,7 +1268,7 @@ proc genNewFinalize(p: BProc, e: PNode) = refType = skipTypes(e.sons[1].typ, abstractVarRange) initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], f) - initLoc(b, locExpr, a.t, OnHeap) + initLoc(b, locExpr, a.lode, OnHeap) ti = genTypeInfo(p.module, refType) addf(p.module.s[cfsTypeInit3], "$1->finalizer = (void*)$2;$n", [ti, rdLoc(f)]) b.r = ropecg(p.module, "($1) #newObj($2, sizeof($3))", [ @@ -1308,7 +1320,7 @@ proc genOf(p: BProc, x: PNode, typ: PType, d: var TLoc) = r = rfmt(p.module, "(($1) && ($2))", nilCheck, genOfHelper(p, dest, r)) else: r = rfmt(p.module, "($1)", genOfHelper(p, dest, r)) - putIntoDest(p, d, getSysType(tyBool), r, a.s) + putIntoDest(p, d, x, r, a.storage) proc genOf(p: BProc, n: PNode, d: var TLoc) = genOf(p, n.sons[1], n.sons[2].typ, d) @@ -1319,52 +1331,53 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) = var t = skipTypes(e.sons[1].typ, abstractVarRange) case t.kind of tyInt..tyInt64, tyUInt..tyUInt64: - putIntoDest(p, d, e.typ, - ropecg(p.module, "#reprInt((NI64)$1)", [rdLoc(a)]), a.s) + putIntoDest(p, d, e, + ropecg(p.module, "#reprInt((NI64)$1)", [rdLoc(a)]), a.storage) of tyFloat..tyFloat128: - putIntoDest(p, d, e.typ, ropecg(p.module, "#reprFloat($1)", [rdLoc(a)]), a.s) + putIntoDest(p, d, e, ropecg(p.module, "#reprFloat($1)", [rdLoc(a)]), a.storage) of tyBool: - putIntoDest(p, d, e.typ, ropecg(p.module, "#reprBool($1)", [rdLoc(a)]), a.s) + putIntoDest(p, d, e, ropecg(p.module, "#reprBool($1)", [rdLoc(a)]), a.storage) of tyChar: - putIntoDest(p, d, e.typ, ropecg(p.module, "#reprChar($1)", [rdLoc(a)]), a.s) + putIntoDest(p, d, e, ropecg(p.module, "#reprChar($1)", [rdLoc(a)]), a.storage) of tyEnum, tyOrdinal: - putIntoDest(p, d, e.typ, + putIntoDest(p, d, e, ropecg(p.module, "#reprEnum((NI)$1, $2)", [ - rdLoc(a), genTypeInfo(p.module, t)]), a.s) + rdLoc(a), genTypeInfo(p.module, t)]), a.storage) of tyString: - putIntoDest(p, d, e.typ, ropecg(p.module, "#reprStr($1)", [rdLoc(a)]), a.s) + putIntoDest(p, d, e, ropecg(p.module, "#reprStr($1)", [rdLoc(a)]), a.storage) of tySet: - putIntoDest(p, d, e.typ, ropecg(p.module, "#reprSet($1, $2)", [ - addrLoc(a), genTypeInfo(p.module, t)]), a.s) + putIntoDest(p, d, e, ropecg(p.module, "#reprSet($1, $2)", [ + addrLoc(a), genTypeInfo(p.module, t)]), a.storage) of tyOpenArray, tyVarargs: var b: TLoc case a.t.kind of tyOpenArray, tyVarargs: - putIntoDest(p, b, e.typ, "$1, $1Len_0" % [rdLoc(a)], a.s) + putIntoDest(p, b, e, "$1, $1Len_0" % [rdLoc(a)], a.storage) of tyString, tySequence: - putIntoDest(p, b, e.typ, - "$1->data, $1->$2" % [rdLoc(a), lenField(p)], a.s) + putIntoDest(p, b, e, + "$1->data, $1->$2" % [rdLoc(a), lenField(p)], a.storage) of tyArray: - putIntoDest(p, b, e.typ, - "$1, $2" % [rdLoc(a), rope(lengthOrd(a.t))], a.s) + putIntoDest(p, b, e, + "$1, $2" % [rdLoc(a), rope(lengthOrd(a.t))], a.storage) else: internalError(e.sons[0].info, "genRepr()") - putIntoDest(p, d, e.typ, + putIntoDest(p, d, e, ropecg(p.module, "#reprOpenArray($1, $2)", [rdLoc(b), - genTypeInfo(p.module, elemType(t))]), a.s) + genTypeInfo(p.module, elemType(t))]), a.storage) of tyCString, tyArray, tyRef, tyPtr, tyPointer, tyNil, tySequence: - putIntoDest(p, d, e.typ, + putIntoDest(p, d, e, ropecg(p.module, "#reprAny($1, $2)", [ - rdLoc(a), genTypeInfo(p.module, t)]), a.s) + rdLoc(a), genTypeInfo(p.module, t)]), a.storage) of tyEmpty, tyVoid: localError(e.info, "'repr' doesn't support 'void' type") else: - putIntoDest(p, d, e.typ, ropecg(p.module, "#reprAny($1, $2)", - [addrLoc(a), genTypeInfo(p.module, t)]), a.s) + putIntoDest(p, d, e, ropecg(p.module, "#reprAny($1, $2)", + [addrLoc(a), genTypeInfo(p.module, t)]), + a.storage) gcUsage(e) proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) = let t = e.sons[1].typ - putIntoDest(p, d, e.typ, genTypeInfo(p.module, t)) + putIntoDest(p, d, e, genTypeInfo(p.module, t)) proc genDollar(p: BProc, n: PNode, d: var TLoc, frmt: string) = var a: TLoc @@ -1409,11 +1422,11 @@ proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = else: frmt = "$1 = ($2 ? $2->len : 0);$n" lineCg(p, cpsStmts, frmt, tmp.r, rdLoc(a)) - putIntoDest(p, d, e.typ, tmp.r) + putIntoDest(p, d, e, tmp.r) of tyArray: # YYY: length(sideeffect) is optimized away incorrectly? - if op == mHigh: putIntoDest(p, d, e.typ, rope(lastOrd(typ))) - else: putIntoDest(p, d, e.typ, rope(lengthOrd(typ))) + if op == mHigh: putIntoDest(p, d, e, rope(lastOrd(typ))) + else: putIntoDest(p, d, e, rope(lengthOrd(typ))) else: internalError(e.info, "genArrayLen()") proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = @@ -1471,7 +1484,7 @@ proc fewCmps(s: PNode): bool = result = sonsLen(s) <= 8 # 8 seems to be a good value proc binaryExprIn(p: BProc, e: PNode, a, b, d: var TLoc, frmt: string) = - putIntoDest(p, d, e.typ, frmt % [rdLoc(a), rdSetElemLoc(b, a.t)]) + putIntoDest(p, d, e, frmt % [rdLoc(a), rdSetElemLoc(b, a.t)]) proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) = case int(getSize(skipTypes(e.sons[1].typ, abstractVar))) @@ -1500,7 +1513,7 @@ proc genInOp(p: BProc, e: PNode, d: var TLoc) = else: e.sons[2] initLocExpr(p, ea, a) - initLoc(b, locExpr, e.typ, OnUnknown) + initLoc(b, locExpr, e, OnUnknown) b.r = rope("(") var length = sonsLen(e.sons[1]) for i in countup(0, length - 1): @@ -1514,7 +1527,7 @@ proc genInOp(p: BProc, e: PNode, d: var TLoc) = addf(b.r, "$1 == $2", [rdCharLoc(a), rdCharLoc(x)]) if i < length - 1: add(b.r, " || ") add(b.r, ")") - putIntoDest(p, d, e.typ, b.r) + putIntoDest(p, d, e, b.r) else: assert(e.sons[1].typ != nil) assert(e.sons[2].typ != nil) @@ -1599,14 +1612,14 @@ proc genSomeCast(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[1], a) let etyp = skipTypes(e.typ, abstractRange) if etyp.kind in ValueTypes and lfIndirect notin a.flags: - putIntoDest(p, d, e.typ, "(*($1*) ($2))" % - [getTypeDesc(p.module, e.typ), addrLoc(a)], a.s) + putIntoDest(p, d, e, "(*($1*) ($2))" % + [getTypeDesc(p.module, e.typ), addrLoc(a)], a.storage) elif etyp.kind == tyProc and etyp.callConv == ccClosure: - putIntoDest(p, d, e.typ, "(($1) ($2))" % - [getClosureType(p.module, etyp, clHalfWithEnv), rdCharLoc(a)], a.s) + putIntoDest(p, d, e, "(($1) ($2))" % + [getClosureType(p.module, etyp, clHalfWithEnv), rdCharLoc(a)], a.storage) else: - putIntoDest(p, d, e.typ, "(($1) ($2))" % - [getTypeDesc(p.module, e.typ), rdCharLoc(a)], a.s) + putIntoDest(p, d, e, "(($1) ($2))" % + [getTypeDesc(p.module, e.typ), rdCharLoc(a)], a.storage) proc genCast(p: BProc, e: PNode, d: var TLoc) = const ValueTypes = {tyFloat..tyFloat128, tyTuple, tyObject, tyArray} @@ -1622,11 +1635,11 @@ proc genCast(p: BProc, e: PNode, d: var TLoc) = linefmt(p, cpsLocals, "union { $1 source; $2 dest; } LOC$3;$n", getTypeDesc(p.module, e.sons[1].typ), getTypeDesc(p.module, e.typ), lbl) tmp.k = locExpr - tmp.t = srct - tmp.s = OnStack + tmp.lode = lodeTyp srct + tmp.storage = OnStack tmp.flags = {} expr(p, e.sons[1], tmp) - putIntoDest(p, d, e.typ, "LOC$#.dest" % [lbl], tmp.s) + putIntoDest(p, d, e, "LOC$#.dest" % [lbl], tmp.storage) else: # I prefer the shorter cast version for pointer types -> generate less # C code; plus it's the right thing to do for closures: @@ -1639,14 +1652,14 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc, magic: string) = if optRangeCheck notin p.options or dest.skipTypes({tyRange}).kind in {tyUInt..tyUInt64}: initLocExpr(p, n.sons[0], a) - putIntoDest(p, d, n.typ, "(($1) ($2))" % - [getTypeDesc(p.module, dest), rdCharLoc(a)], a.s) + putIntoDest(p, d, n, "(($1) ($2))" % + [getTypeDesc(p.module, dest), rdCharLoc(a)], a.storage) else: initLocExpr(p, n.sons[0], a) - putIntoDest(p, d, dest, ropecg(p.module, "(($1)#$5($2, $3, $4))", [ + putIntoDest(p, d, lodeTyp dest, ropecg(p.module, "(($1)#$5($2, $3, $4))", [ getTypeDesc(p.module, dest), rdCharLoc(a), genLiteral(p, n.sons[1], dest), genLiteral(p, n.sons[2], dest), - rope(magic)]), a.s) + rope(magic)]), a.storage) proc genConv(p: BProc, e: PNode, d: var TLoc) = let destType = e.typ.skipTypes({tyVar, tyGenericInst, tyAlias}) @@ -1658,13 +1671,15 @@ proc genConv(p: BProc, e: PNode, d: var TLoc) = proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) = var a: TLoc initLocExpr(p, n.sons[0], a) - putIntoDest(p, d, skipTypes(n.typ, abstractVar), "$1->data" % [rdLoc(a)], a.s) + putIntoDest(p, d, n, "$1->data" % [rdLoc(a)], + a.storage) proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = var a: TLoc initLocExpr(p, n.sons[0], a) - putIntoDest(p, d, skipTypes(n.typ, abstractVar), - ropecg(p.module, "#cstrToNimstr($1)", [rdLoc(a)]), a.s) + putIntoDest(p, d, n, + ropecg(p.module, "#cstrToNimstr($1)", [rdLoc(a)]), + a.storage) gcUsage(n) proc genStrEquals(p: BProc, e: PNode, d: var TLoc) = @@ -1675,11 +1690,11 @@ proc genStrEquals(p: BProc, e: PNode, d: var TLoc) = binaryExpr(p, e, d, "($1 == $2)") elif (a.kind in {nkStrLit..nkTripleStrLit}) and (a.strVal == ""): initLocExpr(p, e.sons[2], x) - putIntoDest(p, d, e.typ, + putIntoDest(p, d, e, rfmt(nil, "(($1) && ($1)->$2 == 0)", rdLoc(x), lenField(p))) elif (b.kind in {nkStrLit..nkTripleStrLit}) and (b.strVal == ""): initLocExpr(p, e.sons[1], x) - putIntoDest(p, d, e.typ, + putIntoDest(p, d, e, rfmt(nil, "(($1) && ($1)->$2 == 0)", rdLoc(x), lenField(p))) else: binaryExpr(p, e, d, "#eqStrings($1, $2)") @@ -1692,9 +1707,9 @@ proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) = assert(e.sons[2].typ != nil) initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - putIntoDest(p, d, e.typ, rfmt(nil, "(($4)($2) $1 ($4)($3))", - rope(opr[m]), rdLoc(a), rdLoc(b), - getSimpleTypeDesc(p.module, e[1].typ))) + putIntoDest(p, d, e, rfmt(nil, "(($4)($2) $1 ($4)($3))", + rope(opr[m]), rdLoc(a), rdLoc(b), + getSimpleTypeDesc(p.module, e[1].typ))) if optNaNCheck in p.options: linefmt(p, cpsStmts, "#nanCheck($1);$n", rdLoc(d)) if optInfCheck in p.options: @@ -1736,7 +1751,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = let ranged = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tyVar}) let res = binaryArithOverflowRaw(p, ranged, a, b, if underlying.kind == tyInt64: fun64[op] else: fun[op]) - putIntoDest(p, a, ranged, "($#)($#)" % [ + putIntoDest(p, a, e.sons[1], "($#)($#)" % [ getTypeDesc(p.module, ranged), res]) of mConStrStr: genStrConcat(p, e, d) @@ -1762,7 +1777,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mNewSeqOfCap: genNewSeqOfCap(p, e, d) of mSizeOf: let t = e.sons[1].typ.skipTypes({tyTypeDesc}) - putIntoDest(p, d, e.typ, "((NI)sizeof($1))" % [getTypeDesc(p.module, t)]) + putIntoDest(p, d, e, "((NI)sizeof($1))" % [getTypeDesc(p.module, t)]) of mChr: genSomeCast(p, e, d) of mOrd: genOrd(p, e, d) of mLengthArray, mHigh, mLengthStr, mLengthSeq, mLengthOpenArray: @@ -1783,7 +1798,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = else: frmt = "$1 = $2->len;$n" lineCg(p, cpsStmts, frmt, tmp.r, rdLoc(a)) - putIntoDest(p, d, e.typ, tmp.r) + putIntoDest(p, d, e, tmp.r) of mGCref: unaryStmt(p, e, d, "#nimGCref($1);$n") of mGCunref: unaryStmt(p, e, d, "#nimGCunref($1);$n") of mSetLengthStr: genSetLengthStr(p, e, d) @@ -1825,7 +1840,7 @@ proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = var a, b, idx: TLoc if nfAllConst in e.flags: - putIntoDest(p, d, e.typ, genSetNode(p, e)) + putIntoDest(p, d, e, genSetNode(p, e)) else: if d.k == locNone: getTemp(p, e.typ, d) if getSize(e.typ) > 8: @@ -1872,7 +1887,7 @@ proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) = for i in countup(0, sonsLen(n) - 1): var it = n.sons[i] if it.kind == nkExprColonExpr: it = it.sons[1] - initLoc(rec, locExpr, it.typ, d.s) + initLoc(rec, locExpr, it, d.storage) rec.r = "$1.Field$2" % [rdLoc(d), rope(i)] expr(p, it, rec) @@ -1888,7 +1903,7 @@ proc genClosure(p: BProc, n: PNode, d: var TLoc) = var tmp = "CNSTCLOSURE" & rope(p.module.labels) addf(p.module.s[cfsData], "static NIM_CONST $1 $2 = $3;$n", [getTypeDesc(p.module, n.typ), tmp, genConstExpr(p, n)]) - putIntoDest(p, d, n.typ, tmp, OnStatic) + putIntoDest(p, d, n, tmp, OnStatic) else: var tmp, a, b: TLoc initLocExpr(p, n.sons[0], a) @@ -1911,7 +1926,7 @@ proc genArrayConstr(p: BProc, n: PNode, d: var TLoc) = if not handleConstExpr(p, n, d): if d.k == locNone: getTemp(p, n.typ, d) for i in countup(0, sonsLen(n) - 1): - initLoc(arr, locExpr, elemType(skipTypes(n.typ, abstractInst)), d.s) + initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), d.storage) arr.r = "$1[$2]" % [rdLoc(d), intLiteral(i)] expr(p, n.sons[i], arr) @@ -1949,11 +1964,11 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) = linefmt(p, cpsStmts, "#chckObj($1.m_type, $2);$n", r, genTypeInfo(p.module, dest)) if n.sons[0].typ.kind != tyObject: - putIntoDest(p, d, n.typ, - "(($1) ($2))" % [getTypeDesc(p.module, n.typ), rdLoc(a)], a.s) + putIntoDest(p, d, n, + "(($1) ($2))" % [getTypeDesc(p.module, n.typ), rdLoc(a)], a.storage) else: - putIntoDest(p, d, n.typ, "(*($1*) ($2))" % - [getTypeDesc(p.module, dest), addrLoc(a)], a.s) + putIntoDest(p, d, n, "(*($1*) ($2))" % + [getTypeDesc(p.module, dest), addrLoc(a)], a.storage) proc downConv(p: BProc, n: PNode, d: var TLoc) = if p.module.compileToCpp: @@ -1985,9 +2000,9 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) = linefmt(p, cpsStmts, "$1 = &$2;$n", rdLoc(d), r) else: r = "&" & r - putIntoDest(p, d, n.typ, r, a.s) + putIntoDest(p, d, n, r, a.storage) else: - putIntoDest(p, d, n.typ, r, a.s) + putIntoDest(p, d, n, r, a.storage) proc exprComplexConst(p: BProc, n: PNode, d: var TLoc) = let t = n.typ @@ -2002,13 +2017,13 @@ proc exprComplexConst(p: BProc, n: PNode, d: var TLoc) = [getTypeDesc(p.module, t), tmp, genConstExpr(p, n)]) if d.k == locNone: - fillLoc(d, locData, t, tmp, OnStatic) + fillLoc(d, locData, n, tmp, OnStatic) else: - putDataIntoDest(p, d, t, tmp) + putDataIntoDest(p, d, n, tmp) # This fixes bug #4551, but we really need better dataflow # analysis to make this 100% safe. if t.kind notin {tySequence, tyString}: - d.s = OnStatic + d.storage = OnStatic proc expr(p: BProc, n: PNode, d: var TLoc) = p.currLineInfo = n.info @@ -2019,31 +2034,31 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = of skMethod: if {sfDispatcher, sfForward} * sym.flags != {}: # we cannot produce code for the dispatcher yet: - fillProcLoc(p.module, sym) + fillProcLoc(p.module, n) genProcPrototype(p.module, sym) else: genProc(p.module, sym) putLocIntoDest(p, d, sym.loc) - of skProc, skConverter, skIterator: + of skProc, skConverter, skIterator, skFunc: #if sym.kind == skIterator: # echo renderTree(sym.getBody, {renderIds}) if sfCompileTime in sym.flags: localError(n.info, "request to generate code for .compileTime proc: " & sym.name.s) genProc(p.module, sym) - if sym.loc.r == nil or sym.loc.t == nil: + if sym.loc.r == nil or sym.loc.lode == nil: internalError(n.info, "expr: proc not init " & sym.name.s) putLocIntoDest(p, d, sym.loc) of skConst: if isSimpleConst(sym.typ): - putIntoDest(p, d, n.typ, genLiteral(p, sym.ast, sym.typ), OnStatic) + putIntoDest(p, d, n, genLiteral(p, sym.ast, sym.typ), OnStatic) else: genComplexConst(p, sym, d) of skEnumField: - putIntoDest(p, d, n.typ, rope(sym.position)) + putIntoDest(p, d, n, rope(sym.position)) of skVar, skForVar, skResult, skLet: if {sfGlobal, sfThread} * sym.flags != {}: - genVarPrototype(p.module, sym) + genVarPrototype(p.module, n) if sym.loc.r == nil or sym.loc.t == nil: #echo "FAILED FOR PRCO ", p.prc.name.s #echo renderTree(p.prc.ast, {renderIds}) @@ -2051,7 +2066,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = if sfThread in sym.flags: accessThreadLocalVar(p, sym) if emulatedThreadVars(): - putIntoDest(p, d, sym.loc.t, "NimTV_->" & sym.loc.r) + putIntoDest(p, d, sym.loc.lode, "NimTV_->" & sym.loc.r) else: putLocIntoDest(p, d, sym.loc) else: @@ -2072,12 +2087,12 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = else: internalError(n.info, "expr(" & $sym.kind & "); unknown symbol") of nkNilLit: if not isEmptyType(n.typ): - putIntoDest(p, d, n.typ, genLiteral(p, n)) + putIntoDest(p, d, n, genLiteral(p, n)) of nkStrLit..nkTripleStrLit: - putDataIntoDest(p, d, n.typ, genLiteral(p, n)) + putDataIntoDest(p, d, n, genLiteral(p, n)) of nkIntLit..nkUInt64Lit, nkFloatLit..nkFloat128Lit, nkCharLit: - putIntoDest(p, d, n.typ, genLiteral(p, n)) + putIntoDest(p, d, n, genLiteral(p, n)) of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: genLineDir(p, n) @@ -2097,7 +2112,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = genCall(p, n, d) of nkCurly: if isDeepConstExpr(n) and n.len != 0: - putIntoDest(p, d, n.typ, genSetNode(p, n)) + putIntoDest(p, d, n, genSetNode(p, n)) else: genSetConstr(p, n, d) of nkBracket: @@ -2138,7 +2153,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = of nkLambdaKinds: var sym = n.sons[namePos].sym genProc(p.module, sym) - if sym.loc.r == nil or sym.loc.t == nil: + if sym.loc.r == nil or sym.loc.lode == nil: internalError(n.info, "expr: proc not init " & sym.name.s) putLocIntoDest(p, d, sym.loc) of nkClosure: genClosure(p, n, d) @@ -2190,7 +2205,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = discard of nkPragma: genPragma(p, n) of nkPragmaBlock: expr(p, n.lastSon, d) - of nkProcDef, nkMethodDef, nkConverterDef: + of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: if n.sons[genericParamsPos].kind == nkEmpty: var prc = n.sons[namePos].sym # due to a bug/limitation in the lambda lifting, unused inner procs diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 8796dd729..eb32e7dd0 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -48,16 +48,17 @@ proc genVarTuple(p: BProc, n: PNode) = initLocExpr(p, n.sons[L-1], tup) var t = tup.t.skipTypes(abstractInst) for i in countup(0, L-3): - var v = n.sons[i].sym + let vn = n.sons[i] + let v = vn.sym if sfCompileTime in v.flags: continue if sfGlobal in v.flags: - assignGlobalVar(p, v) + assignGlobalVar(p, vn) genObjectInit(p, cpsInit, v.typ, v.loc, true) registerGcRoot(p, v) else: - assignLocalVar(p, v) + assignLocalVar(p, vn) initLocalVar(p, v, immediateAsgn=isAssignedImmediately(n[L-1])) - initLoc(field, locExpr, t.sons[i], tup.s) + initLoc(field, locExpr, vn, tup.storage) if t.kind == tyTuple: field.r = "$1.Field$2" % [rdLoc(tup), rope(i)] else: @@ -171,7 +172,7 @@ proc genBreakState(p: BProc, n: PNode) = lineF(p, cpsStmts, "if ((((NI*) $1.ClE_0)[1]) < 0) break;$n", [rdLoc(a)]) # lineF(p, cpsStmts, "if (($1) < 0) break;$n", [rdLoc(a)]) -proc genVarPrototypeAux(m: BModule, sym: PSym) +proc genVarPrototypeAux(m: BModule, n: PNode) proc genGotoVar(p: BProc; value: PNode) = if value.kind notin {nkCharLit..nkUInt64Lit}: @@ -180,7 +181,8 @@ proc genGotoVar(p: BProc; value: PNode) = lineF(p, cpsStmts, "goto NIMSTATE_$#;$n", [value.intVal.rope]) proc genSingleVar(p: BProc, a: PNode) = - let v = a.sons[0].sym + let vn = a.sons[0] + let v = vn.sym if sfCompileTime in v.flags: return if sfGoto in v.flags: # translate 'var state {.goto.} = X' into 'goto LX': @@ -195,7 +197,7 @@ proc genSingleVar(p: BProc, a: PNode) = if sfPure in v.flags: # v.owner.kind != skModule: targetProc = p.module.preInitProc - assignGlobalVar(targetProc, v) + assignGlobalVar(targetProc, vn) # XXX: be careful here. # Global variables should not be zeromem-ed within loops # (see bug #20). @@ -206,7 +208,7 @@ proc genSingleVar(p: BProc, a: PNode) = # Alternative construction using default constructor (which may zeromem): # if sfImportc notin v.flags: constructLoc(p.module.preInitProc, v.loc) if sfExportc in v.flags and p.module.g.generatedHeader != nil: - genVarPrototypeAux(p.module.g.generatedHeader, v) + genVarPrototypeAux(p.module.g.generatedHeader, vn) registerGcRoot(p, v) else: let value = a.sons[2] @@ -217,7 +219,7 @@ proc genSingleVar(p: BProc, a: PNode) = # parameterless constructor followed by an assignment operator. So we # generate better code here: genLineDir(p, a) - let decl = localVarDecl(p, v) + let decl = localVarDecl(p, vn) var tmp: TLoc if value.kind in nkCallKinds and value[0].kind == nkSym and sfConstructor in value[0].sym.flags: @@ -233,7 +235,7 @@ proc genSingleVar(p: BProc, a: PNode) = initLocExprSingleUse(p, value, tmp) lineF(p, cpsStmts, "$# = $#;$n", [decl, tmp.rdLoc]) return - assignLocalVar(p, v) + assignLocalVar(p, vn) initLocalVar(p, v, imm) if a.sons[2].kind != nkEmpty: @@ -513,7 +515,7 @@ proc genParForStmt(p: BProc, t: PNode) = preserveBreakIdx: let forLoopVar = t.sons[0].sym var rangeA, rangeB: TLoc - assignLocalVar(p, forLoopVar) + assignLocalVar(p, t.sons[0]) #initLoc(forLoopVar.loc, locLocalVar, forLoopVar.typ, onStack) #discard mangleName(forLoopVar) let call = t.sons[1] @@ -958,7 +960,7 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope = res.add(t.sons[i].strVal) of nkSym: var sym = t.sons[i].sym - if sym.kind in {skProc, skIterator, skMethod}: + if sym.kind in {skProc, skFunc, skIterator, skMethod}: var a: TLoc initLocExpr(p, t.sons[i], a) res.add($rdLoc(a)) diff --git a/compiler/ccgtrav.nim b/compiler/ccgtrav.nim index fa228ff04..4215a84b1 100644 --- a/compiler/ccgtrav.nim +++ b/compiler/ccgtrav.nim @@ -121,7 +121,8 @@ proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash; var c: TTraversalClosure var p = newProc(nil, m) result = "Marker_" & getTypeName(m, origTyp, sig) - let typ = origTyp.skipTypes(abstractInst) + var typ = origTyp.skipTypes(abstractInst) + if typ.kind == tyOpt: typ = optLowering(typ) case reason of tiNew: c.visitorFrmt = "#nimGCvisit((void*)$1, op);$n" diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 35d73aac0..254b13429 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -183,7 +183,7 @@ proc mapType(typ: PType): TCTypeKind = of 8: result = ctInt64 else: internalError("mapType") of tyRange: result = mapType(typ.sons[0]) - of tyPtr, tyVar, tyRef: + of tyPtr, tyVar, tyRef, tyOptAsRef: var base = skipTypes(typ.lastSon, typedescInst) case base.kind of tyOpenArray, tyArray, tyVarargs: result = ctPtrToArray @@ -194,6 +194,13 @@ proc mapType(typ: PType): TCTypeKind = else: result = ctPtr of tyPointer: result = ctPtr of tySequence: result = ctNimSeq + of tyOpt: + case optKind(typ) + of oBool: result = ctStruct + of oNil, oPtr: result = ctPtr + of oEnum: + # The 'nil' value is always negative, so we always use a signed integer + result = if getSize(typ.sons[0]) == 8: ctInt64 else: ctInt32 of tyProc: result = if typ.callConv != ccClosure: ctProc else: ctStruct of tyString: result = ctNimStr of tyCString: result = ctCString @@ -282,12 +289,13 @@ proc ccgIntroducedPtr(s: PSym): bool = result = (getSize(pt) > platform.floatSize*2) or (optByRef in s.options) else: result = false -proc fillResult(param: PSym) = - fillLoc(param.loc, locParam, param.typ, ~"Result", +proc fillResult(param: PNode) = + fillLoc(param.sym.loc, locParam, param, ~"Result", OnStack) - if mapReturnType(param.typ) != ctArray and isInvalidReturnType(param.typ): - incl(param.loc.flags, lfIndirect) - param.loc.s = OnUnknown + let t = param.sym.typ + if mapReturnType(t) != ctArray and isInvalidReturnType(t): + incl(param.sym.loc.flags, lfIndirect) + param.sym.loc.storage = OnUnknown proc typeNameOrLiteral(m: BModule; t: PType, literal: string): Rope = if t.sym != nil and sfImportc in t.sym.flags and t.sym.magic == mNone: @@ -349,7 +357,7 @@ proc getTypeForward(m: BModule, typ: PType; sig: SigHash): Rope = if result != nil: return result = getTypePre(m, typ, sig) if result != nil: return - let concrete = typ.skipTypes(abstractInst) + let concrete = typ.skipTypes(abstractInst + {tyOpt}) case concrete.kind of tySequence, tyTuple, tyObject: result = getTypeName(m, typ, sig) @@ -375,6 +383,12 @@ proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet): Rope = of tySequence: result = getTypeForward(m, t, hashType(t)) & "*" pushType(m, t) + of tyOpt: + if optKind(etB) == oPtr: + result = getTypeForward(m, t, hashType(t)) & "*" + pushType(m, t) + else: + result = getTypeDescAux(m, t, check) else: result = getTypeDescAux(m, t, check) @@ -398,13 +412,13 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, var param = t.n.sons[i].sym if isCompileTimeOnly(param.typ): continue if params != nil: add(params, ~", ") - fillLoc(param.loc, locParam, param.typ, mangleParamName(m, param), + fillLoc(param.loc, locParam, t.n.sons[i], mangleParamName(m, param), param.paramStorageLoc) if ccgIntroducedPtr(param): add(params, getTypeDescWeak(m, param.typ, check)) add(params, ~"*") incl(param.loc.flags, lfIndirect) - param.loc.s = OnUnknown + param.loc.storage = OnUnknown elif weakDep: add(params, getTypeDescWeak(m, param.typ, check)) else: @@ -417,7 +431,7 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, var j = 0 while arr.kind in {tyOpenArray, tyVarargs}: # this fixes the 'sort' bug: - if param.typ.kind == tyVar: param.loc.s = OnUnknown + if param.typ.kind == tyVar: param.loc.storage = OnUnknown # need to pass hidden parameter: addf(params, ", NI $1Len_$2", [param.loc.r, j.rope]) inc(j) @@ -496,16 +510,16 @@ proc genRecordFieldsAux(m: BModule, n: PNode, let sname = mangleRecFieldName(m, field, rectype) let ae = if accessExpr != nil: "$1.$2" % [accessExpr, sname] else: sname - fillLoc(field.loc, locField, field.typ, ae, OnUnknown) + fillLoc(field.loc, locField, n, ae, OnUnknown) # for importcpp'ed objects, we only need to set field.loc, but don't # have to recurse via 'getTypeDescAux'. And not doing so prevents problems # with heavily templatized C++ code: if not isImportedCppType(rectype): - let fieldType = field.loc.t.skipTypes(abstractInst) + let fieldType = field.loc.lode.typ.skipTypes(abstractInst) if fieldType.kind == tyArray and tfUncheckedArray in fieldType.flags: addf(result, "$1 $2[SEQ_DECL_SIZE];$n", [getTypeDescAux(m, fieldType.elemType, check), sname]) - elif fieldType.kind == tySequence: + elif fieldType.kind in {tySequence, tyOpt}: # we need to use a weak dependency here for trecursive_table. addf(result, "$1 $2;$n", [getTypeDescWeak(m, field.loc.t, check), sname]) elif field.bitsize != 0: @@ -624,7 +638,7 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = excl(check, t.id) return case t.kind - of tyRef, tyPtr, tyVar: + of tyRef, tyOptAsRef, tyPtr, tyVar: var star = if t.kind == tyVar and tfVarIsPtr notin origTyp.flags and compileToCpp(m): "&" else: "*" var et = origTyp.skipTypes(abstractInst).lastSon @@ -651,6 +665,21 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = result = name & "*" & star m.typeCache[sig] = result pushType(m, et) + of tyOpt: + if etB.sons[0].kind in {tyObject, tyTuple}: + let name = getTypeForward(m, et, hashType et) + result = name & "*" & star + m.typeCache[sig] = result + pushType(m, et) + elif optKind(etB) == oBool: + let name = getTypeForward(m, et, hashType et) + result = name & "*" + m.typeCache[sig] = result + pushType(m, et) + else: + # else we have a strong dependency :-( + result = getTypeDescAux(m, et, check) & star + m.typeCache[sig] = result else: # else we have a strong dependency :-( result = getTypeDescAux(m, et, check) & star @@ -726,6 +755,38 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = else: result = rope("TGenericSeq") add(result, "*") + of tyOpt: + result = cacheGetType(m.typeCache, sig) + if result == nil: + case optKind(t) + of oBool: + result = cacheGetType(m.forwTypeCache, sig) + if result == nil: + result = getTypeName(m, origTyp, sig) + addf(m.s[cfsForwardTypes], getForwardStructFormat(m), + [structOrUnion(t), result]) + m.forwTypeCache[sig] = result + appcg(m, m.s[cfsSeqTypes], "struct $2 {$n" & + " NIM_BOOL Field0;$n" & + " $1 Field1;$n" & + "};$n", [getTypeDescAux(m, t.sons[0], check), result]) + of oPtr: + let et = t.sons[0] + if et.kind in {tyTuple, tyObject}: + let name = getTypeForward(m, et, hashType et) + result = name & "*" + pushType(m, et) + else: + result = getTypeDescAux(m, t.sons[0], check) & "*" + of oNil: + result = getTypeDescAux(m, t.sons[0], check) + of oEnum: + result = getTypeName(m, origTyp, sig) + if getSize(t.sons[0]) == 8: + addf(m.s[cfsTypes], "typedef NI64 $1;$n", [result]) + else: + addf(m.s[cfsTypes], "typedef NI32 $1;$n", [result]) + m.typeCache[sig] = result of tyArray: var n: BiggestInt = lengthOrd(t) if n <= 0: n = 1 # make an array of at least one element @@ -861,7 +922,7 @@ proc genProcHeader(m: BModule, prc: PSym): Rope = elif prc.typ.callConv == ccInline: result.add "static " var check = initIntSet() - fillLoc(prc.loc, locProc, prc.typ, mangleName(m, prc), OnUnknown) + fillLoc(prc.loc, locProc, prc.ast[namePos], mangleName(m, prc), OnUnknown) genProcParams(m, prc.typ, rettype, params, check) # careful here! don't access ``prc.ast`` as that could reload large parts of # the object graph! @@ -1113,6 +1174,8 @@ proc genDeepCopyProc(m: BModule; s: PSym; result: Rope) = proc genTypeInfo(m: BModule, t: PType): Rope = let origType = t var t = skipTypes(origType, irrelevantForBackend + tyUserTypeClasses) + if t.kind == tyOpt: + return genTypeInfo(m, optLowering(t)) let sig = hashType(origType) result = m.typeInfoMarker.getOrDefault(sig) @@ -1158,7 +1221,7 @@ proc genTypeInfo(m: BModule, t: PType): Rope = else: let x = fakeClosureType(t.owner) genTupleInfo(m, x, x, result) - of tySequence, tyRef: + of tySequence, tyRef, tyOptAsRef: genTypeInfoAux(m, t, t, result) if gSelectedGC >= gcMarkAndSweep: let markerProc = genTraverseProc(m, origType, sig, tiNew) diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index 6a7aa8951..4cfeeb3c3 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -100,7 +100,7 @@ proc getUniqueType*(key: PType): PType = if result == nil: gCanonicalTypes[k] = key result = key - of tyTypeDesc, tyTypeClasses, tyGenericParam, tyFromExpr, tyFieldAccessor: + of tyTypeDesc, tyTypeClasses, tyGenericParam, tyFromExpr: if key.isResolvedUserTypeClass: return getUniqueType(lastSon(key)) if key.sym != nil: @@ -126,7 +126,7 @@ proc getUniqueType*(key: PType): PType = result = slowSearch(key, k) of tyGenericInvocation, tyGenericBody, tyOpenArray, tyArray, tySet, tyRange, tyTuple, - tySequence, tyForward, tyVarargs, tyProxy: + tySequence, tyForward, tyVarargs, tyProxy, tyOpt: # we have to do a slow linear search because types may need # to be compared by their structure: result = slowSearch(key, k) @@ -157,7 +157,7 @@ proc getUniqueType*(key: PType): PType = else: # ugh, we need the canon here: result = slowSearch(key, k) - of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("getUniqueType") + of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("getUniqueType") proc makeSingleLineCString*(s: string): string = result = "\"" diff --git a/compiler/cgen.nim b/compiler/cgen.nim index b618837c7..6970d09c3 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -47,21 +47,31 @@ proc findPendingModule(m: BModule, s: PSym): BModule = var ms = getModule(s) result = m.g.modules[ms.position] -proc initLoc(result: var TLoc, k: TLocKind, typ: PType, s: TStorageLoc) = +proc initLoc(result: var TLoc, k: TLocKind, lode: PNode, s: TStorageLoc) = result.k = k - result.s = s - result.t = typ + result.storage = s + result.lode = lode result.r = nil result.flags = {} -proc fillLoc(a: var TLoc, k: TLocKind, typ: PType, r: Rope, s: TStorageLoc) = +proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, r: Rope, s: TStorageLoc) = # fills the loc if it is not already initialized if a.k == locNone: a.k = k - a.t = typ - a.s = s + a.lode = lode + a.storage = s if a.r == nil: a.r = r +proc t(a: TLoc): PType {.inline.} = + if a.lode.kind == nkSym: + result = a.lode.sym.typ + else: + result = a.lode.typ + +proc lodeTyp(t: PType): PNode = + result = newNode(nkEmpty) + result.typ = t + proc isSimpleConst(typ: PType): bool = let t = skipTypes(typ, abstractVar) result = t.kind notin @@ -286,7 +296,7 @@ proc resetLoc(p: BProc, loc: var TLoc) = if not isComplexValueType(typ): if containsGcRef: var nilLoc: TLoc - initLoc(nilLoc, locTemp, loc.t, OnStack) + initLoc(nilLoc, locTemp, loc.lode, OnStack) nilLoc.r = rope("NIM_NIL") genRefAssign(p, loc, nilLoc, {afSrcIsNil}) else: @@ -294,7 +304,7 @@ proc resetLoc(p: BProc, loc: var TLoc) = else: if optNilCheck in p.options: linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(loc)) - if loc.s != OnStack: + if loc.storage != OnStack: linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n", addrLoc(loc), genTypeInfo(p.module, loc.t)) # XXX: generated reset procs should not touch the m_type @@ -340,8 +350,8 @@ proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false) = result.r = "T" & rope(p.labels) & "_" linefmt(p, cpsLocals, "$1 $2;$n", getTypeDesc(p.module, t), result.r) result.k = locTemp - result.t = t - result.s = OnStack + result.lode = lodeTyp t + result.storage = OnStack result.flags = {} constructLoc(p, result, not needsInit) @@ -350,8 +360,8 @@ proc getIntTemp(p: BProc, result: var TLoc) = result.r = "T" & rope(p.labels) & "_" linefmt(p, cpsLocals, "NI $1;$n", result.r) result.k = locTemp - result.s = OnStack - result.t = getSysType(tyInt) + result.storage = OnStack + result.lode = lodeTyp getSysType(tyInt) result.flags = {} proc initGCFrame(p: BProc): Rope = @@ -375,9 +385,10 @@ proc localDebugInfo(p: BProc, s: PSym) = inc(p.maxFrameLen) inc p.blocks[p.blocks.len-1].frameLen -proc localVarDecl(p: BProc; s: PSym): Rope = +proc localVarDecl(p: BProc; n: PNode): Rope = + let s = n.sym if s.loc.k == locNone: - fillLoc(s.loc, locLocalVar, s.typ, mangleLocalName(p, s), OnStack) + fillLoc(s.loc, locLocalVar, n, mangleLocalName(p, s), OnStack) if s.kind == skLet: incl(s.loc.flags, lfNoDeepCopy) result = getTypeDesc(p.module, s.typ) if s.constraint.isNil: @@ -390,23 +401,24 @@ proc localVarDecl(p: BProc; s: PSym): Rope = else: result = s.cgDeclFrmt % [result, s.loc.r] -proc assignLocalVar(p: BProc, s: PSym) = +proc assignLocalVar(p: BProc, n: PNode) = #assert(s.loc.k == locNone) # not yet assigned # this need not be fulfilled for inline procs; they are regenerated # for each module that uses them! let nl = if optLineDir in gOptions: "" else: tnl - let decl = localVarDecl(p, s) & ";" & nl + let decl = localVarDecl(p, n) & ";" & nl line(p, cpsLocals, decl) - localDebugInfo(p, s) + localDebugInfo(p, n.sym) include ccgthreadvars proc varInDynamicLib(m: BModule, sym: PSym) proc mangleDynLibProc(sym: PSym): Rope -proc assignGlobalVar(p: BProc, s: PSym) = +proc assignGlobalVar(p: BProc, n: PNode) = + let s = n.sym if s.loc.k == locNone: - fillLoc(s.loc, locGlobalVar, s.typ, mangleName(p.module, s), OnHeap) + fillLoc(s.loc, locGlobalVar, n, mangleName(p.module, s), OnHeap) if lfDynamicLib in s.loc.flags: var q = findPendingModule(p.module, s) @@ -446,9 +458,10 @@ proc assignParam(p: BProc, s: PSym) = scopeMangledParam(p, s) localDebugInfo(p, s) -proc fillProcLoc(m: BModule; sym: PSym) = +proc fillProcLoc(m: BModule; n: PNode) = + let sym = n.sym if sym.loc.k == locNone: - fillLoc(sym.loc, locProc, sym.typ, mangleName(m, sym), OnStack) + fillLoc(sym.loc, locProc, n, mangleName(m, sym), OnStack) proc getLabel(p: BProc): TLabel = inc(p.labels) @@ -457,7 +470,7 @@ proc getLabel(p: BProc): TLabel = proc fixLabel(p: BProc, labl: TLabel) = lineF(p, cpsStmts, "$1: ;$n", [labl]) -proc genVarPrototype(m: BModule, sym: PSym) +proc genVarPrototype(m: BModule, n: PNode) proc requestConstImpl(p: BProc, sym: PSym) proc genStmts(p: BProc, t: PNode) proc expr(p: BProc, n: PNode, d: var TLoc) @@ -469,11 +482,11 @@ proc genLiteral(p: BProc, n: PNode): Rope proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope proc initLocExpr(p: BProc, e: PNode, result: var TLoc) = - initLoc(result, locNone, e.typ, OnUnknown) + initLoc(result, locNone, e, OnUnknown) expr(p, e, result) proc initLocExprSingleUse(p: BProc, e: PNode, result: var TLoc) = - initLoc(result, locNone, e.typ, OnUnknown) + initLoc(result, locNone, e, OnUnknown) result.flags.incl lfSingleUse expr(p, e, result) @@ -590,8 +603,8 @@ proc cgsym(m: BModule, name: string): Rope = var sym = magicsys.getCompilerProc(name) if sym != nil: case sym.kind - of skProc, skMethod, skConverter, skIterator: genProc(m, sym) - of skVar, skResult, skLet: genVarPrototype(m, sym) + of skProc, skFunc, skMethod, skConverter, skIterator: genProc(m, sym) + of skVar, skResult, skLet: genVarPrototype(m, newSymNode sym) of skType: discard getTypeDesc(m, sym.typ) else: internalError("cgsym: " & name & ": " & $sym.kind) else: @@ -645,7 +658,7 @@ proc closureSetup(p: BProc, prc: PSym) = internalError(prc.info, "closure generation failed") var env = ls.sym #echo "created environment: ", env.id, " for ", prc.name.s - assignLocalVar(p, env) + assignLocalVar(p, ls) # generate cast assignment: linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n", rdLoc(env.loc), getTypeDesc(p.module, env.typ)) @@ -676,29 +689,30 @@ proc genProcAux(m: BModule, prc: PSym) = if sfPure notin prc.flags and prc.typ.sons[0] != nil: if resultPos >= prc.ast.len: internalError(prc.info, "proc has no result symbol") - var res = prc.ast.sons[resultPos].sym # get result symbol + let resNode = prc.ast.sons[resultPos] + let res = resNode.sym # get result symbol if not isInvalidReturnType(prc.typ.sons[0]): if sfNoInit in prc.flags: incl(res.flags, sfNoInit) if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(prc.getBody); val != nil): - var decl = localVarDecl(p, res) + var decl = localVarDecl(p, resNode) var a: TLoc initLocExprSingleUse(p, val, a) linefmt(p, cpsStmts, "$1 = $2;$n", decl, rdLoc(a)) else: # declare the result symbol: - assignLocalVar(p, res) + assignLocalVar(p, resNode) assert(res.loc.r != nil) initLocalVar(p, res, immediateAsgn=false) returnStmt = rfmt(nil, "\treturn $1;$n", rdLoc(res.loc)) else: - fillResult(res) + fillResult(resNode) assignParam(p, res) if skipTypes(res.typ, abstractInst).kind == tyArray: #incl(res.loc.flags, lfIndirect) - res.loc.s = OnUnknown + res.loc.storage = OnUnknown for i in countup(1, sonsLen(prc.typ.n) - 1): - var param = prc.typ.n.sons[i].sym + let param = prc.typ.n.sons[i].sym if param.typ.isCompileTimeOnly: continue assignParam(p, param) closureSetup(p, prc) @@ -764,13 +778,13 @@ proc genProcPrototype(m: BModule, sym: PSym) = proc genProcNoForward(m: BModule, prc: PSym) = if lfImportCompilerProc in prc.loc.flags: - fillProcLoc(m, prc) + fillProcLoc(m, prc.ast[namePos]) useHeader(m, prc) # dependency to a compilerproc: discard cgsym(m, prc.name.s) return if lfNoDecl in prc.loc.flags: - fillProcLoc(m, prc) + fillProcLoc(m, prc.ast[namePos]) useHeader(m, prc) genProcPrototype(m, prc) elif prc.typ.callConv == ccInline: @@ -779,7 +793,7 @@ proc genProcNoForward(m: BModule, prc: PSym) = # a check for ``m.declaredThings``. if not containsOrIncl(m.declaredThings, prc.id): #if prc.loc.k == locNone: - fillProcLoc(m, prc) + fillProcLoc(m, prc.ast[namePos]) #elif {sfExportc, sfImportc} * prc.flags == {}: # # reset name to restore consistency in case of hashing collisions: # echo "resetting ", prc.id, " by ", m.module.name.s @@ -790,7 +804,7 @@ proc genProcNoForward(m: BModule, prc: PSym) = genProcAux(m, prc) elif lfDynamicLib in prc.loc.flags: var q = findPendingModule(m, prc) - fillProcLoc(q, prc) + fillProcLoc(q, prc.ast[namePos]) useHeader(m, prc) genProcPrototype(m, prc) if q != nil and not containsOrIncl(q.declaredThings, prc.id): @@ -799,13 +813,13 @@ proc genProcNoForward(m: BModule, prc: PSym) = symInDynamicLibPartial(m, prc) elif sfImportc notin prc.flags: var q = findPendingModule(m, prc) - fillProcLoc(q, prc) + fillProcLoc(q, prc.ast[namePos]) useHeader(m, prc) genProcPrototype(m, prc) if q != nil and not containsOrIncl(q.declaredThings, prc.id): genProcAux(q, prc) else: - fillProcLoc(m, prc) + fillProcLoc(m, prc.ast[namePos]) useHeader(m, prc) if sfInfixCall notin prc.flags: genProcPrototype(m, prc) @@ -813,7 +827,7 @@ proc requestConstImpl(p: BProc, sym: PSym) = var m = p.module useHeader(m, sym) if sym.loc.k == locNone: - fillLoc(sym.loc, locData, sym.typ, mangleName(p.module, sym), OnStatic) + fillLoc(sym.loc, locData, sym.ast, mangleName(p.module, sym), OnStatic) if lfNoDecl in sym.loc.flags: return # declare implementation: var q = findPendingModule(m, sym) @@ -836,7 +850,7 @@ proc genProc(m: BModule, prc: PSym) = if sfBorrow in prc.flags or not isActivated(prc): return if sfForward in prc.flags: addForwardedProc(m, prc) - fillProcLoc(m, prc) + fillProcLoc(m, prc.ast[namePos]) else: genProcNoForward(m, prc) if {sfExportc, sfCompilerProc} * prc.flags == {sfExportc} and @@ -846,10 +860,11 @@ proc genProc(m: BModule, prc: PSym) = if not containsOrIncl(m.g.generatedHeader.declaredThings, prc.id): genProcAux(m.g.generatedHeader, prc) -proc genVarPrototypeAux(m: BModule, sym: PSym) = +proc genVarPrototypeAux(m: BModule, n: PNode) = #assert(sfGlobal in sym.flags) + let sym = n.sym useHeader(m, sym) - fillLoc(sym.loc, locGlobalVar, sym.typ, mangleName(m, sym), OnHeap) + fillLoc(sym.loc, locGlobalVar, n, mangleName(m, sym), OnHeap) if (lfNoDecl in sym.loc.flags) or containsOrIncl(m.declaredThings, sym.id): return if sym.owner.id != m.module.id: @@ -865,8 +880,8 @@ proc genVarPrototypeAux(m: BModule, sym: PSym) = if sfVolatile in sym.flags: add(m.s[cfsVars], " volatile") addf(m.s[cfsVars], " $1;$n", [sym.loc.r]) -proc genVarPrototype(m: BModule, sym: PSym) = - genVarPrototypeAux(m, sym) +proc genVarPrototype(m: BModule, n: PNode) = + genVarPrototypeAux(m, n) proc addIntTypes(result: var Rope) {.inline.} = addf(result, "#define NIM_NEW_MANGLING_RULES" & tnl & diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index dc97e3648..9863e90bb 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -107,3 +107,4 @@ proc initDefines*() = defineSymbol("nimDistros") defineSymbol("nimHasCppDefine") defineSymbol("nimGenericInOutFlags") + when false: defineSymbol("nimHasOpt") diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 3f4f7b164..8c50a4f1d 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -325,7 +325,7 @@ proc complexName(k: TSymKind, n: PNode, baseName: string): string = ## section of ``doc/docgen.txt``. result = baseName case k: - of skProc: result.add(defaultParamSeparator) + of skProc, skFunc: result.add(defaultParamSeparator) of skMacro: result.add(".m" & defaultParamSeparator) of skMethod: result.add(".e" & defaultParamSeparator) of skIterator: result.add(".i" & defaultParamSeparator) @@ -341,7 +341,7 @@ proc isCallable(n: PNode): bool = ## Returns true if `n` contains a callable node. case n.kind of nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, - nkConverterDef: result = true + nkConverterDef, nkFuncDef: result = true else: result = false @@ -533,6 +533,9 @@ proc generateDoc*(d: PDoc, n: PNode) = of nkProcDef: when useEffectSystem: documentRaises(n) genItem(d, n, n.sons[namePos], skProc) + of nkFuncDef: + when useEffectSystem: documentRaises(n) + genItem(d, n, n.sons[namePos], skFunc) of nkMethodDef: when useEffectSystem: documentRaises(n) genItem(d, n, n.sons[namePos], skMethod) @@ -574,6 +577,9 @@ proc generateJson*(d: PDoc, n: PNode) = of nkProcDef: when useEffectSystem: documentRaises(n) d.add genJsonItem(d, n, n.sons[namePos], skProc) + of nkFuncDef: + when useEffectSystem: documentRaises(n) + d.add genJsonItem(d, n, n.sons[namePos], skFunc) of nkMethodDef: when useEffectSystem: documentRaises(n) d.add genJsonItem(d, n, n.sons[namePos], skMethod) @@ -604,8 +610,8 @@ proc generateJson*(d: PDoc, n: PNode) = proc genSection(d: PDoc, kind: TSymKind) = const sectionNames: array[skModule..skTemplate, string] = [ - "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Methods", - "Iterators", "Converters", "Macros", "Templates" + "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs", + "Methods", "Iterators", "Converters", "Macros", "Templates" ] if d.section[kind] == nil: return var title = sectionNames[kind].rope diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index c47e4fb9a..4614eafe6 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -652,9 +652,10 @@ proc getLinkCmd(projectfile, objfiles: string): string = else: var linkerExe = getConfigVar(cCompiler, ".linkerexe") if len(linkerExe) == 0: linkerExe = cCompiler.getLinkerExe + # bug #6452: We must not use ``quoteShell`` here for ``linkerExe`` if needsExeExt(): linkerExe = addFileExt(linkerExe, "exe") - if noAbsolutePaths(): result = quoteShell(linkerExe) - else: result = quoteShell(joinPath(ccompilerpath, linkerExe)) + if noAbsolutePaths(): result = linkerExe + else: result = joinPath(ccompilerpath, linkerExe) let buildgui = if optGenGuiApp in gGlobalOptions: CC[cCompiler].buildGui else: "" var exefile, builddll: string @@ -701,6 +702,40 @@ template tryExceptOSErrorMessage(errorPrefix: string = "", body: untyped): typed rawMessage(errExecutionOfProgramFailed, ose.msg & " " & $ose.errorCode) raise +proc execLinkCmd(linkCmd: string) = + tryExceptOSErrorMessage("invocation of external linker program failed."): + execExternalProgram(linkCmd, + if optListCmd in gGlobalOptions or gVerbosity > 1: hintExecuting else: hintLinking) + +proc execCmdsInParallel(cmds: seq[string]; prettyCb: proc (idx: int)) = + let runCb = proc (idx: int, p: Process) = + let exitCode = p.peekExitCode + if exitCode != 0: + rawMessage(errGenerated, "execution of an external compiler program '" & + cmds[idx] & "' failed with exit code: " & $exitCode & "\n\n" & + p.outputStream.readAll.strip) + if gNumberOfProcessors == 0: gNumberOfProcessors = countProcessors() + var res = 0 + if gNumberOfProcessors <= 1: + for i in countup(0, high(cmds)): + tryExceptOSErrorMessage("invocation of external compiler program failed."): + res = execWithEcho(cmds[i]) + if res != 0: rawMessage(errExecutionOfProgramFailed, cmds[i]) + else: + tryExceptOSErrorMessage("invocation of external compiler program failed."): + if optListCmd in gGlobalOptions or gVerbosity > 1: + res = execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, + gNumberOfProcessors, afterRunEvent=runCb) + elif gVerbosity == 1: + res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams}, + gNumberOfProcessors, prettyCb, afterRunEvent=runCb) + else: + res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams}, + gNumberOfProcessors, afterRunEvent=runCb) + if res != 0: + if gNumberOfProcessors <= 1: + rawMessage(errExecutionOfProgramFailed, cmds.join()) + proc callCCompiler*(projectfile: string) = var linkCmd: string @@ -713,35 +748,9 @@ proc callCCompiler*(projectfile: string) = var prettyCmds: TStringSeq = @[] let prettyCb = proc (idx: int) = echo prettyCmds[idx] - let runCb = proc (idx: int, p: Process) = - let exitCode = p.peekExitCode - if exitCode != 0: - rawMessage(errGenerated, "execution of an external compiler program '" & - cmds[idx] & "' failed with exit code: " & $exitCode & "\n\n" & - p.outputStream.readAll.strip) compileCFile(toCompile, script, cmds, prettyCmds) if optCompileOnly notin gGlobalOptions: - if gNumberOfProcessors == 0: gNumberOfProcessors = countProcessors() - var res = 0 - if gNumberOfProcessors <= 1: - for i in countup(0, high(cmds)): - tryExceptOSErrorMessage("invocation of external compiler program failed."): - res = execWithEcho(cmds[i]) - if res != 0: rawMessage(errExecutionOfProgramFailed, cmds[i]) - else: - tryExceptOSErrorMessage("invocation of external compiler program failed."): - if optListCmd in gGlobalOptions or gVerbosity > 1: - res = execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, - gNumberOfProcessors, afterRunEvent=runCb) - elif gVerbosity == 1: - res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams}, - gNumberOfProcessors, prettyCb, afterRunEvent=runCb) - else: - res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams}, - gNumberOfProcessors, afterRunEvent=runCb) - if res != 0: - if gNumberOfProcessors <= 1: - rawMessage(errExecutionOfProgramFailed, cmds.join()) + execCmdsInParallel(cmds, prettyCb) if optNoLinking notin gGlobalOptions: # call the linker: var objfiles = "" @@ -756,9 +765,7 @@ proc callCCompiler*(projectfile: string) = linkCmd = getLinkCmd(projectfile, objfiles) if optCompileOnly notin gGlobalOptions: - tryExceptOSErrorMessage("invocation of external linker program failed."): - execExternalProgram(linkCmd, - if optListCmd in gGlobalOptions or gVerbosity > 1: hintExecuting else: hintLinking) + execLinkCmd(linkCmd) else: linkCmd = "" if optGenScript in gGlobalOptions: @@ -766,7 +773,8 @@ proc callCCompiler*(projectfile: string) = add(script, tnl) generateScript(projectfile, script) -from json import escapeJson +#from json import escapeJson +import json proc writeJsonBuildInstructions*(projectfile: string) = template lit(x: untyped) = f.write x @@ -834,6 +842,34 @@ proc writeJsonBuildInstructions*(projectfile: string) = lit "\L}\L" close(f) +proc runJsonBuildInstructions*(projectfile: string) = + let file = projectfile.splitFile.name + let jsonFile = toGeneratedFile(file, "json") + try: + let data = json.parseFile(jsonFile) + let toCompile = data["compile"] + doAssert toCompile.kind == JArray + var cmds: TStringSeq = @[] + var prettyCmds: TStringSeq = @[] + for c in toCompile: + doAssert c.kind == JArray + doAssert c.len >= 2 + + add(cmds, c[1].getStr) + let (_, name, _) = splitFile(c[0].getStr) + add(prettyCmds, "CC: " & name) + + let prettyCb = proc (idx: int) = + echo prettyCmds[idx] + execCmdsInParallel(cmds, prettyCb) + + let linkCmd = data["linkcmd"] + doAssert linkCmd.kind == JString + execLinkCmd(linkCmd.getStr) + except: + echo getCurrentException().getStackTrace() + quit "error evaluating JSON file: " & jsonFile + proc genMappingFiles(list: CFileList): Rope = for it in list: addf(result, "--file:r\"$1\"$N", [rope(it.cname)]) diff --git a/compiler/gorgeimpl.nim b/compiler/gorgeimpl.nim new file mode 100644 index 000000000..4e1ce6d50 --- /dev/null +++ b/compiler/gorgeimpl.nim @@ -0,0 +1,83 @@ +# +# +# The Nim Compiler +# (c) Copyright 2017 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Module that implements ``gorge`` for the compiler as well as +## the scriptable import mechanism. + +import msgs, securehash, os, osproc, streams, strutils, options + +proc readOutput(p: Process): (string, int) = + result[0] = "" + var output = p.outputStream + while not output.atEnd: + result[0].add(output.readLine) + result[0].add("\n") + if result[0].len > 0: + result[0].setLen(result[0].len - "\n".len) + result[1] = p.waitForExit + +proc opGorge*(cmd, input, cache: string, info: TLineInfo): (string, int) = + let workingDir = parentDir(info.toFullPath) + if cache.len > 0:# and optForceFullMake notin gGlobalOptions: + let h = secureHash(cmd & "\t" & input & "\t" & cache) + let filename = options.toGeneratedFile("gorge_" & $h, "txt") + var f: File + if open(f, filename): + result = (f.readAll, 0) + f.close + return + var readSuccessful = false + try: + var p = startProcess(cmd, workingDir, + options={poEvalCommand, poStderrToStdout}) + if input.len != 0: + p.inputStream.write(input) + p.inputStream.close() + result = p.readOutput + readSuccessful = true + # only cache successful runs: + if result[1] == 0: + writeFile(filename, result[0]) + except IOError, OSError: + if not readSuccessful: result = ("", -1) + else: + try: + var p = startProcess(cmd, workingDir, + options={poEvalCommand, poStderrToStdout}) + if input.len != 0: + p.inputStream.write(input) + p.inputStream.close() + result = p.readOutput + except IOError, OSError: + result = ("", -1) + +proc scriptableImport*(pkg, subdir: string; info: TLineInfo): string = + var cmd = getConfigVar("resolver.exe") + if cmd.len == 0: cmd = "nimresolve" + else: cmd = quoteShell(cmd) + cmd.add " --source:" + cmd.add quoteShell(info.toFullPath()) + cmd.add " --stdlib:" + cmd.add quoteShell(options.libpath) + cmd.add " --project:" + cmd.add quoteShell(gProjectFull) + if subdir.len != 0: + cmd.add " --subdir:" + cmd.add quoteShell(subdir) + if options.gNoNimblePath: + cmd.add " --nonimblepath" + cmd.add ' ' + cmd.add quoteShell(pkg) + let (res, exitCode) = opGorge(cmd, "", cmd, info) + if exitCode == 0: + result = res.strip() + elif res.len > 0: + localError(info, res) + else: + localError(info, "cannot resolve: " & (pkg / subdir)) diff --git a/compiler/importer.nim b/compiler/importer.nim index c4861df7f..07f42a147 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -11,11 +11,22 @@ import intsets, strutils, os, ast, astalgo, msgs, options, idents, rodread, lookups, - semdata, passes, renderer + semdata, passes, renderer, gorgeimpl proc evalImport*(c: PContext, n: PNode): PNode proc evalFrom*(c: PContext, n: PNode): PNode +proc lookupPackage(pkg, subdir: PNode): string = + let sub = if subdir != nil: renderTree(subdir, {renderNoComments}).replace(" ") else: "" + case pkg.kind + of nkStrLit, nkRStrLit, nkTripleStrLit: + result = scriptableImport(pkg.strVal, sub, pkg.info) + of nkIdent: + result = scriptableImport(pkg.ident.s, sub, pkg.info) + else: + localError(pkg.info, "package name must be an identifier or string literal") + result = "" + proc getModuleName*(n: PNode): string = # This returns a short relative module name without the nim extension # e.g. like "system", "importer" or "somepath/module" @@ -31,16 +42,33 @@ proc getModuleName*(n: PNode): string = result = n.ident.s of nkSym: result = n.sym.name.s - of nkInfix, nkPrefix: - if n.sons[0].kind == nkIdent and n.sons[0].ident.id == getIdent("as").id: + of nkInfix: + let n0 = n[0] + let n1 = n[1] + if n0.kind == nkIdent and n0.ident.id == getIdent("as").id: # XXX hack ahead: n.kind = nkImportAs n.sons[0] = n.sons[1] n.sons[1] = n.sons[2] n.sons.setLen(2) return getModuleName(n.sons[0]) - # hacky way to implement 'x / y /../ z': - result = renderTree(n, {renderNoComments}).replace(" ") + if n1.kind == nkPrefix and n1[0].kind == nkIdent and n1[0].ident.s == "$": + if n0.kind == nkIdent and n0.ident.s == "/": + result = lookupPackage(n1[1], n[2]) + else: + localError(n.info, "only '/' supported with $package notation") + result = "" + else: + # hacky way to implement 'x / y /../ z': + result = getModuleName(n1) + result.add renderTree(n0, {renderNoComments}) + result.add getModuleName(n[2]) + of nkPrefix: + if n.sons[0].kind == nkIdent and n.sons[0].ident.s == "$": + result = lookupPackage(n[1], nil) + else: + # hacky way to implement 'x / y /../ z': + result = renderTree(n, {renderNoComments}).replace(" ") of nkDotExpr: result = renderTree(n, {renderNoComments}).replace(".", "/") of nkImportAs: @@ -60,6 +88,11 @@ proc checkModuleName*(n: PNode; doLocalError=true): int32 = else: result = fullPath.fileInfoIdx +proc importPureEnumField*(c: PContext; s: PSym) = + var check = strTableGet(c.importTable.symbols, s.name) + if check == nil: + strTableAdd(c.pureEnumFields, s) + proc rawImportSymbol(c: PContext, s: PSym) = # This does not handle stubs, because otherwise loading on demand would be # pointless in practice. So importing stubs is fine here! @@ -75,7 +108,7 @@ proc rawImportSymbol(c: PContext, s: PSym) = strTableAdd(c.importTable.symbols, s) if s.kind == skType: var etyp = s.typ - if etyp.kind in {tyBool, tyEnum} and sfPure notin s.flags: + if etyp.kind in {tyBool, tyEnum}: for j in countup(0, sonsLen(etyp.n) - 1): var e = etyp.n.sons[j].sym if e.kind != skEnumField: @@ -91,7 +124,10 @@ proc rawImportSymbol(c: PContext, s: PSym) = break check = nextIdentIter(it, c.importTable.symbols) if e != nil: - rawImportSymbol(c, e) + if sfPure notin s.flags: + rawImportSymbol(c, e) + else: + importPureEnumField(c, e) else: # rodgen assures that converters and patterns are no stubs if s.kind == skConverter: addConverter(c, s) @@ -201,12 +237,14 @@ proc evalImport(c: PContext, n: PNode): PNode = for i in countup(0, sonsLen(n) - 1): let it = n.sons[i] if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket: - let sep = renderTree(it.sons[0], {renderNoComments}) - let dir = renderTree(it.sons[1], {renderNoComments}) + let sep = it[0] + let dir = it[1] + let a = newNodeI(nkInfix, it.info) + a.add sep + a.add dir + a.add sep # dummy entry, replaced in the loop for x in it[2]: - let f = renderTree(x, {renderNoComments}) - let a = newStrNode(nkStrLit, (dir & sep & f).replace(" ")) - a.info = it.info + a.sons[2] = x impMod(c, a) else: impMod(c, it) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 73e6a9948..7b1c43817 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -187,12 +187,12 @@ proc mapType(typ: PType): TJSTypeKind = of tyBool: result = etyBool of tyFloat..tyFloat128: result = etyFloat of tySet: result = etyObject # map a set to a table - of tyString, tySequence: result = etySeq + of tyString, tySequence, tyOpt: result = etySeq of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs: result = etyObject of tyNil: result = etyNull of tyGenericInst, tyGenericParam, tyGenericBody, tyGenericInvocation, - tyNone, tyFromExpr, tyForward, tyEmpty, tyFieldAccessor, + tyNone, tyFromExpr, tyForward, tyEmpty, tyExpr, tyStmt, tyTypeDesc, tyTypeClasses, tyVoid, tyAlias: result = etyNone of tyInferred: @@ -202,7 +202,7 @@ proc mapType(typ: PType): TJSTypeKind = else: result = etyNone of tyProc: result = etyProc of tyCString: result = etyString - of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("mapType") + of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("mapType") proc mapType(p: PProc; typ: PType): TJSTypeKind = if p.target == targetPHP: result = etyObject @@ -292,7 +292,7 @@ proc useMagic(p: PProc, name: string) = if name.len == 0: return var s = magicsys.getCompilerProc(name) if s != nil: - internalAssert s.kind in {skProc, skMethod, skConverter} + internalAssert s.kind in {skProc, skFunc, skMethod, skConverter} if not p.g.generatedSyms.containsOrIncl(s.id): let code = genProc(p, s) add(p.g.constants, code) @@ -927,7 +927,7 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = # we don't care if it's an etyBaseIndex (global) of a string, it's # still a string that needs to be copied properly: - if x.typ.skipTypes(abstractInst).kind in {tySequence, tyString}: + if x.typ.skipTypes(abstractInst).kind in {tySequence, tyOpt, tyString}: xtyp = etySeq case xtyp of etySeq: @@ -971,7 +971,7 @@ proc genFastAsgn(p: PProc, n: PNode) = # See bug #5933. So we try to be more compatible with the C backend semantics # here for 'shallowCopy'. This is an educated guess and might require further # changes later: - let noCopy = n[0].typ.skipTypes(abstractInst).kind in {tySequence, tyString} + let noCopy = n[0].typ.skipTypes(abstractInst).kind in {tySequence, tyOpt, tyString} genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=noCopy) proc genSwap(p: PProc, n: PNode) = @@ -1111,7 +1111,7 @@ template isIndirect(x: PSym): bool = ({sfAddrTaken, sfGlobal} * v.flags != {} and #(mapType(v.typ) != etyObject) and {sfImportc, sfVolatile, sfExportc} * v.flags == {} and - v.kind notin {skProc, skConverter, skMethod, skIterator, + v.kind notin {skProc, skFunc, skConverter, skMethod, skIterator, skConst, skTemp, skLet} and p.target == targetJS) proc genAddr(p: PProc, n: PNode, r: var TCompRes) = @@ -1237,7 +1237,7 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) = else: r.res = "$" & s.loc.r p.declareGlobal(s.id, r.res) - of skProc, skConverter, skMethod: + of skProc, skFunc, skConverter, skMethod: discard mangleName(s, p.target) if p.target == targetPHP and r.kind != resCallee: r.res = makeJsString($s.loc.r) @@ -1550,7 +1550,7 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope = result = putToSeq("[null, 0]", indirect) else: result = putToSeq("null", indirect) - of tySequence, tyString, tyCString, tyPointer, tyProc: + of tySequence, tyOpt, tyString, tyCString, tyPointer, tyProc: result = putToSeq("null", indirect) of tyStatic: if t.n != nil: @@ -2338,7 +2338,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt, nkFromStmt, nkTemplateDef, nkMacroDef: discard of nkPragma: genPragma(p, n) - of nkProcDef, nkMethodDef, nkConverterDef: + of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: var s = n.sons[namePos].sym if {sfExportc, sfCompilerProc} * s.flags == {sfExportc}: genSym(p, n.sons[namePos], r) diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 986d8c716..e64e0a898 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -194,7 +194,7 @@ proc illegalCapture(s: PSym): bool {.inline.} = s.kind == skResult proc isInnerProc(s: PSym): bool = - if s.kind in {skProc, skMethod, skConverter, skIterator} and s.magic == mNone: + if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator} and s.magic == mNone: result = s.skipGenericOwner.kind in routineKinds proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode = @@ -371,7 +371,8 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) = case n.kind of nkSym: let s = n.sym - if s.kind in {skProc, skMethod, skConverter, skIterator} and s.typ != nil and s.typ.callConv == ccClosure: + if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator} and + s.typ != nil and s.typ.callConv == ccClosure: # this handles the case that the inner proc was declared as # .closure but does not actually capture anything: addClosureParam(c, s, n.info) @@ -443,7 +444,7 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) = discard of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef: discard - of nkLambdaKinds, nkIteratorDef: + of nkLambdaKinds, nkIteratorDef, nkFuncDef: if n.typ != nil: detectCapturedVars(n[namePos], owner, c) else: @@ -730,7 +731,7 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; # now we know better, so patch it: n.sons[0] = x.sons[0] n.sons[1] = x.sons[1] - of nkLambdaKinds, nkIteratorDef: + of nkLambdaKinds, nkIteratorDef, nkFuncDef: if n.typ != nil and n[namePos].kind == nkSym: let m = newSymNode(n[namePos].sym) m.typ = n.typ diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 09bcb4ce0..45d090b16 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -48,7 +48,7 @@ type tkShl, tkShr, tkStatic, tkTemplate, tkTry, tkTuple, tkType, tkUsing, - tkVar, tkWhen, tkWhile, tkWith, tkWithout, tkXor, + tkVar, tkWhen, tkWhile, tkXor, tkYield, # end of keywords tkIntLit, tkInt8Lit, tkInt16Lit, tkInt32Lit, tkInt64Lit, tkUIntLit, tkUInt8Lit, tkUInt16Lit, tkUInt32Lit, tkUInt64Lit, @@ -89,7 +89,7 @@ const "shl", "shr", "static", "template", "try", "tuple", "type", "using", - "var", "when", "while", "with", "without", "xor", + "var", "when", "while", "xor", "yield", "tkIntLit", "tkInt8Lit", "tkInt16Lit", "tkInt32Lit", "tkInt64Lit", "tkUIntLit", "tkUInt8Lit", "tkUInt16Lit", "tkUInt32Lit", "tkUInt64Lit", diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 5c326d10a..eddfeea56 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -144,8 +144,10 @@ type proc getSymRepr*(s: PSym): string = case s.kind - of skProc, skMethod, skConverter, skIterator: result = getProcHeader(s) - else: result = s.name.s + of skProc, skFunc, skMethod, skConverter, skIterator: + result = getProcHeader(s) + else: + result = s.name.s proc ensureNoMissingOrUnusedSymbols(scope: PScope) = # check if all symbols have been used and defined: @@ -286,7 +288,7 @@ proc lookUp*(c: PContext, n: PNode): PSym = type TLookupFlag* = enum - checkAmbiguity, checkUndeclared, checkModule + checkAmbiguity, checkUndeclared, checkModule, checkPureEnumFields proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = const allExceptModule = {low(TSymKind)..high(TSymKind)}-{skModule,skPackage} @@ -297,6 +299,8 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym = result = searchInScopes(c, ident).skipAlias(n) else: result = searchInScopes(c, ident, allExceptModule).skipAlias(n) + if result == nil and checkPureEnumFields in flags: + result = strTableGet(c.pureEnumFields, ident) if result == nil and checkUndeclared in flags: fixSpelling(n, ident, searchInScopes) errorUndeclaredIdentifier(c, n.info, ident.s) diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index ce76b63a4..033472c07 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -633,7 +633,7 @@ proc wrapProcForSpawn*(owner: PSym; spawnExpr: PNode; retType: PType; if fn.kind == nkClosure: localError(n.info, "closure in spawn environment is not allowed") if not (fn.kind == nkSym and fn.sym.kind in {skProc, skTemplate, skMacro, - skMethod, skConverter}): + skFunc, skMethod, skConverter}): # for indirect calls we pass the function pointer in the scratchObj var argType = n[0].typ.skipTypes(abstractInst) var field = newSym(skField, getIdent"fn", owner, n.info) diff --git a/compiler/main.nim b/compiler/main.nim index f662ded1b..450542c4c 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -78,6 +78,10 @@ proc commandCompileToC(graph: ModuleGraph; cache: IdentCache) = extccomp.callCCompiler(proj) extccomp.writeJsonBuildInstructions(proj) +proc commandJsonScript(graph: ModuleGraph; cache: IdentCache) = + let proj = changeFileExt(gProjectFull, "") + extccomp.runJsonBuildInstructions(proj) + proc commandCompileToJS(graph: ModuleGraph; cache: IdentCache) = #incl(gGlobalOptions, optSafeCode) setTarget(osJS, cpuJS) @@ -266,6 +270,9 @@ proc mainCommand*(graph: ModuleGraph; cache: IdentCache) = of "nop", "help": # prevent the "success" message: gCmd = cmdDump + of "jsonscript": + gCmd = cmdJsonScript + commandJsonScript(graph, cache) else: rawMessage(errInvalidCommandX, command) diff --git a/compiler/nim.nim b/compiler/nim.nim index 56885e9f1..89225a5e0 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -119,4 +119,6 @@ condsyms.initDefines() when not defined(selftest): handleCmdLine(newIdentCache(), newConfigRef()) + when declared(GC_setMaxPause): + echo GC_getStatistics() msgQuit(int8(msgs.gErrorCounter > 0)) diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim index ab63f9e12..39c3a17e7 100644 --- a/compiler/nimblecmd.nim +++ b/compiler/nimblecmd.nim @@ -16,11 +16,11 @@ proc addPath*(path: string, info: TLineInfo) = options.searchPaths.insert(path, 0) type - Version = distinct string + Version* = distinct string -proc `$`(ver: Version): string {.borrow.} +proc `$`*(ver: Version): string {.borrow.} -proc newVersion(ver: string): Version = +proc newVersion*(ver: string): Version = doAssert(ver.len == 0 or ver[0] in {'#', '\0'} + Digits, "Wrong version: " & ver) return Version(ver) @@ -28,7 +28,7 @@ proc newVersion(ver: string): Version = proc isSpecial(ver: Version): bool = return ($ver).len > 0 and ($ver)[0] == '#' -proc `<`(ver: Version, ver2: Version): bool = +proc `<`*(ver: Version, ver2: Version): bool = ## This is synced from Nimble's version module. # Handling for special versions such as "#head" or "#branch". diff --git a/compiler/options.nim b/compiler/options.nim index 40d56aea5..9f44514c5 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -91,7 +91,8 @@ type cmdRst2html, # convert a reStructuredText file to HTML cmdRst2tex, # convert a reStructuredText file to TeX cmdInteractive, # start interactive session - cmdRun # run the project via TCC backend + cmdRun, # run the project via TCC backend + cmdJsonScript # compile a .json build file TStringSeq* = seq[string] TGCMode* = enum # the selected GC gcNone, gcBoehm, gcGo, gcRegions, gcMarkAndSweep, gcRefc, diff --git a/compiler/parser.nim b/compiler/parser.nim index 253716247..e14a8fbdf 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -255,13 +255,6 @@ proc isUnary(p: TParser): bool = p.tok.strongSpaceB == 0 and p.tok.strongSpaceA > 0: result = true - # versions prior to 0.13.0 used to do this: - when false: - if p.strongSpaces: - result = true - else: - parMessage(p, warnDeprecated, - "will be parsed as unary operator; inconsistent spacing") proc checkBinary(p: TParser) {.inline.} = ## Check if the current parser token is a binary operator. @@ -700,12 +693,7 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = result = r template somePar() = - if p.tok.strongSpaceA > 0: - if p.strongSpaces: - break - else: - parMessage(p, warnDeprecated, - "a [b] will be parsed as command syntax; spacing") + if p.tok.strongSpaceA > 0: break # progress guaranteed while p.tok.indent < 0 or (p.tok.tokType == tkDot and p.tok.indent >= baseIndent): @@ -991,7 +979,7 @@ proc parseDoBlock(p: var TParser; info: TLineInfo): PNode = if params.kind != nkEmpty: result = newProcNode(nkDo, info, result, params = params, pragmas = pragmas) -proc parseProcExpr(p: var TParser, isExpr: bool): PNode = +proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode = #| procExpr = 'proc' paramListColon pragmas? ('=' COMMENT? stmt)? # either a proc type or a anonymous proc let info = parLineInfo(p) @@ -1002,7 +990,7 @@ proc parseProcExpr(p: var TParser, isExpr: bool): PNode = if p.tok.tokType == tkEquals and isExpr: getTok(p) skipComment(p, result) - result = newProcNode(nkLambda, info, parseStmt(p), + result = newProcNode(kind, info, parseStmt(p), params = params, pragmas = pragmas) else: @@ -1014,7 +1002,7 @@ proc parseProcExpr(p: var TParser, isExpr: bool): PNode = proc isExprStart(p: TParser): bool = case p.tok.tokType of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, - tkProc, tkIterator, tkBind, tkAddr, + tkProc, tkFunc, tkIterator, tkBind, tkAddr, tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, tkTuple, tkObject, tkType, tkWhen, tkCase, tkOut: result = true @@ -1038,9 +1026,15 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind, optInd(p, result) if not isOperator(p.tok) and isExprStart(p): addSon(result, primary(p, mode)) - if kind == nkDistinctTy and p.tok.tokType in {tkWith, tkWithout}: - let nodeKind = if p.tok.tokType == tkWith: nkWith - else: nkWithout + if kind == nkDistinctTy and p.tok.tokType == tkSymbol: + # XXX document this feature! + var nodeKind: TNodeKind + if p.tok.ident.s == "with": + nodeKind = nkWith + elif p.tok.ident.s == "without": + nodeKind = nkWithout + else: + return result getTok(p) let list = newNodeP(nodeKind, p) result.addSon list @@ -1088,21 +1082,12 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = case p.tok.tokType: of tkTuple: result = parseTuple(p, mode == pmTypeDef) - of tkProc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}) + of tkProc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}, nkLambda) + of tkFunc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}, nkFuncDef) of tkIterator: - when false: - if mode in {pmTypeDesc, pmTypeDef}: - result = parseProcExpr(p, false) - result.kind = nkIteratorTy - else: - # no anon iterators for now: - parMessage(p, errExprExpected, p.tok) - getTok(p) # we must consume a token here to prevend endless loops! - result = ast.emptyNode - else: - result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}) - if result.kind == nkLambda: result.kind = nkIteratorDef - else: result.kind = nkIteratorTy + result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}, nkLambda) + if result.kind == nkLambda: result.kind = nkIteratorDef + else: result.kind = nkIteratorTy of tkEnum: if mode == pmTypeDef: result = parseEnum(p) @@ -2000,6 +1985,7 @@ proc complexOrSimpleStmt(p: var TParser): PNode = of tkDefer: result = parseStaticOrDefer(p, nkDefer) of tkAsm: result = parseAsm(p) of tkProc: result = parseRoutine(p, nkProcDef) + of tkFunc: result = parseRoutine(p, nkFuncDef) of tkMethod: result = parseRoutine(p, nkMethodDef) of tkIterator: result = parseRoutine(p, nkIteratorDef) of tkMacro: result = parseRoutine(p, nkMacroDef) @@ -2056,8 +2042,8 @@ proc parseStmt(p: var TParser): PNode = else: # the case statement is only needed for better error messages: case p.tok.tokType - of tkIf, tkWhile, tkCase, tkTry, tkFor, tkBlock, tkAsm, tkProc, tkIterator, - tkMacro, tkType, tkConst, tkWhen, tkVar: + of tkIf, tkWhile, tkCase, tkTry, tkFor, tkBlock, tkAsm, tkProc, tkFunc, + tkIterator, tkMacro, tkType, tkConst, tkWhen, tkVar: parMessage(p, errComplexStmtRequiresInd) result = ast.emptyNode else: diff --git a/compiler/passes.nim b/compiler/passes.nim index bf6ce1a0a..6efd50385 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -64,7 +64,7 @@ proc astNeeded*(s: PSym): bool = # needs to be stored. The passes manager frees s.sons[codePos] when # appropriate to free the procedure body's memory. This is important # to keep memory usage down. - if (s.kind in {skMethod, skProc}) and + if (s.kind in {skMethod, skProc, skFunc}) and ({sfCompilerProc, sfCompileTime} * s.flags == {}) and (s.typ.callConv != ccInline) and (s.ast.sons[genericParamsPos].kind == nkEmpty): diff --git a/compiler/pbraces.nim b/compiler/pbraces.nim index fa4ccc139..eba6f0b62 100644 --- a/compiler/pbraces.nim +++ b/compiler/pbraces.nim @@ -906,9 +906,14 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind, optInd(p, result) if not isOperator(p.tok) and isExprStart(p): addSon(result, primary(p, mode)) - if kind == nkDistinctTy and p.tok.tokType in {tkWith, tkWithout}: - let nodeKind = if p.tok.tokType == tkWith: nkWith - else: nkWithout + if kind == nkDistinctTy and p.tok.tokType == tkSymbol: + var nodeKind: TNodeKind + if p.tok.ident.s == "with": + nodeKind = nkWith + elif p.tok.ident.s == "without": + nodeKind = nkWithout + else: + return result getTok(p) let list = newNodeP(nodeKind, p) result.addSon list diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 220693f68..bbe81fe37 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -1079,9 +1079,9 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gsub(g, n.sons[0]) if n.len > 1: if n[1].kind == nkWith: - putWithSpace(g, tkWith, " with") + putWithSpace(g, tkSymbol, " with") else: - putWithSpace(g, tkWithout, " without") + putWithSpace(g, tkSymbol, " without") gcomma(g, n[1]) else: put(g, tkDistinct, "distinct") @@ -1166,6 +1166,9 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = of nkProcDef: if renderNoProcDefs notin g.flags: putWithSpace(g, tkProc, "proc") gproc(g, n) + of nkFuncDef: + if renderNoProcDefs notin g.flags: putWithSpace(g, tkFunc, "func") + gproc(g, n) of nkConverterDef: if renderNoProcDefs notin g.flags: putWithSpace(g, tkConverter, "converter") gproc(g, n) @@ -1324,7 +1327,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = if p.typ == nil or tfImplicitTypeParam notin p.typ.flags: return true return false - + if n.hasExplicitParams: put(g, tkBracketLe, "[") gsemicolon(g, n) @@ -1363,7 +1366,13 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = proc renderTree(n: PNode, renderFlags: TRenderFlags = {}): string = var g: TSrcGen initSrcGen(g, renderFlags) - gsub(g, n) + # do not indent the initial statement list so that + # writeFile("file.nim", repr n) + # produces working Nim code: + if n.kind in {nkStmtList, nkStmtListExpr, nkStmtListType}: + gstmts(g, n, emptyContext, doIndent = false) + else: + gsub(g, n) result = g.buf proc renderModule(n: PNode, filename: string, diff --git a/compiler/rodread.nim b/compiler/rodread.nim index f7e5a0f84..31b54d760 100644 --- a/compiler/rodread.nim +++ b/compiler/rodread.nim @@ -257,9 +257,9 @@ proc decodeLoc(r: PRodReader, loc: var TLoc, info: TLineInfo) = loc.k = low(loc.k) if r.s[r.pos] == '*': inc(r.pos) - loc.s = TStorageLoc(decodeVInt(r.s, r.pos)) + loc.storage = TStorageLoc(decodeVInt(r.s, r.pos)) else: - loc.s = low(loc.s) + loc.storage = low(loc.storage) if r.s[r.pos] == '$': inc(r.pos) loc.flags = cast[TLocFlags](int32(decodeVInt(r.s, r.pos))) @@ -267,9 +267,10 @@ proc decodeLoc(r: PRodReader, loc: var TLoc, info: TLineInfo) = loc.flags = {} if r.s[r.pos] == '^': inc(r.pos) - loc.t = rrGetType(r, decodeVInt(r.s, r.pos), info) + loc.lode = decodeNode(r, info) + # rrGetType(r, decodeVInt(r.s, r.pos), info) else: - loc.t = nil + loc.lode = nil if r.s[r.pos] == '!': inc(r.pos) loc.r = rope(decodeStr(r.s, r.pos)) @@ -588,7 +589,7 @@ proc cmdChangeTriggersRecompilation(old, new: TCommands): bool = return false of cmdNone, cmdDoc, cmdInterpret, cmdPretty, cmdGenDepend, cmdDump, cmdCheck, cmdParse, cmdScan, cmdIdeTools, cmdDef, - cmdRst2html, cmdRst2tex, cmdInteractive, cmdRun: + cmdRst2html, cmdRst2tex, cmdInteractive, cmdRun, cmdJsonScript: discard # else: trigger recompilation: result = true diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim index d61d817dd..fb50c6473 100644 --- a/compiler/rodwrite.nim +++ b/compiler/rodwrite.nim @@ -175,16 +175,17 @@ proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) = var oldLen = result.len result.add('<') if loc.k != low(loc.k): encodeVInt(ord(loc.k), result) - if loc.s != low(loc.s): + if loc.storage != low(loc.storage): add(result, '*') - encodeVInt(ord(loc.s), result) + encodeVInt(ord(loc.storage), result) if loc.flags != {}: add(result, '$') encodeVInt(cast[int32](loc.flags), result) - if loc.t != nil: + if loc.lode != nil: add(result, '^') - encodeVInt(cast[int32](loc.t.id), result) - pushType(w, loc.t) + encodeNode(w, unknownLineInfo(), loc.lode, result) + #encodeVInt(cast[int32](loc.t.id), result) + #pushType(w, loc.t) if loc.r != nil: add(result, '!') encodeStr($loc.r, result) @@ -579,7 +580,7 @@ proc process(c: PPassContext, n: PNode): PNode = for i in countup(0, sonsLen(n) - 1): discard process(c, n.sons[i]) #var s = n.sons[namePos].sym #addInterfaceSym(w, s) - of nkProcDef, nkIteratorDef, nkConverterDef, + of nkProcDef, nkFuncDef, nkIteratorDef, nkConverterDef, nkTemplateDef, nkMacroDef: let s = n.sons[namePos].sym if s == nil: internalError(n.info, "rodwrite.process") diff --git a/compiler/sem.nim b/compiler/sem.nim index cd0df0de0..ebfdafea7 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -449,7 +449,7 @@ include semtypes, semtempl, semgnrc, semstmts, semexprs proc addCodeForGenerics(c: PContext, n: PNode) = for i in countup(c.lastGenericIdx, c.generics.len - 1): var prc = c.generics[i].inst.sym - if prc.kind in {skProc, skMethod, skConverter} and prc.magic == mNone: + if prc.kind in {skProc, skFunc, skMethod, skConverter} and prc.magic == mNone: if prc.ast == nil or prc.ast.sons[bodyPos] == nil: internalError(prc.info, "no code for " & prc.name.s) else: @@ -495,13 +495,15 @@ proc isImportSystemStmt(n: PNode): bool = case n.kind of nkImportStmt: for x in n: - let f = checkModuleName(x, false) + if x.kind == nkIdent: + let f = checkModuleName(x, false) + if f == magicsys.systemModule.info.fileIndex: + return true + of nkImportExceptStmt, nkFromStmt: + if n[0].kind == nkIdent: + let f = checkModuleName(n[0], false) if f == magicsys.systemModule.info.fileIndex: return true - of nkImportExceptStmt, nkFromStmt: - let f = checkModuleName(n[0], false) - if f == magicsys.systemModule.info.fileIndex: - return true else: discard proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index f2144037c..dbb2a140b 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -185,7 +185,7 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = case t.kind of tyNone, tyEmpty, tyVoid: discard of tyPointer, tySet, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCString, - tyPtr, tyString, tyRef: + tyPtr, tyString, tyRef, tyOpt: defaultOp(c, t, body, x, y) of tyArray, tySequence: if tfHasAsgn in t.flags: @@ -227,9 +227,9 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = tyTypeDesc, tyGenericInvocation, tyForward: internalError(c.info, "assignment requested for type: " & typeToString(t)) of tyOrdinal, tyRange, tyInferred, - tyGenericInst, tyFieldAccessor, tyStatic, tyVar, tyAlias: + tyGenericInst, tyStatic, tyVar, tyAlias: liftBodyAux(c, lastSon(t), body, x, y) - of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("liftBodyAux") + of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("liftBodyAux") proc newProcType(info: TLineInfo; owner: PSym): PType = result = newType(tyProc, owner) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 5984e25e0..9492e63f4 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -468,7 +468,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = for i in countup(0, len(a)-1): var candidate = a.sons[i].sym if candidate.kind in {skProc, skMethod, skConverter, - skIterator}: + skFunc, skIterator}: # it suffices that the candidate has the proper number of generic # type parameters: if safeLen(candidate.ast.sons[genericParamsPos]) == n.len-1: diff --git a/compiler/semdata.nim b/compiler/semdata.nim index d422646a8..a3f0f715b 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -65,7 +65,7 @@ type efWantStmt, efAllowStmt, efDetermineType, efExplain, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo, - + TExprFlags* = set[TExprFlag] TTypeAttachedOp* = enum @@ -112,6 +112,7 @@ type semGenerateInstance*: proc (c: PContext, fn: PSym, pt: TIdTable, info: TLineInfo): PSym includedFiles*: IntSet # used to detect recursive include files + pureEnumFields*: TStrTable # pure enum fields that can be used unambiguously userPragmas*: TStrTable evalContext*: PEvalContext unknownIdents*: IntSet # ids of all unknown identifiers to prevent @@ -210,6 +211,7 @@ proc newContext*(graph: ModuleGraph; module: PSym; cache: IdentCache): PContext result.converters = @[] result.patterns = @[] result.includedFiles = initIntSet() + initStrTable(result.pureEnumFields) initStrTable(result.userPragmas) result.generics = @[] result.unknownIdents = initIntSet() @@ -371,7 +373,7 @@ proc makeRangeType*(c: PContext; first, last: BiggestInt; addSonSkipIntLit(result, intType) # basetype of range proc markIndirect*(c: PContext, s: PSym) {.inline.} = - if s.kind in {skProc, skConverter, skMethod, skIterator}: + if s.kind in {skProc, skFunc, skConverter, skMethod, skIterator}: incl(s.flags, sfAddrTaken) # XXX add to 'c' for global analysis diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 74b074f61..161847a4f 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -246,8 +246,7 @@ 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, tyFieldAccessor}) + var typ = skipTypes(n.sons[1].typ, abstractVarRange + {tyTypeDesc}) case typ.kind of tySequence, tyString, tyCString, tyOpenArray, tyVarargs: n.typ = getSysType(tyInt) @@ -255,7 +254,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 + {tyFieldAccessor}) + n.typ = n.sons[1].typ.skipTypes(abstractVar) of tyGenericParam: # prepare this for resolving in semtypinst: # we must use copyTree here in order to avoid creating a cycle @@ -279,7 +278,7 @@ proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): 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, tyFieldAccessor}) + let t1 = n[1].typ.skipTypes({tyTypeDesc}) if n[2].kind in {nkStrLit..nkTripleStrLit}: case n[2].strVal.normalize @@ -573,7 +572,7 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = optImplicitStatic notin gOptions: return if callee.magic notin ctfeWhitelist: return - if callee.kind notin {skProc, skConverter} or callee.isGenericRoutine: + if callee.kind notin {skProc, skFunc, skConverter} or callee.isGenericRoutine: return if n.typ != nil and typeAllowed(n.typ, skConst) != nil: return @@ -615,10 +614,10 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode, # for typeof support. # for ``type(countup(1,3))``, see ``tests/ttoseq``. result = semOverloadedCall(c, n, nOrig, - {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator}, flags) + {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate, skIterator}, flags) else: result = semOverloadedCall(c, n, nOrig, - {skProc, skMethod, skConverter, skMacro, skTemplate}, flags) + {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate}, flags) if result != nil: if result.sons[0].kind != nkSym: @@ -1086,9 +1085,6 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = if field != nil: n.typ = makeTypeDesc(c, field.typ) return n - #n.typ = newTypeWithSons(c, tyFieldAccessor, @[ty, field.typ]) - #n.typ.n = copyTree(n) - #return n else: tryReadingGenericParam(ty) return @@ -1229,7 +1225,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = else: nil if s != nil: case s.kind - of skProc, skMethod, skConverter, skIterator: + of skProc, skFunc, skMethod, skConverter, skIterator: # type parameters: partial generic specialization n.sons[0] = semSymGenericInstantiation(c, n.sons[0], s) result = explicitGenericInstantiation(c, n, s) @@ -1397,7 +1393,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = proc semReturn(c: PContext, n: PNode): PNode = result = n checkSonsLen(n, 1) - if c.p.owner.kind in {skConverter, skMethod, skProc, skMacro} or ( + if c.p.owner.kind in {skConverter, skMethod, skProc, skFunc, skMacro} or ( c.p.owner.kind == skIterator and c.p.owner.typ.callConv == ccClosure): if n.sons[0].kind != nkEmpty: # transform ``return expr`` to ``result = expr; return`` @@ -1620,9 +1616,10 @@ proc semExpandToAst(c: PContext, n: PNode): PNode = # Preserve the magic symbol in order to be handled in evals.nim internalAssert n.sons[0].sym.magic == mExpandToAst #n.typ = getSysSym("NimNode").typ # expandedSym.getReturnType - n.typ = if getCompilerProc("NimNode") != nil: sysTypeFromName"NimNode" - else: sysTypeFromName"PNimrodNode" - result = n + if n.kind == nkStmtList and n.len == 1: result = n[0] + else: result = n + result.typ = if getCompilerProc("NimNode") != nil: sysTypeFromName"NimNode" + else: sysTypeFromName"PNimrodNode" proc semExpandToAst(c: PContext, n: PNode, magicSym: PSym, flags: TExprFlags = {}): PNode = @@ -2114,12 +2111,14 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = if nfSem in n.flags: return case n.kind of nkIdent, nkAccQuoted: - let checks = if efNoEvaluateGeneric in flags: {checkUndeclared} - else: {checkUndeclared, checkModule, checkAmbiguity} + let checks = if efNoEvaluateGeneric in flags: + {checkUndeclared, checkPureEnumFields} + else: + {checkUndeclared, checkModule, checkAmbiguity, checkPureEnumFields} var s = qualifiedLookUp(c, n, checks) if c.matchedConcept == nil: semCaptureSym(s, c.p.owner) result = semSym(c, n, s, flags) - if s.kind in {skProc, skMethod, skConverter, skIterator}: + if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator}: #performProcvarCheck(c, n, s) result = symChoice(c, n, s, scClosed) if result.kind == nkSym: @@ -2214,7 +2213,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = errorUseQualifier(c, n.info, s) elif s.magic == mNone: result = semDirectOp(c, n, flags) else: result = semMagic(c, n, s, flags) - of skProc, skMethod, skConverter, skIterator: + of skProc, skFunc, skMethod, skConverter, skIterator: if s.magic == mNone: result = semDirectOp(c, n, flags) else: result = semMagic(c, n, s, flags) else: @@ -2328,6 +2327,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkPragma: pragma(c, c.p.owner, n, stmtPragmas) of nkIteratorDef: result = semIterator(c, n) of nkProcDef: result = semProc(c, n) + of nkFuncDef: result = semFunc(c, n) of nkMethodDef: result = semMethod(c, n) of nkConverterDef: result = semConverterDef(c, n) of nkMacroDef: result = semMacroDef(c, n) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 612df1ba3..089e66abd 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -486,6 +486,8 @@ proc getConstExpr(m: PSym, n: PNode): PNode = of mCpuEndian: result = newIntNodeT(ord(CPU[targetCPU].endian), n) of mHostOS: result = newStrNodeT(toLowerAscii(platform.OS[targetOS].name), n) of mHostCPU: result = newStrNodeT(platform.CPU[targetCPU].name.toLowerAscii, n) + of mBuildOS: result = newStrNodeT(toLowerAscii(platform.OS[platform.hostOS].name), n) + of mBuildCPU: result = newStrNodeT(platform.CPU[platform.hostCPU].name.toLowerAscii, n) of mAppType: result = getAppType(n) of mNaN: result = newFloatNodeT(NaN, n) of mInf: result = newFloatNodeT(Inf, n) @@ -498,7 +500,7 @@ proc getConstExpr(m: PSym, n: PNode): PNode = result = newStrNodeT(lookupSymbol(s.name), n) else: result = copyTree(s.ast) - of {skProc, skMethod}: + of {skProc, skFunc, skMethod}: result = n of skType: # XXX gensym'ed symbols can come here and cannot be resolved. This is @@ -522,7 +524,7 @@ proc getConstExpr(m: PSym, n: PNode): PNode = of nkCallKinds: if n.sons[0].kind != nkSym: return var s = n.sons[0].sym - if s.kind != skProc: return + if s.kind != skProc and s.kind != skFunc: return try: case s.magic of mNone: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 7e55b266a..3cdb68df6 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -61,7 +61,7 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, of skUnknown: # Introduced in this pass! Leave it as an identifier. result = n - of skProc, skMethod, skIterator, skConverter, skModule: + of skProc, skFunc, skMethod, skIterator, skConverter, skModule: result = symChoice(c, n, s, scOpen) of skTemplate: if macroToExpandSym(s): @@ -106,6 +106,10 @@ proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags, let ident = considerQuotedIdent(n) var s = searchInScopes(c, ident).skipAlias(n) if s == nil: + s = strTableGet(c.pureEnumFields, ident) + if s != nil and contains(c.ambiguousSymbols, s.id): + s = nil + if s == nil: if ident.id notin ctx.toMixin and withinMixin notin flags: errorUndeclaredIdentifier(c, n.info, ident.s) else: @@ -239,7 +243,7 @@ proc semGenericStmt(c: PContext, n: PNode, of skUnknown, skParam: # Leave it as an identifier. discard - of skProc, skMethod, skIterator, skConverter, skModule: + of skProc, skFunc, skMethod, skIterator, skConverter, skModule: result.sons[0] = sc # do not check of 's.magic==mRoof' here because it might be some # other '^' but after overload resolution the proper one: @@ -433,7 +437,7 @@ proc semGenericStmt(c: PContext, n: PNode, for j in countup(0, L-3): addTempDecl(c, getIdentNode(a.sons[j]), skParam) of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef, - nkIteratorDef, nkLambdaKinds: + nkFuncDef, nkIteratorDef, nkLambdaKinds: checkSonsLen(n, bodyPos + 1) if n.sons[namePos].kind != nkEmpty: addTempDecl(c, getIdentNode(n.sons[0]), skProc) diff --git a/compiler/seminst.nim b/compiler/seminst.nim index a28d322b1..6abb34e90 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -36,7 +36,7 @@ proc rawPushProcCon(c: PContext, owner: PSym) = c.p = x proc rawHandleSelf(c: PContext; owner: PSym) = - const callableSymbols = {skProc, skMethod, skConverter, skIterator, skMacro} + const callableSymbols = {skProc, skFunc, skMethod, skConverter, skIterator, skMacro} if c.selfName != nil and owner.kind in callableSymbols and owner.typ != nil: let params = owner.typ.n if params.len > 1: diff --git a/compiler/semparallel.nim b/compiler/semparallel.nim index 90c1a315a..2581f5728 100644 --- a/compiler/semparallel.nim +++ b/compiler/semparallel.nim @@ -387,7 +387,7 @@ proc analyse(c: var AnalysisCtx; n: PNode) = addFactNeg(c.guards, canon(n.sons[0])) dec c.inLoop of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, - nkMacroDef, nkTemplateDef, nkConstSection, nkPragma: + nkMacroDef, nkTemplateDef, nkConstSection, nkPragma, nkFuncDef: discard else: analyseSons(c, n) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index e24c5fd29..e1a3939fc 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -835,7 +835,7 @@ proc track(tracked: PEffects, n: PNode) = setLen(tracked.locked, oldLocked) tracked.currLockLevel = oldLockLevel of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, - nkMacroDef, nkTemplateDef, nkLambda, nkDo: + nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef: discard of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv: if n.len == 2: track(tracked, n.sons[1]) @@ -937,7 +937,7 @@ proc trackProc*(s: PSym, body: PNode) = track(t, body) if not isEmptyType(s.typ.sons[0]) and {tfNeedsInit, tfNotNil} * s.typ.sons[0].flags != {} and - s.kind in {skProc, skConverter, skMethod}: + s.kind in {skProc, skFunc, skConverter, skMethod}: var res = s.ast.sons[resultPos].sym # get result symbol if res.id notin t.init: message(body.info, warnProveInit, "result") @@ -979,10 +979,10 @@ proc trackProc*(s: PSym, body: PNode) = message(s.info, warnLockLevel, "declared lock level is $1, but real lock level is $2" % [$s.typ.lockLevel, $t.maxLockLevel]) - when useWriteTracking: trackWrites(s, body) + if s.kind == skFunc: trackWrites(s, body) proc trackTopLevelStmt*(module: PSym; n: PNode) = - if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, + if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef, nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}: return var effects = newNode(nkEffectList, n.info) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index dbdb543f5..a4dd8f354 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -437,8 +437,6 @@ proc isDiscardUnderscore(v: PSym): bool = proc semUsing(c: PContext; n: PNode): PNode = result = ast.emptyNode if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "using") - if not experimentalMode(c): - localError(n.info, "use the {.experimental.} pragma to enable 'using'") for i in countup(0, sonsLen(n)-1): var a = n.sons[i] if gCmd == cmdIdeTools: suggestStmt(c, a) @@ -493,6 +491,7 @@ proc fillPartialObject(c: PContext; n: PNode; typ: PType) = addSon(obj.n, newSymNode(field)) n.sons[0] = makeDeref x n.sons[1] = newSymNode(field) + n.typ = field.typ else: localError(n.info, "implicit object field construction " & "requires a .partial object, but got " & typeToString(obj)) @@ -1233,7 +1232,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode = s.typ = n.typ for i in 1..<params.len: if params[i].typ.kind in {tyTypeDesc, tyGenericParam, - tyFromExpr, tyFieldAccessor}+tyTypeClasses: + tyFromExpr}+tyTypeClasses: localError(params[i].info, "cannot infer type of parameter: " & params[i].sym.name.s) #params[i].sym.owner = s @@ -1603,6 +1602,9 @@ proc semIterator(c: PContext, n: PNode): PNode = proc semProc(c: PContext, n: PNode): PNode = result = semProcAux(c, n, skProc, procPragmas) +proc semFunc(c: PContext, n: PNode): PNode = + result = semProcAux(c, n, skFunc, procPragmas) + proc semMethod(c: PContext, n: PNode): PNode = if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "method") result = semProcAux(c, n, skMethod, methodPragmas) diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 8ad8a6288..be8567c9c 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -449,6 +449,8 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = a.sons[2] = semTemplBody(c, a.sons[2]) of nkProcDef, nkLambdaKinds: result = semRoutineInTemplBody(c, n, skProc) + of nkFuncDef: + result = semRoutineInTemplBody(c, n, skFunc) of nkMethodDef: result = semRoutineInTemplBody(c, n, skMethod) of nkIteratorDef: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index a7c9244cc..fbb5d0b6b 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -88,7 +88,9 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = if not isPure: strTableAdd(c.module.tab, e) addSon(result.n, newSymNode(e)) styleCheckDef(e) - if sfGenSym notin e.flags and not isPure: addDecl(c, e) + if sfGenSym notin e.flags: + if not isPure: addDecl(c, e) + else: importPureEnumField(c, e) if isPure and strTableIncl(symbols, e): wrongRedefinition(e.info, e.name.s) inc(counter) @@ -1391,6 +1393,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of mSet: result = semSet(c, n, prev) of mOrdinal: result = semOrdinal(c, n, prev) of mSeq: result = semContainer(c, n, tySequence, "seq", prev) + of mOpt: result = semContainer(c, n, tyOpt, "opt", prev) of mVarargs: result = semVarargs(c, n, prev) of mTypeDesc: result = makeTypeDesc(c, semTypeNode(c, n[1], nil)) of mExpr: @@ -1590,6 +1593,8 @@ proc processMagicType(c: PContext, m: PSym) = setMagicType(m, tySet, 0) of mSeq: setMagicType(m, tySequence, 0) + of mOpt: + setMagicType(m, tyOpt, 0) of mOrdinal: setMagicType(m, tyOrdinal, 0) rawAddSon(m.typ, newTypeS(tyNone, c)) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index b4a61deb7..a3953d87e 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -170,7 +170,7 @@ proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode = if isTypeParam(n[i]): needsFixing = true if needsFixing: n.sons[0] = newSymNode(n.sons[0].sym.owner) - return cl.c.semOverloadedCall(cl.c, n, n, {skProc}, {}) + return cl.c.semOverloadedCall(cl.c, n, n, {skProc, skFunc}, {}) for i in 0 .. <n.safeLen: n.sons[i] = reResolveCallsWithTypedescParams(cl, n[i]) diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim index 6eecf98d8..6043eb4a7 100644 --- a/compiler/sighashes.nim +++ b/compiler/sighashes.nim @@ -201,7 +201,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = of tyRef, tyPtr, tyGenericBody, tyVar: c.hashType t.lastSon, flags if tfVarIsPtr in t.flags: c &= ".varisptr" - of tyFromExpr, tyFieldAccessor: + of tyFromExpr: c.hashTree(t.n) of tyTuple: if t.n != nil and CoType notin flags: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 41596f05c..f2bc24399 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -936,7 +936,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, tfExplicit notin aOrig.flags aOrig = if useTypeLoweringRuleInTypeClass: - aOrig.skipTypes({tyTypeDesc, tyFieldAccessor}) + aOrig.skipTypes({tyTypeDesc}) else: aOrig @@ -1373,7 +1373,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, # XXX: This is very hacky. It should be moved back into liftTypeParam if x.kind in {tyGenericInst, tyArray} and c.calleeSym != nil and - c.calleeSym.kind == skProc: + c.calleeSym.kind in {skProc, skFunc}: let inst = prepareMetatypeForSigmatch(c.c, c.bindings, c.call.info, f) return typeRel(c, inst, a) @@ -1832,7 +1832,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, bothMetaCounter < 100: lastBindingsLength = m.bindings.counter inc(bothMetaCounter) - if arg.kind in {nkProcDef, nkIteratorDef} + nkLambdaKinds: + if arg.kind in {nkProcDef, nkFuncDef, nkIteratorDef} + nkLambdaKinds: result = c.semInferredLambda(c, m.bindings, arg) elif arg.kind != nkSym: return nil @@ -1865,7 +1865,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, else: result = implicitConv(nkHiddenStdConv, f, arg, m, c) of isInferred, isInferredConvertible: - if arg.kind in {nkProcDef, nkIteratorDef} + nkLambdaKinds: + if arg.kind in {nkProcDef, nkFuncDef, nkIteratorDef} + nkLambdaKinds: result = c.semInferredLambda(c, m.bindings, arg) elif arg.kind != nkSym: return nil @@ -1952,7 +1952,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType, z.calleeSym = m.calleeSym var best = -1 for i in countup(0, sonsLen(arg) - 1): - if arg.sons[i].sym.kind in {skProc, skMethod, skConverter, skIterator}: + if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter, skIterator}: copyCandidate(z, m) z.callee = arg.sons[i].typ if tfUnresolved in z.callee.flags: continue diff --git a/compiler/transf.nim b/compiler/transf.nim index 41959b018..f1ee49a54 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -121,7 +121,7 @@ proc transformSymAux(c: PTransf, n: PNode): PNode = if s.kind == skIterator: if c.tooEarly: return n else: return liftIterSym(n, getCurrOwner(c)) - elif s.kind in {skProc, skConverter, skMethod} and not c.tooEarly: + elif s.kind in {skProc, skFunc, skConverter, skMethod} and not c.tooEarly: # top level .closure procs are still somewhat supported for 'Nake': return makeClosure(s, nil, n.info) #elif n.sym.kind in {skVar, skLet} and n.sym.typ.callConv == ccClosure: diff --git a/compiler/types.nim b/compiler/types.nim index dc7cd52db..3dbf6fd18 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -12,9 +12,6 @@ import intsets, ast, astalgo, trees, msgs, strutils, platform, renderer -proc firstOrd*(t: PType): BiggestInt -proc lastOrd*(t: PType): BiggestInt -proc lengthOrd*(t: PType): BiggestInt type TPreferedDesc* = enum preferName, preferDesc, preferExported, preferModuleInfo, preferGenericArg @@ -48,8 +45,6 @@ type proc equalParams*(a, b: PNode): TParamsEquality # returns whether the parameter lists of the procs a, b are exactly the same -proc isOrdinalType*(t: PType): bool -proc enumHasHoles*(t: PType): bool const # TODO: Remove tyTypeDesc from each abstractX and (where necessary) @@ -70,17 +65,6 @@ const typedescPtrs* = abstractPtrs + {tyTypeDesc} typedescInst* = abstractInst + {tyTypeDesc} -proc containsObject*(t: PType): bool -proc containsGarbageCollectedRef*(typ: PType): bool -proc containsHiddenPointer*(typ: PType): bool -proc canFormAcycle*(typ: PType): bool -proc isCompatibleToCString*(a: PType): bool -proc getOrdValue*(n: PNode): BiggestInt -proc computeSize*(typ: PType): BiggestInt -proc getSize*(typ: PType): BiggestInt -proc isPureObject*(typ: PType): bool -proc invalidGenericInst*(f: PType): bool - # for debugging type TTypeFieldResult* = enum frNone, # type has no object type field @@ -92,16 +76,16 @@ proc analyseObjectWithTypeField*(t: PType): TTypeFieldResult # made or intializing of the type field suffices or if there is no type field # at all in this type. -proc invalidGenericInst(f: PType): bool = +proc invalidGenericInst*(f: PType): bool = result = f.kind == tyGenericInst and lastSon(f) == nil -proc isPureObject(typ: PType): bool = +proc isPureObject*(typ: PType): bool = var t = typ while t.kind == tyObject and t.sons[0] != nil: t = t.sons[0].skipTypes(skipPtrs) result = t.sym != nil and sfPure in t.sym.flags -proc getOrdValue(n: PNode): BiggestInt = +proc getOrdValue*(n: PNode): BiggestInt = case n.kind of nkCharLit..nkUInt64Lit: result = n.intVal of nkNilLit: result = 0 @@ -116,14 +100,6 @@ proc isIntLit*(t: PType): bool {.inline.} = proc isFloatLit*(t: PType): bool {.inline.} = result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit -proc isCompatibleToCString(a: PType): bool = - if a.kind == tyArray: - if (firstOrd(a.sons[0]) == 0) and - (skipTypes(a.sons[0], {tyRange, tyGenericInst, tyAlias}).kind in - {tyInt..tyInt64, tyUInt..tyUInt64}) and - (a.sons[1].kind == tyChar): - result = true - proc getProcHeader*(sym: PSym; prefer: TPreferedDesc = preferName): string = result = sym.owner.name.s & '.' & sym.name.s & '(' var n = sym.typ.n @@ -151,7 +127,7 @@ proc elemType*(t: PType): PType = else: result = t.lastSon assert(result != nil) -proc isOrdinalType(t: PType): bool = +proc isOrdinalType*(t: PType): bool = assert(t != nil) const # caution: uint, uint64 are no ordinal types! @@ -159,7 +135,7 @@ proc isOrdinalType(t: PType): bool = parentKinds = {tyRange, tyOrdinal, tyGenericInst, tyAlias, tyDistinct} t.kind in baseKinds or (t.kind in parentKinds and isOrdinalType(t.sons[0])) -proc enumHasHoles(t: PType): bool = +proc enumHasHoles*(t: PType): bool = var b = t while b.kind in {tyRange, tyGenericInst, tyAlias}: b = b.sons[0] result = b.kind == tyEnum and tfEnumHasHoles in b.flags @@ -252,7 +228,7 @@ proc searchTypeFor(t: PType, predicate: TTypePredicate): bool = proc isObjectPredicate(t: PType): bool = result = t.kind == tyObject -proc containsObject(t: PType): bool = +proc containsObject*(t: PType): bool = result = searchTypeFor(t, isObjectPredicate) proc isObjectWithTypeFieldPredicate(t: PType): bool = @@ -297,7 +273,7 @@ proc isGCRef(t: PType): bool = result = t.kind in GcTypeKinds or (t.kind == tyProc and t.callConv == ccClosure) -proc containsGarbageCollectedRef(typ: PType): bool = +proc containsGarbageCollectedRef*(typ: PType): bool = # returns true if typ contains a reference, sequence or string (all the # things that are garbage-collected) result = searchTypeFor(typ, isGCRef) @@ -312,7 +288,7 @@ proc containsTyRef*(typ: PType): bool = proc isHiddenPointer(t: PType): bool = result = t.kind in {tyString, tySequence} -proc containsHiddenPointer(typ: PType): bool = +proc containsHiddenPointer*(typ: PType): bool = # returns true if typ contains a string, table or sequence (all the things # that need to be copied deeply) result = searchTypeFor(typ, isHiddenPointer) @@ -355,7 +331,7 @@ proc canFormAcycleAux(marker: var IntSet, typ: PType, startId: int): bool = of tyProc: result = typ.callConv == ccClosure else: discard -proc canFormAcycle(typ: PType): bool = +proc canFormAcycle*(typ: PType): bool = var marker = initIntSet() result = canFormAcycleAux(marker, typ, typ.id) @@ -521,7 +497,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = of tyExpr: internalAssert t.len == 0 result = "untyped" - of tyFromExpr, tyFieldAccessor: + of tyFromExpr: result = renderTree(t.n) of tyArray: if t.sons[0].kind == tyRange: @@ -532,6 +508,8 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = typeToString(t.sons[1]) & ']' of tySequence: result = "seq[" & typeToString(t.sons[0]) & ']' + of tyOpt: + result = "opt[" & typeToString(t.sons[0]) & ']' of tyOrdinal: result = "ordinal[" & typeToString(t.sons[0]) & ']' of tySet: @@ -605,7 +583,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = result = typeToStr[t.kind] result.addTypeFlags(t) -proc firstOrd(t: PType): BiggestInt = +proc firstOrd*(t: PType): BiggestInt = case t.kind of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy: result = 0 @@ -630,7 +608,7 @@ proc firstOrd(t: PType): BiggestInt = else: assert(t.n.sons[0].kind == nkSym) result = t.n.sons[0].sym.position - of tyGenericInst, tyDistinct, tyTypeDesc, tyFieldAccessor, tyAlias: + of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias: result = firstOrd(lastSon(t)) of tyOrdinal: if t.len > 0: result = firstOrd(lastSon(t)) @@ -639,7 +617,7 @@ proc firstOrd(t: PType): BiggestInt = internalError("invalid kind for first(" & $t.kind & ')') result = 0 -proc lastOrd(t: PType): BiggestInt = +proc lastOrd*(t: PType): BiggestInt = case t.kind of tyBool: result = 1 of tyChar: result = 255 @@ -666,7 +644,7 @@ 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, tyTypeDesc, tyFieldAccessor, tyAlias: + of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias: result = lastOrd(lastSon(t)) of tyProxy: result = 0 of tyOrdinal: @@ -676,7 +654,7 @@ proc lastOrd(t: PType): BiggestInt = internalError("invalid kind for last(" & $t.kind & ')') result = 0 -proc lengthOrd(t: PType): BiggestInt = +proc lengthOrd*(t: PType): BiggestInt = case t.kind of tyInt64, tyInt32, tyInt: result = lastOrd(t) of tyDistinct: result = lengthOrd(t.sons[0]) @@ -689,6 +667,14 @@ proc lengthOrd(t: PType): BiggestInt = else: result = lastOrd(t) - firstOrd(t) + 1 +proc isCompatibleToCString*(a: PType): bool = + if a.kind == tyArray: + if (firstOrd(a.sons[0]) == 0) and + (skipTypes(a.sons[0], {tyRange, tyGenericInst, tyAlias}).kind in + {tyInt..tyInt64, tyUInt..tyUInt64}) and + (a.sons[1].kind == tyChar): + result = true + # -------------- type equality ----------------------------------------------- type @@ -989,7 +975,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = result = a.sym.position == b.sym.position of tyGenericInvocation, tyGenericBody, tySequence, tyOpenArray, tySet, tyRef, tyPtr, tyVar, - tyArray, tyProc, tyVarargs, tyOrdinal, tyTypeClasses, tyFieldAccessor: + tyArray, tyProc, tyVarargs, tyOrdinal, tyTypeClasses, tyOpt: cycleCheck() if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n result = sameChildrenAux(a, b, c) and sameFlags(a, b) @@ -1007,7 +993,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = cycleCheck() result = sameTypeAux(a.lastSon, b.lastSon, c) of tyNone: result = false - of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("sameFlags") + of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("sameFlags") proc sameBackendType*(x, y: PType): bool = var c = initSameTypeClosure() @@ -1089,7 +1075,7 @@ proc typeAllowedNode(marker: var IntSet, n: PNode, kind: TSymKind, of nkNone..nkNilLit: discard else: - if n.kind == nkRecCase and kind in {skProc, skConst}: + if n.kind == nkRecCase and kind in {skProc, skFunc, skConst}: return n[0].typ for i in countup(0, sonsLen(n) - 1): let it = n.sons[i] @@ -1107,7 +1093,7 @@ proc matchType*(a: PType, pattern: openArray[tuple[k:TTypeKind, i:int]], proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, flags: TTypeAllowedFlags = {}): PType = - assert(kind in {skVar, skLet, skConst, skProc, skParam, skResult}) + assert(kind in {skVar, skLet, skConst, skProc, skFunc, skParam, skResult}) # if we have already checked the type, return true, because we stop the # evaluation if something is wrong: result = nil @@ -1116,7 +1102,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, var t = skipTypes(typ, abstractInst-{tyTypeDesc}) case t.kind of tyVar: - if kind in {skProc, skConst}: return t + if kind in {skProc, skFunc, skConst}: return t var t2 = skipTypes(t.sons[0], abstractInst-{tyTypeDesc}) case t2.kind of tyVar: @@ -1144,7 +1130,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, of tyTypeClasses: if not (tfGenericTypeParam in t.flags or taField notin flags): result = t of tyGenericBody, tyGenericParam, tyGenericInvocation, - tyNone, tyForward, tyFromExpr, tyFieldAccessor: + tyNone, tyForward, tyFromExpr: result = t of tyNil: if kind != skConst: result = t @@ -1160,7 +1146,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, of tyOpenArray, tyVarargs: if kind != skParam: result = t else: result = typeAllowedAux(marker, t.sons[0], skVar, flags) - of tySequence: + of tySequence, tyOpt: if t.sons[0].kind != tyEmpty: result = typeAllowedAux(marker, t.sons[0], skVar, flags+{taHeap}) of tyArray: @@ -1176,7 +1162,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, result = typeAllowedAux(marker, t.sons[i], kind, flags) if result != nil: break of tyObject, tyTuple: - if kind in {skProc, skConst} and + if kind in {skProc, skFunc, skConst} and t.kind == tyObject and t.sons[0] != nil: return t let flags = flags+{taField} for i in countup(0, sonsLen(t) - 1): @@ -1188,7 +1174,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, # for now same as error node; we say it's a valid type as it should # prevent cascading errors: result = nil - of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("typeAllowedAux") + of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("typeAllowedAux") proc typeAllowed*(t: PType, kind: TSymKind): PType = # returns 'nil' on success and otherwise the part of the type that is @@ -1199,6 +1185,63 @@ proc typeAllowed*(t: PType, kind: TSymKind): PType = proc align(address, alignment: BiggestInt): BiggestInt = result = (address + (alignment - 1)) and not (alignment - 1) +type + OptKind* = enum ## What to map 'opt T' to internally. + oBool ## opt[T] requires an additional 'bool' field + oNil ## opt[T] has no overhead since 'nil' + ## is available + oEnum ## We can use some enum value that is not yet + ## used for opt[T] + oPtr ## opt[T] actually introduces a hidden pointer + ## in order for the type recursion to work + +proc optKind*(typ: PType): OptKind = + ## return true iff 'opt[T]' can be mapped to 'T' internally + ## because we have a 'nil' value available: + assert typ.kind == tyOpt + case typ.sons[0].skipTypes(abstractInst).kind + of tyRef, tyPtr, tyProc: + result = oNil + of tyArray, tyObject, tyTuple: + result = oPtr + of tyBool: result = oEnum + of tyEnum: + assert(typ.n.sons[0].kind == nkSym) + if typ.n.sons[0].sym.position != low(int): + result = oEnum + else: + result = oBool + else: + result = oBool + +proc optLowering*(typ: PType): PType = + case optKind(typ) + of oNil: result = typ.sons[0] + of oPtr: + result = newType(tyOptAsRef, typ.owner) + result.rawAddSon typ.sons[0] + of oBool: + result = newType(tyTuple, typ.owner) + result.rawAddSon newType(tyBool, typ.owner) + result.rawAddSon typ.sons[0] + of oEnum: + if lastOrd(typ) + 1 < `shl`(BiggestInt(1), 32): + result = newType(tyInt32, typ.owner) + else: + result = newType(tyInt64, typ.owner) + +proc optEnumValue*(typ: PType): BiggestInt = + assert typ.kind == tyOpt + assert optKind(typ) == oEnum + let elem = typ.sons[0].skipTypes(abstractInst).kind + if elem == tyBool: + result = 2 + else: + assert elem == tyEnum + assert typ.n.sons[0].sym.position != low(int) + result = typ.n.sons[0].sym.position - 1 + + const szNonConcreteType* = -3 szIllegalRecursion* = -2 @@ -1353,13 +1396,21 @@ proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt = of tyStatic: result = if typ.n != nil: computeSizeAux(typ.lastSon, a) else: szUnknownSize + of tyOpt: + case optKind(typ) + of oBool: result = computeSizeAux(lastSon(typ), a) + 1 + of oEnum: + if lastOrd(typ) + 1 < `shl`(BiggestInt(1), 32): result = 4 + else: result = 8 + of oNil: result = computeSizeAux(lastSon(typ), a) + of oPtr: result = ptrSize else: #internalError("computeSizeAux()") result = szUnknownSize typ.size = result typ.align = int16(a) -proc computeSize(typ: PType): BiggestInt = +proc computeSize*(typ: PType): BiggestInt = var a: BiggestInt = 1 result = computeSizeAux(typ, a) @@ -1368,7 +1419,7 @@ proc getReturnType*(s: PSym): PType = assert s.kind in skProcKinds result = s.typ.sons[0] -proc getSize(typ: PType): BiggestInt = +proc getSize*(typ: PType): BiggestInt = result = computeSize(typ) if result < 0: internalError("getSize: " & $typ.kind) diff --git a/compiler/vm.nim b/compiler/vm.nim index 8d4359db9..08605cad1 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -19,7 +19,7 @@ import ast except getstr import strutils, astalgo, msgs, vmdef, vmgen, nimsets, types, passes, parser, vmdeps, idents, trees, renderer, options, transf, parseutils, - vmmarshal + vmmarshal, gorgeimpl from semfold import leValueConv, ordinalValToString from evaltempl import evalTemplate @@ -66,7 +66,10 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = stackTraceAux(c, x.next, x.comesFrom, recursionLimit-1) var info = c.debug[pc] # we now use the same format as in system/except.nim - var s = toFilename(info) + var s = substr(toFilename(info), 0) + # this 'substr' prevents a strange corruption. XXX This needs to be + # investigated eventually but first attempts to fix it broke everything + # see the araq-wip-fixed-writebarrier branch. var line = toLinenumber(info) if line > 0: add(s, '(') @@ -979,7 +982,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = let node = regs[rb+i].regToNode node.info = c.debug[pc] macroCall.add(node) - let a = evalTemplate(macroCall, prc, genSymOwner) + var a = evalTemplate(macroCall, prc, genSymOwner) + if a.kind == nkStmtList and a.len == 1: a = a[0] a.recSetFlagIsRef ensureKind(rkNode) regs[ra].node = a diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index b9bbba551..29c1129b5 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -7,50 +7,7 @@ # distribution, for details about the copyright. # -import ast, types, msgs, os, osproc, streams, options, idents, securehash - -proc readOutput(p: Process): (string, int) = - result[0] = "" - var output = p.outputStream - while not output.atEnd: - result[0].add(output.readLine) - result[0].add("\n") - if result[0].len > 0: - result[0].setLen(result[0].len - "\n".len) - result[1] = p.waitForExit - -proc opGorge*(cmd, input, cache: string, info: TLineInfo): (string, int) = - let workingDir = parentDir(info.toFullPath) - if cache.len > 0:# and optForceFullMake notin gGlobalOptions: - let h = secureHash(cmd & "\t" & input & "\t" & cache) - let filename = options.toGeneratedFile("gorge_" & $h, "txt") - var f: File - if open(f, filename): - result = (f.readAll, 0) - f.close - return - var readSuccessful = false - try: - var p = startProcess(cmd, workingDir, - options={poEvalCommand, poStderrToStdout}) - if input.len != 0: - p.inputStream.write(input) - p.inputStream.close() - result = p.readOutput - readSuccessful = true - writeFile(filename, result[0]) - except IOError, OSError: - if not readSuccessful: result = ("", -1) - else: - try: - var p = startProcess(cmd, workingDir, - options={poEvalCommand, poStderrToStdout}) - if input.len != 0: - p.inputStream.write(input) - p.inputStream.close() - result = p.readOutput - except IOError, OSError: - result = ("", -1) +import ast, types, msgs, os, streams, options, idents proc opSlurp*(file: string, info: TLineInfo, module: PSym): string = try: @@ -250,6 +207,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result = mapTypeToBracket("ref", mRef, t, info) of tyVar: result = mapTypeToBracket("var", mVar, t, info) of tySequence: result = mapTypeToBracket("seq", mSeq, t, info) + of tyOpt: result = mapTypeToBracket("opt", mOpt, t, info) of tyProc: if inst: result = newNodeX(nkProcTy) @@ -304,7 +262,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; of tyNot: result = mapTypeToBracket("not", mNot, t, info) of tyAnything: result = atomicType("anything", mNone) of tyInferred: internalAssert false - of tyStatic, tyFromExpr, tyFieldAccessor: + of tyStatic, tyFromExpr: if inst: if t.n != nil: result = t.n.copyTree else: result = atomicType("void", mVoid) @@ -313,7 +271,7 @@ proc mapTypeToAstX(t: PType; info: TLineInfo; result.add atomicType("static", mNone) if t.n != nil: result.add t.n.copyTree - of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("mapTypeToAstX") + of tyUnused, tyOptAsRef, tyUnused1, tyUnused2: internalError("mapTypeToAstX") proc opMapTypeToAst*(t: PType; info: TLineInfo): PNode = result = mapTypeToAstX(t, info, false, true) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 3d291d8a2..ee9af7de2 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1289,7 +1289,7 @@ proc checkCanEval(c: PCtx; n: PNode) = if s.kind in {skVar, skTemp, skLet, skParam, skResult} and not s.isOwnedBy(c.prc.sym) and s.owner != c.module and c.mode != emRepl: cannotEval(n) - elif s.kind in {skProc, skConverter, skMethod, + elif s.kind in {skProc, skFunc, skConverter, skMethod, skIterator} and sfForward in s.flags: cannotEval(n) @@ -1537,6 +1537,8 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode = addSon(result, getNullValue(t.sons[i], info)) of tySet: result = newNodeIT(nkCurly, info, t) + of tyOpt: + result = newNodeIT(nkNilLit, info, t) else: globalError(info, "cannot create null element for: " & $t.kind) @@ -1712,7 +1714,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = case s.kind of skVar, skForVar, skTemp, skLet, skParam, skResult: genRdVar(c, n, dest, flags) - of skProc, skConverter, skMacro, skTemplate, skMethod, skIterator: + of skProc, skFunc, skConverter, skMacro, skTemplate, skMethod, skIterator: # 'skTemplate' is only allowed for 'getAst' support: if procIsCallback(c, s): discard elif sfImportc in s.flags: c.importcSym(n.info, s) diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index 773ab8ff5..1e89ea1e2 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -30,7 +30,7 @@ type wMacro, wMethod, wMixin, wMod, wNil, wNot, wNotin, wObject, wOf, wOr, wOut, wProc, wPtr, wRaise, wRef, wReturn, wShl, wShr, wStatic, wTemplate, wTry, wTuple, wType, wUsing, wVar, - wWhen, wWhile, wWith, wWithout, wXor, wYield, + wWhen, wWhile, wXor, wYield, wColon, wColonColon, wEquals, wDot, wDotDot, wStar, wMinus, @@ -116,7 +116,7 @@ const "out", "proc", "ptr", "raise", "ref", "return", "shl", "shr", "static", "template", "try", "tuple", "type", "using", "var", - "when", "while", "with", "without", "xor", + "when", "while", "xor", "yield", ":", "::", "=", ".", "..", diff --git a/compiler/writetracking.nim b/compiler/writetracking.nim index 443e8ddf1..fe71e5b31 100644 --- a/compiler/writetracking.nim +++ b/compiler/writetracking.nim @@ -248,6 +248,8 @@ proc markWriteOrEscape(w: var W) = for p in a.dest: if p.kind == skParam and p.owner == w.owner: incl(p.flags, sfWrittenTo) + if w.owner.kind == skFunc and p.typ.kind != tyVar: + localError(a.info, "write access to non-var parameter: " & p.name.s) if {rootIsResultOrParam, rootIsHeapAccess, markAsEscaping}*a.destInfo != {}: var destIsParam = false diff --git a/doc/keywords.txt b/doc/keywords.txt index 405bf1981..f8b444924 100644 --- a/doc/keywords.txt +++ b/doc/keywords.txt @@ -16,6 +16,6 @@ shl shr static template try tuple type using var -when while with without +when while xor yield diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index 87fcb7828..cceea33c0 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -633,6 +633,9 @@ the ``vtptr`` magic produced types bound to ``ptr`` types. Symbol lookup in generics ------------------------- +Open and Closed symbols +~~~~~~~~~~~~~~~~~~~~~~~ + The symbol binding rules in generics are slightly subtle: There are "open" and "closed" symbols. A "closed" symbol cannot be re-bound in the instantiation context, an "open" symbol can. Per default overloaded symbols are open @@ -658,6 +661,9 @@ the ``Index`` type is defined *after* the ``==`` for tuples; yet the example compiles as the instantiation takes the currently defined symbols into account too. +Mixin statement +--------------- + A symbol can be forced to be open by a `mixin`:idx: declaration: .. code-block:: nim diff --git a/doc/manual/stmts.txt b/doc/manual/stmts.txt index 5668b8cc2..3a86a9730 100644 --- a/doc/manual/stmts.txt +++ b/doc/manual/stmts.txt @@ -547,9 +547,6 @@ Instead of: Using statement --------------- -**Warning**: The ``using`` statement is experimental and has to be -explicitly enabled with the `experimental`:idx: pragma or command line option! - The using statement provides syntactic convenience in modules where the same parameter names and types are used over and over. Instead of: @@ -563,7 +560,6 @@ name ``c`` should default to type ``Context``, ``n`` should default to ``Node`` etc.: .. code-block:: nim - {.experimental.} using c: Context n: Node diff --git a/doc/manual/types.txt b/doc/manual/types.txt index 927cda9e1..5d2ad01d7 100644 --- a/doc/manual/types.txt +++ b/doc/manual/types.txt @@ -550,8 +550,7 @@ order. The *names* of the fields also have to be identical. The assignment operator for tuples copies each component. The default assignment operator for objects copies each component. Overloading -of the assignment operator for objects is not possible, but this will change -in future versions of the compiler. +of the assignment operator is described in `type-bound-operations-operator`_. .. code-block:: nim diff --git a/doc/tut1.rst b/doc/tut1.rst index 89893a39a..be5cd8c11 100644 --- a/doc/tut1.rst +++ b/doc/tut1.rst @@ -185,9 +185,8 @@ to a storage location: var x = "abc" # introduces a new variable `x` and assigns a value to it x = "xyz" # assigns a new value to `x` -``=`` is the *assignment operator*. The assignment operator cannot be -overloaded, overwritten or forbidden, but this might change in a future version -of Nim. You can declare multiple variables with a single assignment +``=`` is the *assignment operator*. The assignment operator can be +overloaded. You can declare multiple variables with a single assignment statement and all the variables will have the same value: .. code-block:: diff --git a/koch.nim b/koch.nim index 6ae45fcb7..6b267ce46 100644 --- a/koch.nim +++ b/koch.nim @@ -262,6 +262,9 @@ proc buildTools(latest: bool) = let nimgrepExe = "bin/nimgrep".exe nimexec "c -o:" & nimgrepExe & " tools/nimgrep.nim" when defined(windows): buildVccTool() + + nimexec "c -o:" & ("bin/nimresolve".exe) & " tools/nimresolve.nim" + buildNimble(latest) proc nsis(args: string) = diff --git a/lib/core/macros.nim b/lib/core/macros.nim index e88002c7b..8c70d2b47 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -75,7 +75,8 @@ type nnkClosure, nnkGotoState, nnkState, - nnkBreakState + nnkBreakState, + nnkFuncDef NimNodeKinds* = set[NimNodeKind] NimTypeKind* = enum # some types are no longer used, see ast.nim @@ -96,14 +97,14 @@ type ntyError, ntyBuiltinTypeClass, ntyUserTypeClass, ntyUserTypeClassInst, ntyCompositeTypeClass, ntyInferred, ntyAnd, ntyOr, ntyNot, - ntyAnything, ntyStatic, ntyFromExpr, ntyFieldAccessor, ntyVoid + ntyAnything, ntyStatic, ntyFromExpr, ntyOpt, ntyVoid TNimTypeKinds* {.deprecated.} = set[NimTypeKind] NimSymKind* = enum nskUnknown, nskConditional, nskDynLib, nskParam, nskGenericParam, nskTemp, nskModule, nskType, nskVar, nskLet, nskConst, nskResult, - nskProc, nskMethod, nskIterator, + nskProc, nskFunc, nskMethod, nskIterator, nskConverter, nskMacro, nskTemplate, nskField, nskEnumField, nskForVar, nskLabel, nskStub @@ -843,7 +844,8 @@ proc last*(node: NimNode): NimNode {.compileTime.} = node[<node.len] const - RoutineNodes* = {nnkProcDef, nnkMethodDef, nnkDo, nnkLambda, nnkIteratorDef, nnkTemplateDef, nnkConverterDef} + RoutineNodes* = {nnkProcDef, nnkFuncDef, nnkMethodDef, nnkDo, nnkLambda, + nnkIteratorDef, nnkTemplateDef, nnkConverterDef} AtomicNodes* = {nnkNone..nnkNilLit} CallNodes* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, nnkCallStrLit, nnkHiddenCallConv} diff --git a/lib/core/typeinfo.nim b/lib/core/typeinfo.nim index 72b139202..16580b318 100644 --- a/lib/core/typeinfo.nim +++ b/lib/core/typeinfo.nim @@ -54,6 +54,7 @@ type akUInt16 = 42, ## any represents an unsigned in16 akUInt32 = 43, ## any represents an unsigned int32 akUInt64 = 44, ## any represents an unsigned int64 +# akOpt = 44+18 ## the builtin 'opt' type. Any* = object ## can represent any nim value; NOTE: the wrapped ## value can be modified with its wrapper! This means diff --git a/lib/genode_cpp/threads.h b/lib/genode_cpp/threads.h index 043f808f1..a7cb2f17b 100644 --- a/lib/genode_cpp/threads.h +++ b/lib/genode_cpp/threads.h @@ -31,8 +31,12 @@ struct Nim::SysThread void entry() override { (_func)(_arg); } - Thread(Genode::Env &env, Genode::size_t stack_size, Entry func, void *arg) - : Genode::Thread(env, "nim-thread", stack_size), _func(func), _arg(arg) + Thread(Genode::Env &env, Genode::size_t stack_size, Entry func, void *arg, int affinity) + : Genode::Thread(env, "nim-thread", stack_size, + env.cpu().affinity_space().location_of_index(affinity), + Genode::Cpu_session::Weight(Genode::Cpu_session::Weight::DEFAULT_WEIGHT-1), + env.cpu()), + _func(func), _arg(arg) { Genode::Thread::start(); } @@ -40,8 +44,8 @@ struct Nim::SysThread Genode::Constructible<Thread> _thread; - void initThread(Genode::Env *env, Genode::size_t stack_size, Entry func, void *arg) { - _thread.construct(*env, stack_size, func, arg); } + void initThread(Genode::Env *env, Genode::size_t stack_size, Entry func, void *arg, int aff) { + _thread.construct(*env, stack_size, func, arg, aff); } void joinThread() { _thread->join(); } diff --git a/lib/impure/db_sqlite.nim b/lib/impure/db_sqlite.nim index 1633d48f7..53dafdda7 100644 --- a/lib/impure/db_sqlite.nim +++ b/lib/impure/db_sqlite.nim @@ -129,7 +129,8 @@ proc tryExec*(db: DbConn, query: SqlQuery, var q = dbFormat(query, args) var stmt: sqlite3.Pstmt if prepare_v2(db, q, q.len.cint, stmt, nil) == SQLITE_OK: - if step(stmt) == SQLITE_DONE: + let x = step(stmt) + if x in {SQLITE_DONE, SQLITE_ROW}: result = finalize(stmt) == SQLITE_OK proc exec*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]) {. diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim index 0d359d9e6..13eb1e759 100644 --- a/lib/js/jsffi.nim +++ b/lib/js/jsffi.nim @@ -300,7 +300,7 @@ macro `.()`*[K: string | cstring, V: proc](obj: JsAssoc[K, V], result = quote do: (`dotOp`(`obj`, `field`))() for elem in args: - result[0].add elem + result.add elem # Iterators: @@ -471,7 +471,7 @@ macro bindMethod*(procedure: typed): auto = # construct the `this` parameter: thisQuote = quote do: var `this` {. nodecl, importc .} : `thisType` - call = newNimNode(nnkCall).add(rawProc[0], thisQuote[0][0][0][0]) + call = newNimNode(nnkCall).add(rawProc[0], thisQuote[0][0][0]) # construct the procedure call inside the method if args.len > 2: for idx in 2..args.len-1: @@ -483,6 +483,6 @@ macro bindMethod*(procedure: typed): auto = params, rawProc[4], rawProc[5], - newTree(nnkStmtList, thisQuote[0], call) + newTree(nnkStmtList, thisQuote, call) ) result = body diff --git a/lib/nimbase.h b/lib/nimbase.h index 70391024f..34a2e43e7 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -373,11 +373,13 @@ static N_INLINE(NI32, float32ToInt32)(float x) { #define float64ToInt64(x) ((NI64) (x)) +#define NIM_STRLIT_FLAG ((NU)(1) << ((NIM_INTBITS) - 2)) /* This has to be the same as system.strlitFlag! */ + #define STRING_LITERAL(name, str, length) \ - static const struct { \ - TGenericSeq Sup; \ - NIM_CHAR data[(length) + 1]; \ - } name = {{length, length}, str} + static const struct { \ + TGenericSeq Sup; \ + NIM_CHAR data[(length) + 1]; \ + } name = {{length, (NI) ((NU)length | NIM_STRLIT_FLAG)}, str} typedef struct TStringDesc* string; diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index 06b90768c..1d396d9e0 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -58,8 +58,8 @@ const "interface", "is", "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref", "return", "shl", "shr", "static", - "template", "try", "tuple", "type", "using", "var", "when", "while", "with", - "without", "xor", "yield"] + "template", "try", "tuple", "type", "using", "var", "when", "while", + "xor", "yield"] proc getSourceLanguage*(name: string): SourceLanguage = for i in countup(succ(low(SourceLanguage)), high(SourceLanguage)): diff --git a/lib/pure/basic2d.nim b/lib/pure/basic2d.nim deleted file mode 100644 index 31b3814d6..000000000 --- a/lib/pure/basic2d.nim +++ /dev/null @@ -1,855 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2013 Robert Persson -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -import math -import strutils - - -## Basic 2d support with vectors, points, matrices and some basic utilities. -## Vectors are implemented as direction vectors, ie. when transformed with a matrix -## the translation part of matrix is ignored. -## Operators `+` , `-` , `*` , `/` , `+=` , `-=` , `*=` and `/=` are implemented for vectors and scalars. -## -## Quick start example: -## -## .. code-block:: nim -## -## # Create a matrix which first rotates, then scales and at last translates -## -## var m:Matrix2d=rotate(DEG90) & scale(2.0) & move(100.0,200.0) -## -## # Create a 2d point at (100,0) and a vector (5,2) -## -## var pt:Point2d=point2d(100.0,0.0) -## -## var vec:Vector2d=vector2d(5.0,2.0) -## -## -## pt &= m # transforms pt in place -## -## var pt2:Point2d=pt & m #concatenates pt with m and returns a new point -## -## var vec2:Vector2d=vec & m #concatenates vec with m and returns a new vector - - -const - DEG360* = PI * 2.0 - ## 360 degrees in radians. - DEG270* = PI * 1.5 - ## 270 degrees in radians. - DEG180* = PI - ## 180 degrees in radians. - DEG90* = PI / 2.0 - ## 90 degrees in radians. - DEG60* = PI / 3.0 - ## 60 degrees in radians. - DEG45* = PI / 4.0 - ## 45 degrees in radians. - DEG30* = PI / 6.0 - ## 30 degrees in radians. - DEG15* = PI / 12.0 - ## 15 degrees in radians. - RAD2DEGCONST = 180.0 / PI - ## used internally by DegToRad and RadToDeg - -type - Matrix2d* = object - ## Implements a row major 2d matrix, which means - ## transformations are applied the order they are concatenated. - ## The rightmost column of the 3x3 matrix is left out since normally - ## not used for geometric transformations in 2d. - ax*,ay*,bx*,by*,tx*,ty*:float - Point2d* = object - ## Implements a non-homogeneous 2d point stored as - ## an `x` coordinate and an `y` coordinate. - x*,y*:float - Vector2d* = object - ## Implements a 2d **direction vector** stored as - ## an `x` coordinate and an `y` coordinate. Direction vector means, - ## that when transforming a vector with a matrix, the translational - ## part of the matrix is ignored. - x*,y*:float -{.deprecated: [TMatrix2d: Matrix2d, TPoint2d: Point2d, TVector2d: Vector2d].} - - -# Some forward declarations... -proc matrix2d*(ax,ay,bx,by,tx,ty:float):Matrix2d {.noInit.} - ## Creates a new matrix. - ## `ax`,`ay` is the local x axis - ## `bx`,`by` is the local y axis - ## `tx`,`ty` is the translation -proc vector2d*(x,y:float):Vector2d {.noInit,inline.} - ## Returns a new vector (`x`,`y`) -proc point2d*(x,y:float):Point2d {.noInit,inline.} - ## Returns a new point (`x`,`y`) - - - -let - IDMATRIX*:Matrix2d=matrix2d(1.0,0.0,0.0,1.0,0.0,0.0) - ## Quick access to an identity matrix - ORIGO*:Point2d=point2d(0.0,0.0) - ## Quick access to point (0,0) - XAXIS*:Vector2d=vector2d(1.0,0.0) - ## Quick access to an 2d x-axis unit vector - YAXIS*:Vector2d=vector2d(0.0,1.0) - ## Quick access to an 2d y-axis unit vector - - -# *************************************** -# Private utils -# *************************************** - -proc rtos(val:float):string= - return formatFloat(val,ffDefault,0) - -proc safeArccos(v:float):float= - ## assumes v is in range 0.0-1.0, but clamps - ## the value to avoid out of domain errors - ## due to rounding issues - return arccos(clamp(v,-1.0,1.0)) - - -template makeBinOpVector(s) = - ## implements binary operators ``+``, ``-``, ``*`` and ``/`` for vectors - proc s*(a,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y)) - proc s*(a:Vector2d,b:float):Vector2d {.inline,noInit.} = vector2d(s(a.x,b),s(a.y,b)) - proc s*(a:float,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a,b.x),s(a,b.y)) - -template makeBinOpAssignVector(s)= - ## implements inplace binary operators ``+=``, ``-=``, ``/=`` and ``*=`` for vectors - proc s*(a:var Vector2d,b:Vector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y) - proc s*(a:var Vector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b) - - -# *************************************** -# Matrix2d implementation -# *************************************** - -proc setElements*(t:var Matrix2d,ax,ay,bx,by,tx,ty:float) {.inline.}= - ## Sets arbitrary elements in an existing matrix. - t.ax=ax - t.ay=ay - t.bx=bx - t.by=by - t.tx=tx - t.ty=ty - -proc matrix2d*(ax,ay,bx,by,tx,ty:float):Matrix2d = - result.setElements(ax,ay,bx,by,tx,ty) - -proc `&`*(a,b:Matrix2d):Matrix2d {.noInit.} = #concatenate matrices - ## Concatenates matrices returning a new matrix. - - # | a.AX a.AY 0 | | b.AX b.AY 0 | - # | a.BX a.BY 0 | * | b.BX b.BY 0 | - # | a.TX a.TY 1 | | b.TX b.TY 1 | - result.setElements( - a.ax * b.ax + a.ay * b.bx, - a.ax * b.ay + a.ay * b.by, - a.bx * b.ax + a.by * b.bx, - a.bx * b.ay + a.by * b.by, - a.tx * b.ax + a.ty * b.bx + b.tx, - a.tx * b.ay + a.ty * b.by + b.ty) - - -proc scale*(s:float):Matrix2d {.noInit.} = - ## Returns a new scale matrix. - result.setElements(s,0,0,s,0,0) - -proc scale*(s:float,org:Point2d):Matrix2d {.noInit.} = - ## Returns a new scale matrix using, `org` as scale origin. - result.setElements(s,0,0,s,org.x-s*org.x,org.y-s*org.y) - -proc stretch*(sx,sy:float):Matrix2d {.noInit.} = - ## Returns new a stretch matrix, which is a - ## scale matrix with non uniform scale in x and y. - result.setElements(sx,0,0,sy,0,0) - -proc stretch*(sx,sy:float,org:Point2d):Matrix2d {.noInit.} = - ## Returns a new stretch matrix, which is a - ## scale matrix with non uniform scale in x and y. - ## `org` is used as stretch origin. - result.setElements(sx,0,0,sy,org.x-sx*org.x,org.y-sy*org.y) - -proc move*(dx,dy:float):Matrix2d {.noInit.} = - ## Returns a new translation matrix. - result.setElements(1,0,0,1,dx,dy) - -proc move*(v:Vector2d):Matrix2d {.noInit.} = - ## Returns a new translation matrix from a vector. - result.setElements(1,0,0,1,v.x,v.y) - -proc rotate*(rad:float):Matrix2d {.noInit.} = - ## Returns a new rotation matrix, which - ## represents a rotation by `rad` radians - let - s=sin(rad) - c=cos(rad) - result.setElements(c,s,-s,c,0,0) - -proc rotate*(rad:float,org:Point2d):Matrix2d {.noInit.} = - ## Returns a new rotation matrix, which - ## represents a rotation by `rad` radians around - ## the origin `org` - let - s=sin(rad) - c=cos(rad) - result.setElements(c,s,-s,c,org.x+s*org.y-c*org.x,org.y-c*org.y-s*org.x) - -proc mirror*(v:Vector2d):Matrix2d {.noInit.} = - ## Returns a new mirror matrix, mirroring - ## around the line that passes through origo and - ## has the direction of `v` - let - sqx=v.x*v.x - sqy=v.y*v.y - nd=1.0/(sqx+sqy) #used to normalize invector - xy2=v.x*v.y*2.0*nd - sqd=nd*(sqx-sqy) - - if nd==Inf or nd==NegInf: - return IDMATRIX #mirroring around a zero vector is arbitrary=>just use identity - - result.setElements( - sqd,xy2, - xy2,-sqd, - 0.0,0.0) - -proc mirror*(org:Point2d,v:Vector2d):Matrix2d {.noInit.} = - ## Returns a new mirror matrix, mirroring - ## around the line that passes through `org` and - ## has the direction of `v` - let - sqx=v.x*v.x - sqy=v.y*v.y - nd=1.0/(sqx+sqy) #used to normalize invector - xy2=v.x*v.y*2.0*nd - sqd=nd*(sqx-sqy) - - if nd==Inf or nd==NegInf: - return IDMATRIX #mirroring around a zero vector is arbitrary=>just use identity - - result.setElements( - sqd,xy2, - xy2,-sqd, - org.x-org.y*xy2-org.x*sqd,org.y-org.x*xy2+org.y*sqd) - - - -proc skew*(xskew,yskew:float):Matrix2d {.noInit.} = - ## Returns a new skew matrix, which has its - ## x axis rotated `xskew` radians from the local x axis, and - ## y axis rotated `yskew` radians from the local y axis - result.setElements(cos(yskew),sin(yskew),-sin(xskew),cos(xskew),0,0) - - -proc `$`* (t:Matrix2d):string {.noInit.} = - ## Returns a string representation of the matrix - return rtos(t.ax) & "," & rtos(t.ay) & - "," & rtos(t.bx) & "," & rtos(t.by) & - "," & rtos(t.tx) & "," & rtos(t.ty) - -proc isUniform*(t:Matrix2d,tol=1.0e-6):bool= - ## Checks if the transform is uniform, that is - ## perpendicular axes of equal length, which means (for example) - ## it cannot transform a circle into an ellipse. - ## `tol` is used as tolerance for both equal length comparison - ## and perp. comparison. - - #dot product=0 means perpendicular coord. system: - if abs(t.ax*t.bx+t.ay*t.by)<=tol: - #subtract squared lengths of axes to check if uniform scaling: - if abs((t.ax*t.ax+t.ay*t.ay)-(t.bx*t.bx+t.by*t.by))<=tol: - return true - return false - -proc determinant*(t:Matrix2d):float= - ## Computes the determinant of the matrix. - - #NOTE: equivalent with perp.dot product for two 2d vectors - return t.ax*t.by-t.bx*t.ay - -proc isMirroring* (m:Matrix2d):bool= - ## Checks if the `m` is a mirroring matrix, - ## which means it will reverse direction of a curve transformed with it - return m.determinant<0.0 - -proc inverse*(m:Matrix2d):Matrix2d {.noInit.} = - ## Returns a new matrix, which is the inverse of the matrix - ## If the matrix is not invertible (determinant=0), an EDivByZero - ## will be raised. - let d=m.determinant - if d==0.0: - raise newException(DivByZeroError,"Cannot invert a zero determinant matrix") - - result.setElements( - m.by/d,-m.ay/d, - -m.bx/d,m.ax/d, - (m.bx*m.ty-m.by*m.tx)/d, - (m.ay*m.tx-m.ax*m.ty)/d) - -proc equals*(m1:Matrix2d,m2:Matrix2d,tol=1.0e-6):bool= - ## Checks if all elements of `m1`and `m2` is equal within - ## a given tolerance `tol`. - return - abs(m1.ax-m2.ax)<=tol and - abs(m1.ay-m2.ay)<=tol and - abs(m1.bx-m2.bx)<=tol and - abs(m1.by-m2.by)<=tol and - abs(m1.tx-m2.tx)<=tol and - abs(m1.ty-m2.ty)<=tol - -proc `=~`*(m1,m2:Matrix2d):bool= - ## Checks if `m1`and `m2` is approximately equal, using a - ## tolerance of 1e-6. - equals(m1,m2) - -proc isIdentity*(m:Matrix2d,tol=1.0e-6):bool= - ## Checks is a matrix is approximately an identity matrix, - ## using `tol` as tolerance for each element. - return equals(m,IDMATRIX,tol) - -proc apply*(m:Matrix2d,x,y:var float,translate=false)= - ## Applies transformation `m` onto `x`,`y`, optionally - ## using the translation part of the matrix. - if translate: # positional style transform - let newx=x*m.ax+y*m.bx+m.tx - y=x*m.ay+y*m.by+m.ty - x=newx - else: # delta style transform - let newx=x*m.ax+y*m.bx - y=x*m.ay+y*m.by - x=newx - - - -# *************************************** -# Vector2d implementation -# *************************************** -proc vector2d*(x,y:float):Vector2d = #forward decl. - result.x=x - result.y=y - -proc polarVector2d*(ang:float,len:float):Vector2d {.noInit.} = - ## Returns a new vector with angle `ang` and magnitude `len` - result.x=cos(ang)*len - result.y=sin(ang)*len - -proc slopeVector2d*(slope:float,len:float):Vector2d {.noInit.} = - ## Returns a new vector having slope (dy/dx) given by - ## `slope`, and a magnitude of `len` - let ang=arctan(slope) - result.x=cos(ang)*len - result.y=sin(ang)*len - -proc len*(v:Vector2d):float {.inline.}= - ## Returns the length of the vector. - sqrt(v.x*v.x+v.y*v.y) - -proc `len=`*(v:var Vector2d,newlen:float) {.noInit.} = - ## Sets the length of the vector, keeping its angle. - let fac=newlen/v.len - - if newlen==0.0: - v.x=0.0 - v.y=0.0 - return - - if fac==Inf or fac==NegInf: - #to short for float accuracy - #do as good as possible: - v.x=newlen - v.y=0.0 - else: - v.x*=fac - v.y*=fac - -proc sqrLen*(v:Vector2d):float {.inline.}= - ## Computes the squared length of the vector, which is - ## faster than computing the absolute length. - v.x*v.x+v.y*v.y - -proc angle*(v:Vector2d):float= - ## Returns the angle of the vector. - ## (The counter clockwise plane angle between posetive x axis and `v`) - result=arctan2(v.y,v.x) - if result<0.0: result+=DEG360 - -proc `$` *(v:Vector2d):string= - ## String representation of `v` - result=rtos(v.x) - result.add(",") - result.add(rtos(v.y)) - - -proc `&` *(v:Vector2d,m:Matrix2d):Vector2d {.noInit.} = - ## Concatenate vector `v` with a transformation matrix. - ## Transforming a vector ignores the translational part - ## of the matrix. - - # | AX AY 0 | - # | X Y 1 | * | BX BY 0 | - # | 0 0 1 | - result.x=v.x*m.ax+v.y*m.bx - result.y=v.x*m.ay+v.y*m.by - - -proc `&=`*(v:var Vector2d,m:Matrix2d) {.inline.}= - ## Applies transformation `m` onto `v` in place. - ## Transforming a vector ignores the translational part - ## of the matrix. - - # | AX AY 0 | - # | X Y 1 | * | BX BY 0 | - # | 0 0 1 | - let newx=v.x*m.ax+v.y*m.bx - v.y=v.x*m.ay+v.y*m.by - v.x=newx - - -proc tryNormalize*(v:var Vector2d):bool= - ## Modifies `v` to have a length of 1.0, keeping its angle. - ## If `v` has zero length (and thus no angle), it is left unmodified and - ## false is returned, otherwise true is returned. - - let mag=v.len - - if mag==0.0: - return false - - v.x/=mag - v.y/=mag - return true - - -proc normalize*(v:var Vector2d) {.inline.}= - ## Modifies `v` to have a length of 1.0, keeping its angle. - ## If `v` has zero length, an EDivByZero will be raised. - if not tryNormalize(v): - raise newException(DivByZeroError,"Cannot normalize zero length vector") - -proc transformNorm*(v:var Vector2d,t:Matrix2d)= - ## Applies a normal direction transformation `t` onto `v` in place. - ## The resulting vector is *not* normalized. Transforming a vector ignores the - ## translational part of the matrix. If the matrix is not invertible - ## (determinant=0), an EDivByZero will be raised. - - # transforming a normal is done by transforming - # by the transpose of the inverse of the original matrix - # this can be heavily optimized by precompute and inline - # | | AX AY 0 | ^-1| ^T - # | X Y 1 | * | | BX BY 0 | | - # | | 0 0 1 | | - let d=t.determinant - if(d==0.0): - raise newException(DivByZeroError,"Matrix is not invertible") - let newx = (t.by*v.x-t.ay*v.y)/d - v.y = (t.ax*v.y-t.bx*v.x)/d - v.x = newx - -proc transformInv*(v:var Vector2d,t:Matrix2d)= - ## Applies inverse of a transformation `t` to `v` in place. - ## This is faster than creating an inverse matrix and apply() it. - ## Transforming a vector ignores the translational part - ## of the matrix. If the matrix is not invertible (determinant=0), an EDivByZero - ## will be raised. - let d=t.determinant - - if(d==0.0): - raise newException(DivByZeroError,"Matrix is not invertible") - - let newx=(t.by*v.x-t.bx*v.y)/d - v.y = (t.ax*v.y-t.ay*v.x)/d - v.x = newx - -proc transformNormInv*(v:var Vector2d,t:Matrix2d)= - ## Applies an inverse normal direction transformation `t` onto `v` in place. - ## This is faster than creating an inverse - ## matrix and transformNorm(...) it. Transforming a vector ignores the - ## translational part of the matrix. - - # normal inverse transform is done by transforming - # by the inverse of the transpose of the inverse of the org. matrix - # which is equivalent with transforming with the transpose. - # | | | AX AY 0 |^-1|^T|^-1 | AX BX 0 | - # | X Y 1 | * | | | BX BY 0 | | | = | X Y 1 | * | AY BY 0 | - # | | | 0 0 1 | | | | 0 0 1 | - # This can be heavily reduced to: - let newx=t.ay*v.y+t.ax*v.x - v.y=t.by*v.y+t.bx*v.x - v.x=newx - -proc rotate90*(v:var Vector2d) {.inline.}= - ## Quickly rotates vector `v` 90 degrees counter clockwise, - ## without using any trigonometrics. - swap(v.x,v.y) - v.x= -v.x - -proc rotate180*(v:var Vector2d){.inline.}= - ## Quickly rotates vector `v` 180 degrees counter clockwise, - ## without using any trigonometrics. - v.x= -v.x - v.y= -v.y - -proc rotate270*(v:var Vector2d) {.inline.}= - ## Quickly rotates vector `v` 270 degrees counter clockwise, - ## without using any trigonometrics. - swap(v.x,v.y) - v.y= -v.y - -proc rotate*(v:var Vector2d,rad:float) = - ## Rotates vector `v` `rad` radians in place. - let - s=sin(rad) - c=cos(rad) - newx=c*v.x-s*v.y - v.y=c*v.y+s*v.x - v.x=newx - -proc scale*(v:var Vector2d,fac:float){.inline.}= - ## Scales vector `v` `rad` radians in place. - v.x*=fac - v.y*=fac - -proc stretch*(v:var Vector2d,facx,facy:float){.inline.}= - ## Stretches vector `v` `facx` times horizontally, - ## and `facy` times vertically. - v.x*=facx - v.y*=facy - -proc mirror*(v:var Vector2d,mirrvec:Vector2d)= - ## Mirrors vector `v` using `mirrvec` as mirror direction. - let - sqx=mirrvec.x*mirrvec.x - sqy=mirrvec.y*mirrvec.y - nd=1.0/(sqx+sqy) #used to normalize invector - xy2=mirrvec.x*mirrvec.y*2.0*nd - sqd=nd*(sqx-sqy) - - if nd==Inf or nd==NegInf: - return #mirroring around a zero vector is arbitrary=>keep as is is fastest - - let newx=xy2*v.y+sqd*v.x - v.y=v.x*xy2-sqd*v.y - v.x=newx - - -proc `-` *(v:Vector2d):Vector2d= - ## Negates a vector - result.x= -v.x - result.y= -v.y - -# declare templated binary operators -makeBinOpVector(`+`) -makeBinOpVector(`-`) -makeBinOpVector(`*`) -makeBinOpVector(`/`) -makeBinOpAssignVector(`+=`) -makeBinOpAssignVector(`-=`) -makeBinOpAssignVector(`*=`) -makeBinOpAssignVector(`/=`) - - -proc dot*(v1,v2:Vector2d):float= - ## Computes the dot product of two vectors. - ## Returns 0.0 if the vectors are perpendicular. - return v1.x*v2.x+v1.y*v2.y - -proc cross*(v1,v2:Vector2d):float= - ## Computes the cross product of two vectors, also called - ## the 'perpendicular dot product' in 2d. Returns 0.0 if the vectors - ## are parallel. - return v1.x*v2.y-v1.y*v2.x - -proc equals*(v1,v2:Vector2d,tol=1.0e-6):bool= - ## Checks if two vectors approximately equals with a tolerance. - return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol - -proc `=~` *(v1,v2:Vector2d):bool= - ## Checks if two vectors approximately equals with a - ## hardcoded tolerance 1e-6 - equals(v1,v2) - -proc angleTo*(v1,v2:Vector2d):float= - ## Returns the smallest of the two possible angles - ## between `v1` and `v2` in radians. - var - nv1=v1 - nv2=v2 - if not nv1.tryNormalize or not nv2.tryNormalize: - return 0.0 # zero length vector has zero angle to any other vector - return safeArccos(dot(nv1,nv2)) - -proc angleCCW*(v1,v2:Vector2d):float= - ## Returns the counter clockwise plane angle from `v1` to `v2`, - ## in range 0 - 2*PI - let a=v1.angleTo(v2) - if v1.cross(v2)>=0.0: - return a - return DEG360-a - -proc angleCW*(v1,v2:Vector2d):float= - ## Returns the clockwise plane angle from `v1` to `v2`, - ## in range 0 - 2*PI - let a=v1.angleTo(v2) - if v1.cross(v2)<=0.0: - return a - return DEG360-a - -proc turnAngle*(v1,v2:Vector2d):float= - ## Returns the amount v1 should be rotated (in radians) to equal v2, - ## in range -PI to PI - let a=v1.angleTo(v2) - if v1.cross(v2)<=0.0: - return -a - return a - -proc bisect*(v1,v2:Vector2d):Vector2d {.noInit.}= - ## Computes the bisector between v1 and v2 as a normalized vector. - ## If one of the input vectors has zero length, a normalized version - ## of the other is returned. If both input vectors has zero length, - ## an arbitrary normalized vector is returned. - var - vmag1=v1.len - vmag2=v2.len - - # zero length vector equals arbitrary vector, just change to magnitude to one to - # avoid zero division - if vmag1==0.0: - if vmag2==0: #both are zero length return any normalized vector - return XAXIS - vmag1=1.0 - if vmag2==0.0: vmag2=1.0 - - let - x1=v1.x/vmag1 - y1=v1.y/vmag1 - x2=v2.x/vmag2 - y2=v2.y/vmag2 - - result.x=(x1 + x2) * 0.5 - result.y=(y1 + y2) * 0.5 - - if not result.tryNormalize(): - # This can happen if vectors are colinear. In this special case - # there are actually two bisectors, we select just - # one of them (x1,y1 rotated 90 degrees ccw). - result.x = -y1 - result.y = x1 - - - -# *************************************** -# Point2d implementation -# *************************************** - -proc point2d*(x,y:float):Point2d = - result.x=x - result.y=y - -proc sqrDist*(a,b:Point2d):float= - ## Computes the squared distance between `a` and `b` - let dx=b.x-a.x - let dy=b.y-a.y - result=dx*dx+dy*dy - -proc dist*(a,b:Point2d):float {.inline.}= - ## Computes the absolute distance between `a` and `b` - result=sqrt(sqrDist(a,b)) - -proc angle*(a,b:Point2d):float= - ## Computes the angle of the vector `b`-`a` - let dx=b.x-a.x - let dy=b.y-a.y - result=arctan2(dy,dx) - if result<0: - result += DEG360 - -proc `$` *(p:Point2d):string= - ## String representation of `p` - result=rtos(p.x) - result.add(",") - result.add(rtos(p.y)) - -proc `&`*(p:Point2d,t:Matrix2d):Point2d {.noInit,inline.} = - ## Concatenates a point `p` with a transform `t`, - ## resulting in a new, transformed point. - - # | AX AY 0 | - # | X Y 1 | * | BX BY 0 | - # | TX TY 1 | - result.x=p.x*t.ax+p.y*t.bx+t.tx - result.y=p.x*t.ay+p.y*t.by+t.ty - -proc `&=` *(p:var Point2d,t:Matrix2d) {.inline.}= - ## Applies transformation `t` onto `p` in place. - let newx=p.x*t.ax+p.y*t.bx+t.tx - p.y=p.x*t.ay+p.y*t.by+t.ty - p.x=newx - - -proc transformInv*(p:var Point2d,t:Matrix2d){.inline.}= - ## Applies the inverse of transformation `t` onto `p` in place. - ## If the matrix is not invertable (determinant=0) , EDivByZero will - ## be raised. - - # | AX AY 0 | ^-1 - # | X Y 1 | * | BX BY 0 | - # | TX TY 1 | - let d=t.determinant - if d==0.0: - raise newException(DivByZeroError,"Cannot invert a zero determinant matrix") - let - newx= (t.bx*t.ty-t.by*t.tx+p.x*t.by-p.y*t.bx)/d - p.y = -(t.ax*t.ty-t.ay*t.tx+p.x*t.ay-p.y*t.ax)/d - p.x=newx - - -proc `+`*(p:Point2d,v:Vector2d):Point2d {.noInit,inline.} = - ## Adds a vector `v` to a point `p`, resulting - ## in a new point. - result.x=p.x+v.x - result.y=p.y+v.y - -proc `+=`*(p:var Point2d,v:Vector2d) {.noInit,inline.} = - ## Adds a vector `v` to a point `p` in place. - p.x+=v.x - p.y+=v.y - -proc `-`*(p:Point2d,v:Vector2d):Point2d {.noInit,inline.} = - ## Subtracts a vector `v` from a point `p`, resulting - ## in a new point. - result.x=p.x-v.x - result.y=p.y-v.y - -proc `-`*(p1,p2:Point2d):Vector2d {.noInit,inline.} = - ## Subtracts `p2`from `p1` resulting in a difference vector. - result.x=p1.x-p2.x - result.y=p1.y-p2.y - -proc `-=`*(p:var Point2d,v:Vector2d) {.noInit,inline.} = - ## Subtracts a vector `v` from a point `p` in place. - p.x-=v.x - p.y-=v.y - -proc equals(p1,p2:Point2d,tol=1.0e-6):bool {.inline.}= - ## Checks if two points approximately equals with a tolerance. - return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol - -proc `=~`*(p1,p2:Point2d):bool {.inline.}= - ## Checks if two vectors approximately equals with a - ## hardcoded tolerance 1e-6 - equals(p1,p2) - -proc polar*(p:Point2d,ang,dist:float):Point2d {.noInit.} = - ## Returns a point with a given angle and distance away from `p` - result.x=p.x+cos(ang)*dist - result.y=p.y+sin(ang)*dist - -proc rotate*(p:var Point2d,rad:float)= - ## Rotates a point in place `rad` radians around origo. - let - c=cos(rad) - s=sin(rad) - newx=p.x*c-p.y*s - p.y=p.y*c+p.x*s - p.x=newx - -proc rotate*(p:var Point2d,rad:float,org:Point2d)= - ## Rotates a point in place `rad` radians using `org` as - ## center of rotation. - let - c=cos(rad) - s=sin(rad) - newx=(p.x - org.x) * c - (p.y - org.y) * s + org.x - p.y=(p.y - org.y) * c + (p.x - org.x) * s + org.y - p.x=newx - -proc scale*(p:var Point2d,fac:float) {.inline.}= - ## Scales a point in place `fac` times with world origo as origin. - p.x*=fac - p.y*=fac - -proc scale*(p:var Point2d,fac:float,org:Point2d){.inline.}= - ## Scales the point in place `fac` times with `org` as origin. - p.x=(p.x - org.x) * fac + org.x - p.y=(p.y - org.y) * fac + org.y - -proc stretch*(p:var Point2d,facx,facy:float){.inline.}= - ## Scales a point in place non uniformly `facx` and `facy` times with - ## world origo as origin. - p.x*=facx - p.y*=facy - -proc stretch*(p:var Point2d,facx,facy:float,org:Point2d){.inline.}= - ## Scales the point in place non uniformly `facx` and `facy` times with - ## `org` as origin. - p.x=(p.x - org.x) * facx + org.x - p.y=(p.y - org.y) * facy + org.y - -proc move*(p:var Point2d,dx,dy:float){.inline.}= - ## Translates a point `dx`, `dy` in place. - p.x+=dx - p.y+=dy - -proc move*(p:var Point2d,v:Vector2d){.inline.}= - ## Translates a point with vector `v` in place. - p.x+=v.x - p.y+=v.y - -proc sgnArea*(a,b,c:Point2d):float= - ## Computes the signed area of the triangle thru points `a`,`b` and `c` - ## result>0.0 for counter clockwise triangle - ## result<0.0 for clockwise triangle - ## This is commonly used to determinate side of a point with respect to a line. - return ((b.x - c.x) * (b.y - a.y)-(b.y - c.y) * (b.x - a.x))*0.5 - -proc area*(a,b,c:Point2d):float= - ## Computes the area of the triangle thru points `a`,`b` and `c` - return abs(sgnArea(a,b,c)) - -proc closestPoint*(p:Point2d,pts:varargs[Point2d]):Point2d= - ## Returns a point selected from `pts`, that has the closest - ## euclidean distance to `p` - assert(pts.len>0) # must have at least one point - - var - bestidx=0 - bestdist=p.sqrDist(pts[0]) - curdist:float - - for idx in 1..high(pts): - curdist=p.sqrDist(pts[idx]) - if curdist<bestdist: - bestidx=idx - bestdist=curdist - - result=pts[bestidx] - - -# *************************************** -# Misc. math utilities that should -# probably be in another module. -# *************************************** -proc normAngle*(ang:float):float= - ## Returns an angle in radians, that is equal to `ang`, - ## but in the range 0 to <2*PI - if ang>=0.0 and ang<DEG360: - return ang - - return ang mod DEG360 - -proc degToRad*(deg:float):float {.inline.}= - ## converts `deg` degrees to radians - deg / RAD2DEGCONST - -proc radToDeg*(rad:float):float {.inline.}= - ## converts `rad` radians to degrees - rad * RAD2DEGCONST diff --git a/lib/pure/basic3d.nim b/lib/pure/basic3d.nim deleted file mode 100644 index e2d2464c0..000000000 --- a/lib/pure/basic3d.nim +++ /dev/null @@ -1,1040 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2013 Robert Persson -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -import math -import strutils -import times - - -## Basic 3d support with vectors, points, matrices and some basic utilities. -## Vectors are implemented as direction vectors, ie. when transformed with a matrix -## the translation part of matrix is ignored. The coordinate system used is -## right handed, because its compatible with 2d coordinate system (rotation around -## zaxis equals 2d rotation). -## Operators `+` , `-` , `*` , `/` , `+=` , `-=` , `*=` and `/=` are implemented -## for vectors and scalars. -## -## -## Quick start example: -## -## .. code-block:: nim -## -## # Create a matrix which first rotates, then scales and at last translates -## -## var m:Matrix3d=rotate(PI,vector3d(1,1,2.5)) & scale(2.0) & move(100.0,200.0,300.0) -## -## # Create a 3d point at (100,150,200) and a vector (5,2,3) -## -## var pt:Point3d=point3d(100.0,150.0,200.0) -## -## var vec:Vector3d=vector3d(5.0,2.0,3.0) -## -## -## pt &= m # transforms pt in place -## -## var pt2:Point3d=pt & m #concatenates pt with m and returns a new point -## -## var vec2:Vector3d=vec & m #concatenates vec with m and returns a new vector - - - -type - Matrix3d* =object - ## Implements a row major 3d matrix, which means - ## transformations are applied the order they are concatenated. - ## This matrix is stored as an 4x4 matrix: - ## [ ax ay az aw ] - ## [ bx by bz bw ] - ## [ cx cy cz cw ] - ## [ tx ty tz tw ] - ax*,ay*,az*,aw*, bx*,by*,bz*,bw*, cx*,cy*,cz*,cw*, tx*,ty*,tz*,tw*:float - Point3d* = object - ## Implements a non-homogeneous 3d point stored as - ## an `x` , `y` and `z` coordinate. - x*,y*,z*:float - Vector3d* = object - ## Implements a 3d **direction vector** stored as - ## an `x` , `y` and `z` coordinate. Direction vector means, - ## that when transforming a vector with a matrix, the translational - ## part of the matrix is ignored. - x*,y*,z*:float -{.deprecated: [TMatrix3d: Matrix3d, TPoint3d: Point3d, TVector3d: Vector3d].} - - -# Some forward declarations -proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):Matrix3d {.noInit.} - ## Creates a new 4x4 3d transformation matrix. - ## `ax` , `ay` , `az` is the local x axis. - ## `bx` , `by` , `bz` is the local y axis. - ## `cx` , `cy` , `cz` is the local z axis. - ## `tx` , `ty` , `tz` is the translation. -proc vector3d*(x,y,z:float):Vector3d {.noInit,inline.} - ## Returns a new 3d vector (`x`,`y`,`z`) -proc point3d*(x,y,z:float):Point3d {.noInit,inline.} - ## Returns a new 4d point (`x`,`y`,`z`) -proc tryNormalize*(v:var Vector3d):bool - ## Modifies `v` to have a length of 1.0, keeping its angle. - ## If `v` has zero length (and thus no angle), it is left unmodified and false is - ## returned, otherwise true is returned. - - - -let - IDMATRIX*:Matrix3d=matrix3d( - 1.0,0.0,0.0,0.0, - 0.0,1.0,0.0,0.0, - 0.0,0.0,1.0,0.0, - 0.0,0.0,0.0,1.0) - ## Quick access to a 3d identity matrix - ORIGO*:Point3d=point3d(0.0,0.0,0.0) - ## Quick access to point (0,0) - XAXIS*:Vector3d=vector3d(1.0,0.0,0.0) - ## Quick access to an 3d x-axis unit vector - YAXIS*:Vector3d=vector3d(0.0,1.0,0.0) - ## Quick access to an 3d y-axis unit vector - ZAXIS*:Vector3d=vector3d(0.0,0.0,1.0) - ## Quick access to an 3d z-axis unit vector - - - -# *************************************** -# Private utils -# *************************************** - -proc rtos(val:float):string= - return formatFloat(val,ffDefault,0) - -proc safeArccos(v:float):float= - ## assumes v is in range 0.0-1.0, but clamps - ## the value to avoid out of domain errors - ## due to rounding issues - return arccos(clamp(v,-1.0,1.0)) - -template makeBinOpVector(s) = - proc s*(a,b:Vector3d):Vector3d {.inline,noInit.} = - vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z)) - proc s*(a:Vector3d,b:float):Vector3d {.inline,noInit.} = - vector3d(s(a.x,b),s(a.y,b),s(a.z,b)) - proc s*(a:float,b:Vector3d):Vector3d {.inline,noInit.} = - vector3d(s(a,b.x),s(a,b.y),s(a,b.z)) - -template makeBinOpAssignVector(s) = - proc s*(a:var Vector3d,b:Vector3d) {.inline.} = - s(a.x,b.x); s(a.y,b.y); s(a.z,b.z) - proc s*(a:var Vector3d,b:float) {.inline.} = - s(a.x,b); s(a.y,b); s(a.z,b) - - - -# *************************************** -# Matrix3d implementation -# *************************************** - -proc setElements*(t:var Matrix3d,ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float) {.inline.}= - ## Sets arbitrary elements in an exisitng matrix. - t.ax=ax - t.ay=ay - t.az=az - t.aw=aw - t.bx=bx - t.by=by - t.bz=bz - t.bw=bw - t.cx=cx - t.cy=cy - t.cz=cz - t.cw=cw - t.tx=tx - t.ty=ty - t.tz=tz - t.tw=tw - -proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):Matrix3d = - result.setElements(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw) - -proc `&`*(a,b:Matrix3d):Matrix3d {.noinit.} = - ## Concatenates matrices returning a new matrix. - result.setElements( - a.aw*b.tx+a.az*b.cx+a.ay*b.bx+a.ax*b.ax, - a.aw*b.ty+a.az*b.cy+a.ay*b.by+a.ax*b.ay, - a.aw*b.tz+a.az*b.cz+a.ay*b.bz+a.ax*b.az, - a.aw*b.tw+a.az*b.cw+a.ay*b.bw+a.ax*b.aw, - - a.bw*b.tx+a.bz*b.cx+a.by*b.bx+a.bx*b.ax, - a.bw*b.ty+a.bz*b.cy+a.by*b.by+a.bx*b.ay, - a.bw*b.tz+a.bz*b.cz+a.by*b.bz+a.bx*b.az, - a.bw*b.tw+a.bz*b.cw+a.by*b.bw+a.bx*b.aw, - - a.cw*b.tx+a.cz*b.cx+a.cy*b.bx+a.cx*b.ax, - a.cw*b.ty+a.cz*b.cy+a.cy*b.by+a.cx*b.ay, - a.cw*b.tz+a.cz*b.cz+a.cy*b.bz+a.cx*b.az, - a.cw*b.tw+a.cz*b.cw+a.cy*b.bw+a.cx*b.aw, - - a.tw*b.tx+a.tz*b.cx+a.ty*b.bx+a.tx*b.ax, - a.tw*b.ty+a.tz*b.cy+a.ty*b.by+a.tx*b.ay, - a.tw*b.tz+a.tz*b.cz+a.ty*b.bz+a.tx*b.az, - a.tw*b.tw+a.tz*b.cw+a.ty*b.bw+a.tx*b.aw) - - -proc scale*(s:float):Matrix3d {.noInit.} = - ## Returns a new scaling matrix. - result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0, 0,0,0,1) - -proc scale*(s:float,org:Point3d):Matrix3d {.noInit.} = - ## Returns a new scaling matrix using, `org` as scale origin. - result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0, - org.x-s*org.x,org.y-s*org.y,org.z-s*org.z,1.0) - -proc stretch*(sx,sy,sz:float):Matrix3d {.noInit.} = - ## Returns new a stretch matrix, which is a - ## scale matrix with non uniform scale in x,y and z. - result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, 0,0,0,1) - -proc stretch*(sx,sy,sz:float,org:Point3d):Matrix3d {.noInit.} = - ## Returns a new stretch matrix, which is a - ## scale matrix with non uniform scale in x,y and z. - ## `org` is used as stretch origin. - result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, org.x-sx*org.x,org.y-sy*org.y,org.z-sz*org.z,1) - -proc move*(dx,dy,dz:float):Matrix3d {.noInit.} = - ## Returns a new translation matrix. - result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, dx,dy,dz,1) - -proc move*(v:Vector3d):Matrix3d {.noInit.} = - ## Returns a new translation matrix from a vector. - result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, v.x,v.y,v.z,1) - - -proc rotate*(angle:float,axis:Vector3d):Matrix3d {.noInit.}= - ## Creates a rotation matrix that rotates `angle` radians over - ## `axis`, which passes through origo. - - # see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf - # for how this is computed - - var normax=axis - if not normax.tryNormalize: #simplifies matrix computation below a lot - raise newException(DivByZeroError,"Cannot rotate around zero length axis") - - let - cs=cos(angle) - si=sin(angle) - omc=1.0-cs - usi=normax.x*si - vsi=normax.y*si - wsi=normax.z*si - u2=normax.x*normax.x - v2=normax.y*normax.y - w2=normax.z*normax.z - uvomc=normax.x*normax.y*omc - uwomc=normax.x*normax.z*omc - vwomc=normax.y*normax.z*omc - - result.setElements( - u2+(1.0-u2)*cs, uvomc+wsi, uwomc-vsi, 0.0, - uvomc-wsi, v2+(1.0-v2)*cs, vwomc+usi, 0.0, - uwomc+vsi, vwomc-usi, w2+(1.0-w2)*cs, 0.0, - 0.0,0.0,0.0,1.0) - -proc rotate*(angle:float,org:Point3d,axis:Vector3d):Matrix3d {.noInit.}= - ## Creates a rotation matrix that rotates `angle` radians over - ## `axis`, which passes through `org`. - - # see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf - # for how this is computed - - var normax=axis - if not normax.tryNormalize: #simplifies matrix computation below a lot - raise newException(DivByZeroError,"Cannot rotate around zero length axis") - - let - u=normax.x - v=normax.y - w=normax.z - u2=u*u - v2=v*v - w2=w*w - cs=cos(angle) - omc=1.0-cs - si=sin(angle) - a=org.x - b=org.y - c=org.z - usi=u*si - vsi=v*si - wsi=w*si - uvomc=normax.x*normax.y*omc - uwomc=normax.x*normax.z*omc - vwomc=normax.y*normax.z*omc - - result.setElements( - u2+(v2+w2)*cs, uvomc+wsi, uwomc-vsi, 0.0, - uvomc-wsi, v2+(u2+w2)*cs, vwomc+usi, 0.0, - uwomc+vsi, vwomc-usi, w2+(u2+v2)*cs, 0.0, - (a*(v2+w2)-u*(b*v+c*w))*omc+(b*w-c*v)*si, - (b*(u2+w2)-v*(a*u+c*w))*omc+(c*u-a*w)*si, - (c*(u2+v2)-w*(a*u+b*v))*omc+(a*v-b*u)*si,1.0) - - -proc rotateX*(angle:float):Matrix3d {.noInit.}= - ## Creates a matrix that rotates around the x-axis with `angle` radians, - ## which is also called a 'roll' matrix. - let - c=cos(angle) - s=sin(angle) - result.setElements( - 1,0,0,0, - 0,c,s,0, - 0,-s,c,0, - 0,0,0,1) - -proc rotateY*(angle:float):Matrix3d {.noInit.}= - ## Creates a matrix that rotates around the y-axis with `angle` radians, - ## which is also called a 'pitch' matrix. - let - c=cos(angle) - s=sin(angle) - result.setElements( - c,0,-s,0, - 0,1,0,0, - s,0,c,0, - 0,0,0,1) - -proc rotateZ*(angle:float):Matrix3d {.noInit.}= - ## Creates a matrix that rotates around the z-axis with `angle` radians, - ## which is also called a 'yaw' matrix. - let - c=cos(angle) - s=sin(angle) - result.setElements( - c,s,0,0, - -s,c,0,0, - 0,0,1,0, - 0,0,0,1) - -proc isUniform*(m:Matrix3d,tol=1.0e-6):bool= - ## Checks if the transform is uniform, that is - ## perpendicular axes of equal length, which means (for example) - ## it cannot transform a sphere into an ellipsoid. - ## `tol` is used as tolerance for both equal length comparison - ## and perpendicular comparison. - - #dot product=0 means perpendicular coord. system, check xaxis vs yaxis and xaxis vs zaxis - if abs(m.ax*m.bx+m.ay*m.by+m.az*m.bz)<=tol and # x vs y - abs(m.ax*m.cx+m.ay*m.cy+m.az*m.cz)<=tol and #x vs z - abs(m.bx*m.cx+m.by*m.cy+m.bz*m.cz)<=tol: #y vs z - - #subtract squared lengths of axes to check if uniform scaling: - let - sqxlen=(m.ax*m.ax+m.ay*m.ay+m.az*m.az) - sqylen=(m.bx*m.bx+m.by*m.by+m.bz*m.bz) - sqzlen=(m.cx*m.cx+m.cy*m.cy+m.cz*m.cz) - if abs(sqxlen-sqylen)<=tol and abs(sqxlen-sqzlen)<=tol: - return true - return false - - - -proc mirror*(planeperp:Vector3d):Matrix3d {.noInit.}= - ## Creates a matrix that mirrors over the plane that has `planeperp` as normal, - ## and passes through origo. `planeperp` does not need to be normalized. - - # https://en.wikipedia.org/wiki/Transformation_matrix - var n=planeperp - if not n.tryNormalize: - raise newException(DivByZeroError,"Cannot mirror over a plane with a zero length normal") - - let - a=n.x - b=n.y - c=n.z - ab=a*b - ac=a*c - bc=b*c - - result.setElements( - 1-2*a*a , -2*ab,-2*ac,0, - -2*ab , 1-2*b*b, -2*bc, 0, - -2*ac, -2*bc, 1-2*c*c,0, - 0,0,0,1) - - -proc mirror*(org:Point3d,planeperp:Vector3d):Matrix3d {.noInit.}= - ## Creates a matrix that mirrors over the plane that has `planeperp` as normal, - ## and passes through `org`. `planeperp` does not need to be normalized. - - # constructs a mirror M like the simpler mirror matrix constructor - # above but premultiplies with the inverse traslation of org - # and postmultiplies with the translation of org. - # With some fiddling this becomes reasonably simple: - var n=planeperp - if not n.tryNormalize: - raise newException(DivByZeroError,"Cannot mirror over a plane with a zero length normal") - - let - a=n.x - b=n.y - c=n.z - ab=a*b - ac=a*c - bc=b*c - aa=a*a - bb=b*b - cc=c*c - tx=org.x - ty=org.y - tz=org.z - - result.setElements( - 1-2*aa , -2*ab,-2*ac,0, - -2*ab , 1-2*bb, -2*bc, 0, - -2*ac, -2*bc, 1-2*cc,0, - 2*(ac*tz+ab*ty+aa*tx), - 2*(bc*tz+bb*ty+ab*tx), - 2*(cc*tz+bc*ty+ac*tx) ,1) - - -proc determinant*(m:Matrix3d):float= - ## Computes the determinant of matrix `m`. - - # This computation is gotten from ratsimp(optimize(determinant(m))) - # in maxima CAS - let - O1=m.cx*m.tw-m.cw*m.tx - O2=m.cy*m.tw-m.cw*m.ty - O3=m.cx*m.ty-m.cy*m.tx - O4=m.cz*m.tw-m.cw*m.tz - O5=m.cx*m.tz-m.cz*m.tx - O6=m.cy*m.tz-m.cz*m.ty - - return (O1*m.ay-O2*m.ax-O3*m.aw)*m.bz+ - (-O1*m.az+O4*m.ax+O5*m.aw)*m.by+ - (O2*m.az-O4*m.ay-O6*m.aw)*m.bx+ - (O3*m.az-O5*m.ay+O6*m.ax)*m.bw - - -proc inverse*(m:Matrix3d):Matrix3d {.noInit.}= - ## Computes the inverse of matrix `m`. If the matrix - ## determinant is zero, thus not invertible, a EDivByZero - ## will be raised. - - # this computation comes from optimize(invert(m)) in maxima CAS - - let - det=m.determinant - O2=m.cy*m.tw-m.cw*m.ty - O3=m.cz*m.tw-m.cw*m.tz - O4=m.cy*m.tz-m.cz*m.ty - O5=m.by*m.tw-m.bw*m.ty - O6=m.bz*m.tw-m.bw*m.tz - O7=m.by*m.tz-m.bz*m.ty - O8=m.by*m.cw-m.bw*m.cy - O9=m.bz*m.cw-m.bw*m.cz - O10=m.by*m.cz-m.bz*m.cy - O11=m.cx*m.tw-m.cw*m.tx - O12=m.cx*m.tz-m.cz*m.tx - O13=m.bx*m.tw-m.bw*m.tx - O14=m.bx*m.tz-m.bz*m.tx - O15=m.bx*m.cw-m.bw*m.cx - O16=m.bx*m.cz-m.bz*m.cx - O17=m.cx*m.ty-m.cy*m.tx - O18=m.bx*m.ty-m.by*m.tx - O19=m.bx*m.cy-m.by*m.cx - - if det==0.0: - raise newException(DivByZeroError,"Cannot normalize zero length vector") - - result.setElements( - (m.bw*O4+m.by*O3-m.bz*O2)/det , (-m.aw*O4-m.ay*O3+m.az*O2)/det, - (m.aw*O7+m.ay*O6-m.az*O5)/det , (-m.aw*O10-m.ay*O9+m.az*O8)/det, - (-m.bw*O12-m.bx*O3+m.bz*O11)/det , (m.aw*O12+m.ax*O3-m.az*O11)/det, - (-m.aw*O14-m.ax*O6+m.az*O13)/det , (m.aw*O16+m.ax*O9-m.az*O15)/det, - (m.bw*O17+m.bx*O2-m.by*O11)/det , (-m.aw*O17-m.ax*O2+m.ay*O11)/det, - (m.aw*O18+m.ax*O5-m.ay*O13)/det , (-m.aw*O19-m.ax*O8+m.ay*O15)/det, - (-m.bx*O4+m.by*O12-m.bz*O17)/det , (m.ax*O4-m.ay*O12+m.az*O17)/det, - (-m.ax*O7+m.ay*O14-m.az*O18)/det , (m.ax*O10-m.ay*O16+m.az*O19)/det) - - -proc equals*(m1:Matrix3d,m2:Matrix3d,tol=1.0e-6):bool= - ## Checks if all elements of `m1`and `m2` is equal within - ## a given tolerance `tol`. - return - abs(m1.ax-m2.ax)<=tol and - abs(m1.ay-m2.ay)<=tol and - abs(m1.az-m2.az)<=tol and - abs(m1.aw-m2.aw)<=tol and - abs(m1.bx-m2.bx)<=tol and - abs(m1.by-m2.by)<=tol and - abs(m1.bz-m2.bz)<=tol and - abs(m1.bw-m2.bw)<=tol and - abs(m1.cx-m2.cx)<=tol and - abs(m1.cy-m2.cy)<=tol and - abs(m1.cz-m2.cz)<=tol and - abs(m1.cw-m2.cw)<=tol and - abs(m1.tx-m2.tx)<=tol and - abs(m1.ty-m2.ty)<=tol and - abs(m1.tz-m2.tz)<=tol and - abs(m1.tw-m2.tw)<=tol - -proc `=~`*(m1,m2:Matrix3d):bool= - ## Checks if `m1` and `m2` is approximately equal, using a - ## tolerance of 1e-6. - equals(m1,m2) - -proc transpose*(m:Matrix3d):Matrix3d {.noInit.}= - ## Returns the transpose of `m` - result.setElements(m.ax,m.bx,m.cx,m.tx,m.ay,m.by,m.cy,m.ty,m.az,m.bz,m.cz,m.tz,m.aw,m.bw,m.cw,m.tw) - -proc getXAxis*(m:Matrix3d):Vector3d {.noInit.}= - ## Gets the local x axis of `m` - result.x=m.ax - result.y=m.ay - result.z=m.az - -proc getYAxis*(m:Matrix3d):Vector3d {.noInit.}= - ## Gets the local y axis of `m` - result.x=m.bx - result.y=m.by - result.z=m.bz - -proc getZAxis*(m:Matrix3d):Vector3d {.noInit.}= - ## Gets the local y axis of `m` - result.x=m.cx - result.y=m.cy - result.z=m.cz - - -proc `$`*(m:Matrix3d):string= - ## String representation of `m` - return rtos(m.ax) & "," & rtos(m.ay) & "," & rtos(m.az) & "," & rtos(m.aw) & - "\n" & rtos(m.bx) & "," & rtos(m.by) & "," & rtos(m.bz) & "," & rtos(m.bw) & - "\n" & rtos(m.cx) & "," & rtos(m.cy) & "," & rtos(m.cz) & "," & rtos(m.cw) & - "\n" & rtos(m.tx) & "," & rtos(m.ty) & "," & rtos(m.tz) & "," & rtos(m.tw) - -proc apply*(m:Matrix3d, x,y,z:var float, translate=false)= - ## Applies transformation `m` onto `x` , `y` , `z` , optionally - ## using the translation part of the matrix. - let - oldx=x - oldy=y - oldz=z - - x=m.cx*oldz+m.bx*oldy+m.ax*oldx - y=m.cy*oldz+m.by*oldy+m.ay*oldx - z=m.cz*oldz+m.bz*oldy+m.az*oldx - - if translate: - x+=m.tx - y+=m.ty - z+=m.tz - -# *************************************** -# Vector3d implementation -# *************************************** -proc vector3d*(x,y,z:float):Vector3d= - result.x=x - result.y=y - result.z=z - -proc len*(v:Vector3d):float= - ## Returns the length of the vector `v`. - sqrt(v.x*v.x+v.y*v.y+v.z*v.z) - -proc `len=`*(v:var Vector3d,newlen:float) {.noInit.} = - ## Sets the length of the vector, keeping its direction. - ## If the vector has zero length before changing it's length, - ## an arbitrary vector of the requested length is returned. - - let fac=newlen/v.len - - if newlen==0.0: - v.x=0.0 - v.y=0.0 - v.z=0.0 - return - - if fac==Inf or fac==NegInf: - #to short for float accuracy - #do as good as possible: - v.x=newlen - v.y=0.0 - v.z=0.0 - else: - v.x*=fac - v.y*=fac - v.z*=fac - - -proc sqrLen*(v:Vector3d):float {.inline.}= - ## Computes the squared length of the vector, which is - ## faster than computing the absolute length. - return v.x*v.x+v.y*v.y+v.z*v.z - -proc `$` *(v:Vector3d):string= - ## String representation of `v` - result=rtos(v.x) - result.add(",") - result.add(rtos(v.y)) - result.add(",") - result.add(rtos(v.z)) - -proc `&` *(v:Vector3d,m:Matrix3d):Vector3d {.noInit.} = - ## Concatenate vector `v` with a transformation matrix. - ## Transforming a vector ignores the translational part - ## of the matrix. - - # | AX AY AZ AW | - # | X Y Z 1 | * | BX BY BZ BW | - # | CX CY CZ CW | - # | 0 0 0 1 | - let - newx=m.cx*v.z+m.bx*v.y+m.ax*v.x - newy=m.cy*v.z+m.by*v.y+m.ay*v.x - result.z=m.cz*v.z+m.bz*v.y+m.az*v.x - result.y=newy - result.x=newx - - -proc `&=` *(v:var Vector3d,m:Matrix3d) {.noInit.} = - ## Applies transformation `m` onto `v` in place. - ## Transforming a vector ignores the translational part - ## of the matrix. - - # | AX AY AZ AW | - # | X Y Z 1 | * | BX BY BZ BW | - # | CX CY CZ CW | - # | 0 0 0 1 | - - let - newx=m.cx*v.z+m.bx*v.y+m.ax*v.x - newy=m.cy*v.z+m.by*v.y+m.ay*v.x - v.z=m.cz*v.z+m.bz*v.y+m.az*v.x - v.y=newy - v.x=newx - -proc transformNorm*(v:var Vector3d,m:Matrix3d)= - ## Applies a normal direction transformation `m` onto `v` in place. - ## The resulting vector is *not* normalized. Transforming a vector ignores the - ## translational part of the matrix. If the matrix is not invertible - ## (determinant=0), an EDivByZero will be raised. - - # transforming a normal is done by transforming - # by the transpose of the inverse of the original matrix - - # Major reason this simple function is here is that this function can be optimized in the future, - # (possibly by hardware) as well as having a consistent API with the 2d version. - v&=transpose(inverse(m)) - -proc transformInv*(v:var Vector3d,m:Matrix3d)= - ## Applies the inverse of `m` on vector `v`. Transforming a vector ignores - ## the translational part of the matrix. Transforming a vector ignores the - ## translational part of the matrix. - ## If the matrix is not invertible (determinant=0), an EDivByZero - ## will be raised. - - # Major reason this simple function is here is that this function can be optimized in the future, - # (possibly by hardware) as well as having a consistent API with the 2d version. - v&=m.inverse - -proc transformNormInv*(vec:var Vector3d,m:Matrix3d)= - ## Applies an inverse normal direction transformation `m` onto `v` in place. - ## This is faster than creating an inverse - ## matrix and transformNorm(...) it. Transforming a vector ignores the - ## translational part of the matrix. - - # see vector2d:s equivalent for a deeper look how/why this works - vec&=m.transpose - -proc tryNormalize*(v:var Vector3d):bool= - ## Modifies `v` to have a length of 1.0, keeping its angle. - ## If `v` has zero length (and thus no angle), it is left unmodified and false is - ## returned, otherwise true is returned. - let mag=v.len - - if mag==0.0: - return false - - v.x/=mag - v.y/=mag - v.z/=mag - - return true - -proc normalize*(v:var Vector3d) {.inline.}= - ## Modifies `v` to have a length of 1.0, keeping its angle. - ## If `v` has zero length, an EDivByZero will be raised. - if not tryNormalize(v): - raise newException(DivByZeroError,"Cannot normalize zero length vector") - -proc rotate*(vec:var Vector3d,angle:float,axis:Vector3d)= - ## Rotates `vec` in place, with `angle` radians over `axis`, which passes - ## through origo. - - # see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf - # for how this is computed - - var normax=axis - if not normax.tryNormalize: - raise newException(DivByZeroError,"Cannot rotate around zero length axis") - - let - cs=cos(angle) - si=sin(angle) - omc=1.0-cs - u=normax.x - v=normax.y - w=normax.z - x=vec.x - y=vec.y - z=vec.z - uxyzomc=(u*x+v*y+w*z)*omc - - vec.x=u*uxyzomc+x*cs+(v*z-w*y)*si - vec.y=v*uxyzomc+y*cs+(w*x-u*z)*si - vec.z=w*uxyzomc+z*cs+(u*y-v*x)*si - -proc scale*(v:var Vector3d,s:float)= - ## Scales the vector in place with factor `s` - v.x*=s - v.y*=s - v.z*=s - -proc stretch*(v:var Vector3d,sx,sy,sz:float)= - ## Scales the vector non uniformly with factors `sx` , `sy` , `sz` - v.x*=sx - v.y*=sy - v.z*=sz - -proc mirror*(v:var Vector3d,planeperp:Vector3d)= - ## Computes the mirrored vector of `v` over the plane - ## that has `planeperp` as normal direction. - ## `planeperp` does not need to be normalized. - - var n=planeperp - n.normalize - - let - x=v.x - y=v.y - z=v.z - a=n.x - b=n.y - c=n.z - ac=a*c - ab=a*b - bc=b*c - - v.x= -2*(ac*z+ab*y+a*a*x)+x - v.y= -2*(bc*z+b*b*y+ab*x)+y - v.z= -2*(c*c*z+bc*y+ac*x)+z - - -proc `-` *(v:Vector3d):Vector3d= - ## Negates a vector - result.x= -v.x - result.y= -v.y - result.z= -v.z - -# declare templated binary operators -makeBinOpVector(`+`) -makeBinOpVector(`-`) -makeBinOpVector(`*`) -makeBinOpVector(`/`) -makeBinOpAssignVector(`+=`) -makeBinOpAssignVector(`-=`) -makeBinOpAssignVector(`*=`) -makeBinOpAssignVector(`/=`) - -proc dot*(v1,v2:Vector3d):float {.inline.}= - ## Computes the dot product of two vectors. - ## Returns 0.0 if the vectors are perpendicular. - return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z - -proc cross*(v1,v2:Vector3d):Vector3d {.inline.}= - ## Computes the cross product of two vectors. - ## The result is a vector which is perpendicular - ## to the plane of `v1` and `v2`, which means - ## cross(xaxis,yaxis)=zaxis. The magnitude of the result is - ## zero if the vectors are colinear. - result.x = (v1.y * v2.z) - (v2.y * v1.z) - result.y = (v1.z * v2.x) - (v2.z * v1.x) - result.z = (v1.x * v2.y) - (v2.x * v1.y) - -proc equals*(v1,v2:Vector3d,tol=1.0e-6):bool= - ## Checks if two vectors approximately equals with a tolerance. - return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol and abs(v2.z-v1.z)<=tol - -proc `=~` *(v1,v2:Vector3d):bool= - ## Checks if two vectors approximately equals with a - ## hardcoded tolerance 1e-6 - equals(v1,v2) - -proc angleTo*(v1,v2:Vector3d):float= - ## Returns the smallest angle between v1 and v2, - ## which is in range 0-PI - var - nv1=v1 - nv2=v2 - if not nv1.tryNormalize or not nv2.tryNormalize: - return 0.0 # zero length vector has zero angle to any other vector - return safeArccos(dot(nv1,nv2)) - -proc arbitraryAxis*(norm:Vector3d):Matrix3d {.noInit.}= - ## Computes the rotation matrix that would transform - ## world z vector into `norm`. The inverse of this matrix - ## is useful to transform a planar 3d object to 2d space. - ## This is the same algorithm used to interpret DXF and DWG files. - const lim=1.0/64.0 - var ax,ay,az:Vector3d - if abs(norm.x)<lim and abs(norm.y)<lim: - ax=cross(YAXIS,norm) - else: - ax=cross(ZAXIS,norm) - - ax.normalize() - ay=cross(norm,ax) - ay.normalize() - az=cross(ax,ay) - - result.setElements( - ax.x,ax.y,ax.z,0.0, - ay.x,ay.y,ay.z,0.0, - az.x,az.y,az.z,0.0, - 0.0,0.0,0.0,1.0) - -proc bisect*(v1,v2:Vector3d):Vector3d {.noInit.}= - ## Computes the bisector between v1 and v2 as a normalized vector. - ## If one of the input vectors has zero length, a normalized version - ## of the other is returned. If both input vectors has zero length, - ## an arbitrary normalized vector `v1` is returned. - var - vmag1=v1.len - vmag2=v2.len - - # zero length vector equals arbitrary vector, just change - # magnitude to one to avoid zero division - if vmag1==0.0: - if vmag2==0: #both are zero length return any normalized vector - return XAXIS - vmag1=1.0 - if vmag2==0.0: vmag2=1.0 - - let - x1=v1.x/vmag1 - y1=v1.y/vmag1 - z1=v1.z/vmag1 - x2=v2.x/vmag2 - y2=v2.y/vmag2 - z2=v2.z/vmag2 - - result.x=(x1 + x2) * 0.5 - result.y=(y1 + y2) * 0.5 - result.z=(z1 + z2) * 0.5 - - if not result.tryNormalize(): - # This can happen if vectors are colinear. In this special case - # there are actually inifinitely many bisectors, we select just - # one of them. - result=v1.cross(XAXIS) - if result.sqrLen<1.0e-9: - result=v1.cross(YAXIS) - if result.sqrLen<1.0e-9: - result=v1.cross(ZAXIS) # now we should be guaranteed to have succeeded - result.normalize - - - -# *************************************** -# Point3d implementation -# *************************************** -proc point3d*(x,y,z:float):Point3d= - result.x=x - result.y=y - result.z=z - -proc sqrDist*(a,b:Point3d):float= - ## Computes the squared distance between `a`and `b` - let dx=b.x-a.x - let dy=b.y-a.y - let dz=b.z-a.z - result=dx*dx+dy*dy+dz*dz - -proc dist*(a,b:Point3d):float {.inline.}= - ## Computes the absolute distance between `a`and `b` - result=sqrt(sqrDist(a,b)) - -proc `$` *(p:Point3d):string= - ## String representation of `p` - result=rtos(p.x) - result.add(",") - result.add(rtos(p.y)) - result.add(",") - result.add(rtos(p.z)) - -proc `&`*(p:Point3d,m:Matrix3d):Point3d= - ## Concatenates a point `p` with a transform `m`, - ## resulting in a new, transformed point. - result.z=m.cz*p.z+m.bz*p.y+m.az*p.x+m.tz - result.y=m.cy*p.z+m.by*p.y+m.ay*p.x+m.ty - result.x=m.cx*p.z+m.bx*p.y+m.ax*p.x+m.tx - -proc `&=` *(p:var Point3d,m:Matrix3d)= - ## Applies transformation `m` onto `p` in place. - let - x=p.x - y=p.y - z=p.z - p.x=m.cx*z+m.bx*y+m.ax*x+m.tx - p.y=m.cy*z+m.by*y+m.ay*x+m.ty - p.z=m.cz*z+m.bz*y+m.az*x+m.tz - -proc transformInv*(p:var Point3d,m:Matrix3d)= - ## Applies the inverse of transformation `m` onto `p` in place. - ## If the matrix is not invertable (determinant=0) , EDivByZero will - ## be raised. - - # can possibly be more optimized in the future so use this function when possible - p&=inverse(m) - - -proc `+`*(p:Point3d,v:Vector3d):Point3d {.noInit,inline.} = - ## Adds a vector `v` to a point `p`, resulting - ## in a new point. - result.x=p.x+v.x - result.y=p.y+v.y - result.z=p.z+v.z - -proc `+=`*(p:var Point3d,v:Vector3d) {.noInit,inline.} = - ## Adds a vector `v` to a point `p` in place. - p.x+=v.x - p.y+=v.y - p.z+=v.z - -proc `-`*(p:Point3d,v:Vector3d):Point3d {.noInit,inline.} = - ## Subtracts a vector `v` from a point `p`, resulting - ## in a new point. - result.x=p.x-v.x - result.y=p.y-v.y - result.z=p.z-v.z - -proc `-`*(p1,p2:Point3d):Vector3d {.noInit,inline.} = - ## Subtracts `p2`from `p1` resulting in a difference vector. - result.x=p1.x-p2.x - result.y=p1.y-p2.y - result.z=p1.z-p2.z - -proc `-=`*(p:var Point3d,v:Vector3d) {.noInit,inline.} = - ## Subtracts a vector `v` from a point `p` in place. - p.x-=v.x - p.y-=v.y - p.z-=v.z - -proc equals(p1,p2:Point3d,tol=1.0e-6):bool {.inline.}= - ## Checks if two points approximately equals with a tolerance. - return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol and abs(p2.z-p1.z)<=tol - -proc `=~`*(p1,p2:Point3d):bool {.inline.}= - ## Checks if two vectors approximately equals with a - ## hardcoded tolerance 1e-6 - equals(p1,p2) - -proc rotate*(p:var Point3d,rad:float,axis:Vector3d)= - ## Rotates point `p` in place `rad` radians about an axis - ## passing through origo. - - var v=vector3d(p.x,p.y,p.z) - v.rotate(rad,axis) # reuse this code here since doing the same thing and quite complicated - p.x=v.x - p.y=v.y - p.z=v.z - -proc rotate*(p:var Point3d,angle:float,org:Point3d,axis:Vector3d)= - ## Rotates point `p` in place `rad` radians about an axis - ## passing through `org` - - # see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf - # for how this is computed - - var normax=axis - normax.normalize - - let - cs=cos(angle) - omc=1.0-cs - si=sin(angle) - u=normax.x - v=normax.y - w=normax.z - a=org.x - b=org.y - c=org.z - x=p.x - y=p.y - z=p.z - uu=u*u - vv=v*v - ww=w*w - ux=u*p.x - vy=v*p.y - wz=w*p.z - au=a*u - bv=b*v - cw=c*w - uxmvymwz=ux-vy-wz - - p.x=(a*(vv+ww)-u*(bv+cw-uxmvymwz))*omc + x*cs + (b*w+v*z-c*v-w*y)*si - p.y=(b*(uu+ww)-v*(au+cw-uxmvymwz))*omc + y*cs + (c*u-a*w+w*x-u*z)*si - p.z=(c*(uu+vv)-w*(au+bv-uxmvymwz))*omc + z*cs + (a*v+u*y-b*u-v*x)*si - -proc scale*(p:var Point3d,fac:float) {.inline.}= - ## Scales a point in place `fac` times with world origo as origin. - p.x*=fac - p.y*=fac - p.z*=fac - -proc scale*(p:var Point3d,fac:float,org:Point3d){.inline.}= - ## Scales the point in place `fac` times with `org` as origin. - p.x=(p.x - org.x) * fac + org.x - p.y=(p.y - org.y) * fac + org.y - p.z=(p.z - org.z) * fac + org.z - -proc stretch*(p:var Point3d,facx,facy,facz:float){.inline.}= - ## Scales a point in place non uniformly `facx` , `facy` , `facz` times - ## with world origo as origin. - p.x*=facx - p.y*=facy - p.z*=facz - -proc stretch*(p:var Point3d,facx,facy,facz:float,org:Point3d){.inline.}= - ## Scales the point in place non uniformly `facx` , `facy` , `facz` times - ## with `org` as origin. - p.x=(p.x - org.x) * facx + org.x - p.y=(p.y - org.y) * facy + org.y - p.z=(p.z - org.z) * facz + org.z - - -proc move*(p:var Point3d,dx,dy,dz:float){.inline.}= - ## Translates a point `dx` , `dy` , `dz` in place. - p.x+=dx - p.y+=dy - p.z+=dz - -proc move*(p:var Point3d,v:Vector3d){.inline.}= - ## Translates a point with vector `v` in place. - p.x+=v.x - p.y+=v.y - p.z+=v.z - -proc area*(a,b,c:Point3d):float {.inline.}= - ## Computes the area of the triangle thru points `a` , `b` and `c` - - # The area of a planar 3d quadliteral is the magnitude of the cross - # product of two edge vectors. Taking this time 0.5 gives the triangle area. - return cross(b-a,c-a).len*0.5 - diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index d51a5c388..dbdf17514 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -287,8 +287,6 @@ proc exclImpl[A](s: var HashSet[A], key: A) : bool {. inline .} = if i >= 0: result = false - s.data[i].hcode = 0 - s.data[i].key = default(type(s.data[i].key)) dec(s.counter) while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1 var j = i # The correctness of this depends on (h+1) in nextTry, @@ -300,7 +298,7 @@ proc exclImpl[A](s: var HashSet[A], key: A) : bool {. inline .} = if isEmpty(s.data[i].hcode): # end of collision cluster; So all done return r = s.data[i].hcode and msk # "home" location of key@i - shallowCopy(s.data[j], s.data[i]) # data[j] will be marked EMPTY next loop + shallowCopy(s.data[j], s.data[i]) # data[i] will be marked EMPTY next loop proc missingOrExcl*[A](s: var HashSet[A], key: A): bool = ## Excludes `key` in the set `s` and tells if `key` was removed from `s`. @@ -662,9 +660,12 @@ proc card*[A](s: OrderedSet[A]): int {.inline.} = template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = var h = s.first + var idx = 0 while h >= 0: var nxt = s.data[h].next - if isFilled(s.data[h].hcode): yieldStmt + if isFilled(s.data[h].hcode): + yieldStmt + inc(idx) h = nxt iterator items*[A](s: OrderedSet[A]): A = @@ -689,6 +690,11 @@ iterator items*[A](s: OrderedSet[A]): A = forAllOrderedPairs: yield s.data[h].key +iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] = + assert s.isValid, "The set needs to be initialized" + forAllOrderedPairs: + yield (idx, s.data[h].key) + proc rawGetKnownHC[A](s: OrderedSet[A], key: A, hc: Hash): int {.inline.} = rawGetKnownHCImpl() @@ -760,6 +766,67 @@ proc incl*[A](s: var HashSet[A], other: OrderedSet[A]) = assert other.isValid, "The set `other` needs to be initialized." for item in other: incl(s, item) +proc exclImpl[A](s: var OrderedSet[A], key: A) : bool {. inline .} = + assert s.isValid, "The set needs to be initialized." + var hc: Hash + var i = rawGet(s, key, hc) + var msk = high(s.data) + result = true + + if i >= 0: + result = false + # Fix ordering + if s.first == i: + s.first = s.data[i].next + else: + var itr = s.first + while true: + if (s.data[itr].next == i): + s.data[itr].next = s.data[i].next + if s.last == i: + s.last = itr + break + itr = s.data[itr].next + + dec(s.counter) + while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1 + var j = i # The correctness of this depends on (h+1) in nextTry, + var r = j # though may be adaptable to other simple sequences. + s.data[i].hcode = 0 # mark current EMPTY + s.data[i].key = default(type(s.data[i].key)) + s.data[i].next = 0 + doWhile((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)): + i = (i + 1) and msk # increment mod table size + if isEmpty(s.data[i].hcode): # end of collision cluster; So all done + return + r = s.data[i].hcode and msk # "home" location of key@i + shallowCopy(s.data[j], s.data[i]) # data[i] will be marked EMPTY next loop + +proc missingOrExcl*[A](s: var OrderedSet[A], key: A): bool = + ## Excludes `key` in the set `s` and tells if `key` was removed from `s`. Efficiency: O(n). + ## + ## The difference with regards to the `excl() <#excl,TOrderedSet[A],A>`_ proc is + ## that this proc returns `true` if `key` was not present in `s`. Example: + ## + ## .. code-block:: + ## var s = toOrderedSet([2, 3, 6, 7]) + ## assert s.missingOrExcl(4) == true + ## assert s.missingOrExcl(6) == false + exclImpl(s, key) + + +proc excl*[A](s: var OrderedSet[A], key: A) = + ## Excludes `key` from the set `s`. Efficiency: O(n). + ## + ## This doesn't do anything if `key` is not found in `s`. Example: + ## + ## .. code-block:: + ## var s = toOrderedSet([2, 3, 6, 7]) + ## s.excl(2) + ## s.excl(2) + ## assert s.len == 3 + discard exclImpl(s, key) + proc containsOrIncl*[A](s: var OrderedSet[A], key: A): bool = ## Includes `key` in the set `s` and tells if `key` was added to `s`. ## @@ -986,6 +1053,24 @@ when isMainModule and not defined(release): assert a.len == b.card assert a.len == 2 + block setPairsIterator: + var s = toOrderedSet([1, 3, 5, 7]) + var items = newSeq[tuple[a: int, b: int]]() + for idx, item in s: items.add((idx, item)) + assert items == @[(0, 1), (1, 3), (2, 5), (3, 7)] + + block exclusions: + var s = toOrderedSet([1, 2, 3, 6, 7, 4]) + + s.excl(3) + s.excl(3) + s.excl(1) + s.excl(4) + + var items = newSeq[int]() + for item in s: items.add item + assert items == @[2, 6, 7] + #block orderedSetIterator: # var a = initOrderedSet[int]() # for value in [9, 2, 1, 5, 1, 8, 4, 2]: @@ -1030,6 +1115,11 @@ when isMainModule and not defined(release): if s <= i or mustRehash(s, i): echo "performance issue: rightSize() will not elide enlarge() at ", i + block missingOrExcl: + var s = toOrderedSet([2, 3, 6, 7]) + assert s.missingOrExcl(4) == true + assert s.missingOrExcl(6) == false + when not defined(testing): echo "Micro tests run successfully." diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 097952588..656114fb1 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -721,6 +721,16 @@ proc getElems*(n: JsonNode, default: seq[JsonNode] = @[]): seq[JsonNode] = if n.isNil or n.kind != JArray: return default else: return n.elems +proc add*(father, child: JsonNode) = + ## Adds `child` to a JArray node `father`. + assert father.kind == JArray + father.elems.add(child) + +proc add*(obj: JsonNode, key: string, val: JsonNode) = + ## Sets a field from a `JObject`. + assert obj.kind == JObject + obj.fields[key] = val + proc `%`*(s: string): JsonNode = ## Generic constructor for JSON data. Creates a new `JString JsonNode`. new(result) @@ -759,6 +769,19 @@ proc `%`*[T](elements: openArray[T]): JsonNode = result = newJArray() for elem in elements: result.add(%elem) +when false: + # For 'consistency' we could do this, but that only pushes people further + # into that evil comfort zone where they can use Nim without understanding it + # causing problems later on. + proc `%`*(elements: set[bool]): JsonNode = + ## Generic constructor for JSON data. Creates a new `JObject JsonNode`. + ## This can only be used with the empty set ``{}`` and is supported + ## to prevent the gotcha ``%*{}`` which used to produce an empty + ## JSON array. + result = newJObject() + assert false notin elements, "usage error: only empty sets allowed" + assert true notin elements, "usage error: only empty sets allowed" + proc `%`*(o: object): JsonNode = ## Generic constructor for JSON data. Creates a new `JObject JsonNode` result = newJObject() @@ -779,27 +802,25 @@ proc `%`*(o: enum): JsonNode = proc toJson(x: NimNode): NimNode {.compiletime.} = case x.kind of nnkBracket: # array + if x.len == 0: return newCall(bindSym"newJArray") result = newNimNode(nnkBracket) for i in 0 .. <x.len: result.add(toJson(x[i])) - + result = newCall(bindSym"%", result) of nnkTableConstr: # object + if x.len == 0: return newCall(bindSym"newJObject") result = newNimNode(nnkTableConstr) for i in 0 .. <x.len: x[i].expectKind nnkExprColonExpr - result.add(newNimNode(nnkExprColonExpr).add(x[i][0]).add(toJson(x[i][1]))) - + result.add newTree(nnkExprColonExpr, x[i][0], toJson(x[i][1])) + result = newCall(bindSym"%", result) of nnkCurly: # empty object - result = newNimNode(nnkTableConstr) x.expectLen(0) - + result = newCall(bindSym"newJObject") of nnkNilLit: - result = newCall("newJNull") - + result = newCall(bindSym"newJNull") else: - result = x - - result = prefix(result, "%") + result = newCall(bindSym"%", x) macro `%*`*(x: untyped): untyped = ## Convert an expression to a JsonNode directly, without having to specify @@ -909,16 +930,6 @@ proc contains*(node: JsonNode, val: JsonNode): bool = proc existsKey*(node: JsonNode, key: string): bool {.deprecated.} = node.hasKey(key) ## Deprecated for `hasKey` -proc add*(father, child: JsonNode) = - ## Adds `child` to a JArray node `father`. - assert father.kind == JArray - father.elems.add(child) - -proc add*(obj: JsonNode, key: string, val: JsonNode) = - ## Sets a field from a `JObject`. - assert obj.kind == JObject - obj.fields[key] = val - proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = ## Sets a field from a `JObject`. assert(obj.kind == JObject) @@ -1203,7 +1214,7 @@ proc parseJson(p: var JsonParser): JsonNode = raiseParseErr(p, "{") when not defined(js): - proc parseJson*(s: Stream, filename: string): JsonNode = + proc parseJson*(s: Stream, filename: string = ""): JsonNode = ## Parses from a stream `s` into a `JsonNode`. `filename` is only needed ## for nice error messages. ## If `s` contains extra data, it will raise `JsonParsingError`. @@ -1934,4 +1945,8 @@ when isMainModule: except JsonParsingError: doAssert getCurrentExceptionMsg().contains(errorMessages[errEofExpected]) + # bug #6438 + doAssert($ %*[] == "[]") + doAssert($ %*{} == "{}") + echo("Tests succeeded!") diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index 1a62c0bf6..98fc62a3b 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -498,7 +498,7 @@ proc getLocalAddr*(socket: SocketHandle, domain: Domain): (string, Port) = # Cannot use INET6_ADDRSTRLEN here, because it's a C define. var buf: array[64, char] if inet_ntop(name.sin6_family.cint, - addr name, buf.cstring, sizeof(buf).int32).isNil: + addr name.sin6_addr, buf.cstring, sizeof(buf).int32).isNil: raiseOSError(osLastError()) result = ($buf, Port(nativesockets.ntohs(name.sin6_port))) else: @@ -534,7 +534,7 @@ proc getPeerAddr*(socket: SocketHandle, domain: Domain): (string, Port) = # Cannot use INET6_ADDRSTRLEN here, because it's a C define. var buf: array[64, char] if inet_ntop(name.sin6_family.cint, - addr name, buf.cstring, sizeof(buf).int32).isNil: + addr name.sin6_addr, buf.cstring, sizeof(buf).int32).isNil: raiseOSError(osLastError()) result = ($buf, Port(nativesockets.ntohs(name.sin6_port))) else: diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 2abb80016..ad63bbcb6 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -15,7 +15,7 @@ ## A value of type ``Option[T]`` either contains a value `x` (represented as ## ``some(x)``) or is empty (``none(T)``). ## -## This can be useful when you have a value that can be present or not. The +## This can be useful when you have a value that can be present or not. The ## absence of a value is often represented by ``nil``, but it is not always ## available, nor is it always a good solution. ## @@ -67,10 +67,8 @@ ## assert(false) # This will not be reached ## except UnpackError: # Because an exception is raised ## discard - import typetraits - type Option*[T] = object ## An optional type that stores its value and state separately in a boolean. @@ -78,7 +76,6 @@ type has: bool UnpackError* = ref object of ValueError - proc some*[T](val: T): Option[T] = ## Returns a ``Option`` that has this value. result.has = true @@ -88,14 +85,12 @@ proc none*(T: typedesc): Option[T] = ## Returns a ``Option`` for this type that has no value. result.has = false - proc isSome*[T](self: Option[T]): bool = self.has proc isNone*[T](self: Option[T]): bool = not self.has - proc unsafeGet*[T](self: Option[T]): T = ## Returns the value of a ``some``. Behavior is undefined for ``none``. assert self.isSome @@ -110,12 +105,11 @@ proc get*[T](self: Option[T]): T = proc get*[T](self: Option[T], otherwise: T): T = ## Returns the contents of this option or `otherwise` if the option is none. - if self.isSome: + if self.has: self.val else: otherwise - proc map*[T](self: Option[T], callback: proc (input: T)) = ## Applies a callback to the value in this Option if self.has: @@ -123,12 +117,27 @@ proc map*[T](self: Option[T], callback: proc (input: T)) = proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] = ## Applies a callback to the value in this Option and returns an option - ## containing the new value. If this option is None, None will be returned + ## containing the new value. If this option is None, None will be returned. if self.has: - some[R]( callback(self.val) ) + some[R](callback(self.val)) else: none(R) +proc flatten*[A](self: Option[Option[A]]): Option[A] = + ## Remove one level of structure in a nested Option. + if self.has: + self.val + else: + none(A) + +proc flatMap*[A, B](self: Option[A], callback: proc (input: A): Option[B]): Option[B] = + ## Applies a callback to the value in this Option and returns an + ## option containing the new value. If this option is None, None will be + ## returned. Similar to ``map``, with the difference that the callback + ## returns an Option, not a raw value. This allows multiple procs with a + ## signature of ``A -> Option[B]`` (including A = B) to be chained together. + map(self, callback).flatten() + proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] = ## Applies a callback to the value in this Option. If the callback returns ## `true`, the option is returned as a Some. If it returns false, it is @@ -138,21 +147,18 @@ proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] = else: self - proc `==`*(a, b: Option): bool = ## Returns ``true`` if both ``Option``s are ``none``, ## or if they have equal values (a.has and b.has and a.val == b.val) or (not a.has and not b.has) - -proc `$`*[T]( self: Option[T] ): string = +proc `$`*[T](self: Option[T]): string = ## Returns the contents of this option or `otherwise` if the option is none. if self.has: "Some(" & $self.val & ")" else: "None[" & T.name & "]" - when isMainModule: import unittest, sequtils @@ -198,12 +204,12 @@ when isMainModule: check false test "get with a default value": - check( some("Correct").get("Wrong") == "Correct" ) - check( stringNone.get("Correct") == "Correct" ) + check(some("Correct").get("Wrong") == "Correct") + check(stringNone.get("Correct") == "Correct") test "$": - check( $(some("Correct")) == "Some(Correct)" ) - check( $(stringNone) == "None[string]" ) + check($(some("Correct")) == "Some(Correct)") + check($(stringNone) == "None[string]") test "map with a void result": var procRan = 0 @@ -212,11 +218,38 @@ when isMainModule: intNone.map(proc (v: int) = check false) test "map": - check( some(123).map(proc (v: int): int = v * 2) == some(246) ) - check( intNone.map(proc (v: int): int = v * 2).isNone ) + check(some(123).map(proc (v: int): int = v * 2) == some(246)) + check(intNone.map(proc (v: int): int = v * 2).isNone) test "filter": - check( some(123).filter(proc (v: int): bool = v == 123) == some(123) ) - check( some(456).filter(proc (v: int): bool = v == 123).isNone ) - check( intNone.filter(proc (v: int): bool = check false).isNone ) - + check(some(123).filter(proc (v: int): bool = v == 123) == some(123)) + check(some(456).filter(proc (v: int): bool = v == 123).isNone) + check(intNone.filter(proc (v: int): bool = check false).isNone) + + test "flatMap": + proc addOneIfNotZero(v: int): Option[int] = + if v != 0: + result = some(v + 1) + else: + result = none(int) + + check(some(1).flatMap(addOneIfNotZero) == some(2)) + check(some(0).flatMap(addOneIfNotZero) == none(int)) + check(some(1).flatMap(addOneIfNotZero).flatMap(addOneIfNotZero) == some(3)) + + proc maybeToString(v: int): Option[string] = + if v != 0: + result = some($v) + else: + result = none(string) + + check(some(1).flatMap(maybeToString) == some("1")) + + proc maybeExclaim(v: string): Option[string] = + if v != "": + result = some v & "!" + else: + result = none(string) + + check(some(1).flatMap(maybeToString).flatMap(maybeExclaim) == some("1!")) + check(some(0).flatMap(maybeToString).flatMap(maybeExclaim) == none(string)) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index fa723f593..07429b9a9 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -410,13 +410,11 @@ when defined(Windows) and not defined(useNimRtl): result.readDataImpl = hsReadData result.writeDataImpl = hsWriteData - proc buildCommandLine(a: string, args: openArray[string]): cstring = - var res = quoteShell(a) + proc buildCommandLine(a: string, args: openArray[string]): string = + result = quoteShell(a) for i in 0..high(args): - res.add(' ') - res.add(quoteShell(args[i])) - result = cast[cstring](alloc0(res.len+1)) - copyMem(result, cstring(res), res.len) + result.add(' ') + result.add(quoteShell(args[i])) proc buildEnv(env: StringTableRef): tuple[str: cstring, len: int] = var L = 0 @@ -540,11 +538,13 @@ when defined(Windows) and not defined(useNimRtl): result.errHandle = FileHandle(si.hStdError) var cmdl: cstring + var cmdRoot: string if poEvalCommand in options: cmdl = command assert args.len == 0 else: - cmdl = buildCommandLine(command, args) + cmdRoot = buildCommandLine(command, args) + cmdl = cstring(cmdRoot) var wd: cstring = nil var e = (str: nil.cstring, len: -1) if len(workingDir) > 0: wd = workingDir diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index b39d3b691..8b4e6bd05 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -888,7 +888,7 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, n = x result = newString(len) for j in countdown(len-1, 0): - result[j] = HexChars[(n and 0xF).int] + result[j] = HexChars[int(n and 0xF)] n = n shr 4 # handle negative overflow if n == 0 and x < 0: n = -1 diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 28691fcb4..3772a213a 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -509,10 +509,6 @@ macro check*(conditions: untyped): untyped = ## "AKB48".toLowerAscii() == "akb48" ## 'C' in teams let checked = callsite()[1] - var - argsAsgns = newNimNode(nnkStmtList) - argsPrintOuts = newNimNode(nnkStmtList) - counter = 0 template asgn(a: untyped, value: typed) = var a = value # XXX: we need "var: var" here in order to @@ -522,66 +518,71 @@ macro check*(conditions: untyped): untyped = when compiles(string($value)): checkpoint(name & " was " & $value) - proc inspectArgs(exp: NimNode): NimNode = - result = copyNimTree(exp) + proc inspectArgs(exp: NimNode): tuple[assigns, check, printOuts: NimNode] = + result.check = copyNimTree(exp) + result.assigns = newNimNode(nnkStmtList) + result.printOuts = newNimNode(nnkStmtList) + + var counter = 0 + if exp[0].kind == nnkIdent and - $exp[0] in ["and", "or", "not", "in", "notin", "==", "<=", + $exp[0] in ["not", "in", "notin", "==", "<=", ">=", "<", ">", "!=", "is", "isnot"]: - for i in countup(1, exp.len - 1): + + for i in 1 ..< exp.len: if exp[i].kind notin nnkLiterals: inc counter - var arg = newIdentNode(":p" & $counter) - var argStr = exp[i].toStrLit - var paramAst = exp[i] + let argStr = exp[i].toStrLit + let paramAst = exp[i] if exp[i].kind == nnkIdent: - argsPrintOuts.add getAst(print(argStr, paramAst)) - if exp[i].kind in nnkCallKinds: - var callVar = newIdentNode(":c" & $counter) - argsAsgns.add getAst(asgn(callVar, paramAst)) - result[i] = callVar - argsPrintOuts.add getAst(print(argStr, callVar)) + result.printOuts.add getAst(print(argStr, paramAst)) + if exp[i].kind in nnkCallKinds + { nnkDotExpr, nnkBracketExpr }: + let callVar = newIdentNode(":c" & $counter) + result.assigns.add getAst(asgn(callVar, paramAst)) + result.check[i] = callVar + result.printOuts.add getAst(print(argStr, callVar)) if exp[i].kind == nnkExprEqExpr: # ExprEqExpr # Ident !"v" # IntLit 2 - result[i] = exp[i][1] + result.check[i] = exp[i][1] if exp[i].typekind notin {ntyTypeDesc}: - argsAsgns.add getAst(asgn(arg, paramAst)) - argsPrintOuts.add getAst(print(argStr, arg)) + let arg = newIdentNode(":p" & $counter) + result.assigns.add getAst(asgn(arg, paramAst)) + result.printOuts.add getAst(print(argStr, arg)) if exp[i].kind != nnkExprEqExpr: - result[i] = arg + result.check[i] = arg else: - result[i][1] = arg + result.check[i][1] = arg case checked.kind of nnkCallKinds: - template rewrite(call, lineInfoLit, callLit, - argAssgs, argPrintOuts) = + + let (assigns, check, printOuts) = inspectArgs(checked) + let lineinfo = newStrLitNode(checked.lineinfo) + let callLit = checked.toStrLit + result = quote do: block: - argAssgs #all callables (and assignments) are run here - if not call: - checkpoint(lineInfoLit & ": Check failed: " & callLit) - argPrintOuts + `assigns` + if not `check`: + checkpoint(`lineinfo` & ": Check failed: " & `callLit`) + `printOuts` fail() - var checkedStr = checked.toStrLit - let parameterizedCheck = inspectArgs(checked) - result = getAst(rewrite(parameterizedCheck, checked.lineinfo, checkedStr, - argsAsgns, argsPrintOuts)) - of nnkStmtList: result = newNimNode(nnkStmtList) - for i in countup(0, checked.len - 1): - if checked[i].kind != nnkCommentStmt: - result.add(newCall(!"check", checked[i])) + for node in checked: + if node.kind != nnkCommentStmt: + result.add(newCall(!"check", node)) else: - template rewrite(exp, lineInfoLit, expLit) = - if not exp: - checkpoint(lineInfoLit & ": Check failed: " & expLit) - fail() + let lineinfo = newStrLitNode(checked.lineinfo) + let callLit = checked.toStrLit - result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit)) + result = quote do: + if not `checked`: + checkpoint(`lineinfo` & ": Check failed: " & `callLit`) + fail() template require*(conditions: untyped) = ## Same as `check` except any failed test causes the program to quit diff --git a/lib/system.nim b/lib/system.nim index ad958d733..dc3152faf 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -246,6 +246,9 @@ type UncheckedArray* {.unchecked.}[T] = array[0, T] ## Array with no bounds checking +when defined(nimHasOpt): + type opt*{.magic: "Opt".}[T] + proc high*[T: Ordinal](x: T): T {.magic: "High", noSideEffect.} ## returns the highest possible index of an array, a sequence, a string or ## the highest possible value of an ordinal value `x`. As a special @@ -409,8 +412,7 @@ when not defined(JS): when not defined(JS) and not defined(nimscript): template space(s: PGenericSeq): int {.dirty.} = - s.reserved and not seqShallowFlag - + s.reserved and not (seqShallowFlag or strlitFlag) include "system/hti" type @@ -718,7 +720,7 @@ proc len*[TOpenArray: openArray|varargs](x: TOpenArray): int {. magic: "LengthOpenArray", noSideEffect.} proc len*(x: string): int {.magic: "LengthStr", noSideEffect.} proc len*(x: cstring): int {.magic: "LengthStr", noSideEffect.} -proc len*[I, T](x: array[I, T]): int {.magic: "LengthArray", noSideEffect.} +proc len*(x: (type array)|array): int {.magic: "LengthArray", noSideEffect.} proc len*[T](x: seq[T]): int {.magic: "LengthSeq", noSideEffect.} ## returns the length of an array, an openarray, a sequence or a string. ## This is roughly the same as ``high(T)-low(T)+1``, but its resulting type is @@ -1329,6 +1331,9 @@ const ## "amd64", "mips", "mipsel", "arm", "arm64", "mips64", "mips64el". seqShallowFlag = low(int) + strlitFlag = 1 shl (sizeof(int)*8 - 2) # later versions of the codegen \ + # emit this flag + # for string literals, it allows for some optimizations. {.push profiler: off.} when defined(nimKnowsNimvm): @@ -1435,7 +1440,12 @@ when defined(nimdoc): elif defined(genode): proc quit*(errorcode: int = QuitSuccess) {.magic: "Exit", noreturn, - importcpp: "genodeEnv->parent().exit(@)", header: "<base/env.h>".} + importcpp: "genodeEnv->parent().exit(@); Genode::sleep_forever()", + header: "<base/sleep.h>".} + +elif defined(nodejs): + proc quit*(errorcode: int = QuitSuccess) {.magic: "Exit", + importc: "process.exit", noreturn.} else: proc quit*(errorcode: int = QuitSuccess) {. @@ -3402,10 +3412,10 @@ when hasAlloc or defined(nimscript): proc `[]`*(s: string, x: Slice[int]): string {.inline.} = ## slice operation for strings. ## returns the inclusive range [s[x.a], s[x.b]]: - ## + ## ## .. code-block:: nim ## var s = "abcdef" - ## assert s[1..3] == "bcd" + ## assert s[1..3] == "bcd" result = s.substr(x.a, x.b) proc `[]=`*(s: var string, x: Slice[int], b: string) = @@ -3427,7 +3437,7 @@ when hasAlloc or defined(nimscript): proc `[]`*[Idx, T](a: array[Idx, T], x: Slice[int]): seq[T] = ## slice operation for arrays. ## returns the inclusive range [a[x.a], a[x.b]]: - ## + ## ## .. code-block:: nim ## var a = [1,2,3,4] ## assert a[0..2] == @[1,2,3] @@ -3466,7 +3476,7 @@ proc `[]=`*[Idx, T](a: var array[Idx, T], x: Slice[Idx], b: openArray[T]) = proc `[]`*[T](s: seq[T], x: Slice[int]): seq[T] = ## slice operation for sequences. ## returns the inclusive range [s[x.a], s[x.b]]: - ## + ## ## .. code-block:: nim ## var s = @[1,2,3,4] ## assert s[0..2] == @[1,2,3] @@ -3719,7 +3729,9 @@ proc shallow*(s: var string) {.noSideEffect, inline.} = ## purposes. when not defined(JS) and not defined(nimscript): var s = cast[PGenericSeq](s) - s.reserved = s.reserved or seqShallowFlag + # string literals cannot become 'shallow': + if (s.reserved and strlitFlag) == 0: + s.reserved = s.reserved or seqShallowFlag type NimNodeObj = object diff --git a/lib/system/assign.nim b/lib/system/assign.nim index 61c33e51b..115df61a7 100644 --- a/lib/system/assign.nim +++ b/lib/system/assign.nim @@ -63,12 +63,17 @@ proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) = sysAssert(dest != nil, "genericAssignAux 3") unsureAsgnRef(x, newSeq(mt, seq.len)) var dst = cast[ByteAddress](cast[PPointer](dest)[]) - for i in 0..seq.len-1: - genericAssignAux( - cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), - cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +% - GenericSeqSize), - mt.base, shallow) + if ntfNoRefs in mt.base.flags: + copyMem(cast[pointer](dst +% GenericSeqSize), + cast[pointer](cast[ByteAddress](s2) +% GenericSeqSize), + seq.len * mt.base.size) + else: + for i in 0..seq.len-1: + genericAssignAux( + cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), + cast[pointer](cast[ByteAddress](s2) +% i *% mt.base.size +% + GenericSeqSize), + mt.base, shallow) of tyObject: if mt.base != nil: genericAssignAux(dest, src, mt.base, shallow) @@ -89,6 +94,19 @@ proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) = cast[pointer](s +% i*% mt.base.size), mt.base, shallow) of tyRef: unsureAsgnRef(cast[PPointer](dest), cast[PPointer](s)[]) + of tyOptAsRef: + let s2 = cast[PPointer](src)[] + let d = cast[PPointer](dest) + if s2 == nil: + unsureAsgnRef(d, s2) + else: + when declared(usrToCell): + let realType = usrToCell(s2).typ + else: + let realType = if mt.base.kind == tyObject: cast[ptr PNimType](s2)[] + else: mt.base + var z = newObj(realType, realType.base.size) + genericAssignAux(d, addr z, mt.base, shallow) else: copyMem(dest, src, mt.size) # copy raw bits @@ -115,6 +133,7 @@ when false: of tyPtr: k = "ptr" of tyRef: k = "ref" of tyVar: k = "var" + of tyOptAsRef: k = "optref" of tySequence: k = "seq" of tyProc: k = "proc" of tyPointer: k = "range" @@ -195,7 +214,7 @@ proc genericReset(dest: pointer, mt: PNimType) = var d = cast[ByteAddress](dest) sysAssert(mt != nil, "genericReset 2") case mt.kind - of tyString, tyRef, tySequence: + of tyString, tyRef, tyOptAsRef, tySequence: unsureAsgnRef(cast[PPointer](dest), nil) of tyTuple: genericResetAux(dest, mt.node) diff --git a/lib/system/channels.nim b/lib/system/channels.nim index 1b90e245f..df6c6d41e 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -144,7 +144,7 @@ proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel, for i in 0..(mt.size div mt.base.size)-1: storeAux(cast[pointer](d +% i*% mt.base.size), cast[pointer](s +% i*% mt.base.size), mt.base, t, mode) - of tyRef: + of tyRef, tyOptAsRef: var s = cast[PPointer](src)[] var x = cast[PPointer](dest) if s == nil: diff --git a/lib/system/deepcopy.nim b/lib/system/deepcopy.nim index 65ba2278c..51e138e5e 100644 --- a/lib/system/deepcopy.nim +++ b/lib/system/deepcopy.nim @@ -124,7 +124,7 @@ proc genericDeepCopyAux(dest, src: pointer, mt: PNimType; tab: var PtrTable) = for i in 0..(mt.size div mt.base.size)-1: genericDeepCopyAux(cast[pointer](d +% i*% mt.base.size), cast[pointer](s +% i*% mt.base.size), mt.base, tab) - of tyRef: + of tyRef, tyOptAsRef: let s2 = cast[PPointer](src)[] if s2 == nil: unsureAsgnRef(cast[PPointer](dest), s2) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 80aa5cf1b..a2ff72a30 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -349,7 +349,7 @@ proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: WalkOp) {.benign.} = for i in 0..n.len-1: # inlined for speed if n.sons[i].kind == nkSlot: - if n.sons[i].typ.kind in {tyRef, tyString, tySequence}: + if n.sons[i].typ.kind in {tyRef, tyOptAsRef, tyString, tySequence}: doOperation(cast[PPointer](d +% n.sons[i].offset)[], op) else: forAllChildrenAux(cast[pointer](d +% n.sons[i].offset), @@ -366,7 +366,7 @@ proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) = if dest == nil: return # nothing to do if ntfNoRefs notin mt.flags: case mt.kind - of tyRef, tyString, tySequence: # leaf: + of tyRef, tyOptAsRef, tyString, tySequence: # leaf: doOperation(cast[PPointer](d)[], op) of tyObject, tyTuple: forAllSlotsAux(dest, mt.node, op) @@ -379,13 +379,13 @@ proc forAllChildren(cell: PCell, op: WalkOp) = gcAssert(cell != nil, "forAllChildren: 1") gcAssert(isAllocatedPtr(gch.region, cell), "forAllChildren: 2") gcAssert(cell.typ != nil, "forAllChildren: 3") - gcAssert cell.typ.kind in {tyRef, tySequence, tyString}, "forAllChildren: 4" + gcAssert cell.typ.kind in {tyRef, tyOptAsRef, tySequence, tyString}, "forAllChildren: 4" let marker = cell.typ.marker if marker != nil: marker(cellToUsr(cell), op.int) else: case cell.typ.kind - of tyRef: # common case + of tyRef, tyOptAsRef: # common case forAllChildrenAux(cellToUsr(cell), cell.typ.base, op) of tySequence: var d = cast[ByteAddress](cellToUsr(cell)) @@ -461,7 +461,7 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = incTypeSize typ, size sysAssert(allocInv(gch.region), "rawNewObj begin") acquire(gch) - gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") + gcAssert(typ.kind in {tyRef, tyOptAsRef, tyString, tySequence}, "newObj: 1") collectCT(gch) var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell))) #gcAssert typ.kind in {tyString, tySequence} or size >= typ.base.size, "size too small" @@ -509,7 +509,7 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = incTypeSize typ, size sysAssert(allocInv(gch.region), "newObjRC1 begin") acquire(gch) - gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") + gcAssert(typ.kind in {tyRef, tyOptAsRef, tyString, tySequence}, "newObj: 1") collectCT(gch) sysAssert(allocInv(gch.region), "newObjRC1 after collectCT") @@ -945,10 +945,10 @@ when not defined(useNimRtl): "[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" & "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) & "\n" when nimCoroutines: - result = result & "[GC] number of stacks: " & $gch.stack.len & "\n" + result.add "[GC] number of stacks: " & $gch.stack.len & "\n" for stack in items(gch.stack): - result = result & "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & cast[pointer](stack.maxStackSize).repr & "\n" + result.add "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & cast[pointer](stack.maxStackSize).repr & "\n" else: - result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" + result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" {.pop.} # profiler: off, stackTrace: off diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim index 083c06fe3..6dffc323e 100644 --- a/lib/system/gc2.nim +++ b/lib/system/gc2.nim @@ -1,7 +1,7 @@ # # # Nim's Runtime Library -# (c) Copyright 2015 Andreas Rumpf +# (c) Copyright 2017 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -9,17 +9,19 @@ # Garbage Collector # -# The basic algorithm is *Deferred Reference Counting* with an incremental mark +# The basic algorithm is an incremental mark # and sweep GC to free cycles. It is hard realtime in that if you play # according to its rules, no deadline will ever be missed. - -# XXX Ensure by smart color masking that the object is not in the ZCT. +# Since this kind of collector is very bad at recycling dead objects +# early, Nim's codegen emits ``nimEscape`` calls at strategic +# places. For this to work even 'unsureAsgnRef' needs to mark things +# so that only return values need to be considered in ``nimEscape``. {.push profiler:off.} const CycleIncrease = 2 # is a multiplicative increase - InitialCycleThreshold = 4*1024*1024 # X MB because cycle checking is slow + InitialCycleThreshold = 512*1024 # start collecting after 500KB ZctThreshold = 500 # we collect garbage if the ZCT's size # reaches this threshold # this seems to be a good value @@ -40,13 +42,11 @@ type iterToProc(allObjects, ptr ObjectSpaceIter, allObjectsAsProc) const - rcIncrement = 0b1000 # so that lowest 3 bits are not touched + escapedBit = 0b1000 # so that lowest 3 bits are not touched rcBlackOrig = 0b000 rcWhiteOrig = 0b001 rcGrey = 0b010 # traditional color for incremental mark&sweep rcUnused = 0b011 - ZctFlag = 0b100 # in ZCT - rcShift = 3 # shift by rcShift to get the reference counter colorMask = 0b011 type WalkOp = enum @@ -63,13 +63,13 @@ type GcStat = object stackScans: int # number of performed stack scans (for statistics) - cycleCollections: int # number of performed full collections + completedCollections: int # number of performed full collections maxThreshold: int # max threshold that has been set maxStackSize: int # max stack size maxStackCells: int # max stack cells in ``decStack`` cycleTableSize: int # max entries in cycle table maxPause: int64 # max measured GC pause in nanoseconds - + GcStack {.final, pure.} = object when nimCoroutines: prev: ptr GcStack @@ -93,15 +93,13 @@ type cycleThreshold: int when useCellIds: idGenerator: int - zct: CellSeq # the zero count table - decStack: CellSeq # cells in the stack that are to decref again greyStack: CellSeq recGcLock: int # prevent recursion via finalizers; no thread lock when withRealTime: maxPause: Nanos # max allowed pause in nanoseconds; active if > 0 region: MemRegion # garbage collected region stat: GcStat - additionalRoots: CellSeq # dummy roots for GC_ref/unref + additionalRoots: CellSeq # explicit roots for GC_ref/unref spaceIter: ObjectSpaceIter pDumpHeapFile: pointer # File that is used for GC_dumpHeap when hasThreadSupport: @@ -113,19 +111,25 @@ var when not defined(useNimRtl): instantiateForRegion(gch.region) +template acquire(gch: GcHeap) = + when hasThreadSupport and hasSharedHeap: + acquireSys(HeapLock) + +template release(gch: GcHeap) = + when hasThreadSupport and hasSharedHeap: + releaseSys(HeapLock) + proc initGC() = when not defined(useNimRtl): gch.red = (1-gch.black) gch.cycleThreshold = InitialCycleThreshold gch.stat.stackScans = 0 - gch.stat.cycleCollections = 0 + gch.stat.completedCollections = 0 gch.stat.maxThreshold = 0 gch.stat.maxStackSize = 0 gch.stat.maxStackCells = 0 gch.stat.cycleTableSize = 0 # init the rt - init(gch.zct) - init(gch.decStack) init(gch.additionalRoots) init(gch.greyStack) when hasThreadSupport: @@ -147,11 +151,6 @@ template gcAssert(cond: bool, msg: string) = writeStackTrace() quit 1 -proc addZCT(s: var CellSeq, c: PCell) {.noinline.} = - if (c.refcount and ZctFlag) == 0: - c.refcount = c.refcount or ZctFlag - add(s, c) - proc cellToUsr(cell: PCell): pointer {.inline.} = # convert object (=pointer to refcount) to pointer to userdata result = cast[pointer](cast[ByteAddress](cell)+%ByteAddress(sizeof(Cell))) @@ -168,7 +167,7 @@ proc extGetCellType(c: pointer): PNimType {.compilerproc.} = result = usrToCell(c).typ proc internRefcount(p: pointer): int {.exportc: "getRefcount".} = - result = int(usrToCell(p).refcount) shr rcShift + result = 0 # this that has to equals zero, otherwise we have to round up UnitsPerPage: when BitsPerPage mod (sizeof(int)*8) != 0: @@ -178,6 +177,12 @@ template color(c): expr = c.refCount and colorMask template setColor(c, col) = c.refcount = c.refcount and not colorMask or col +template markAsEscaped(c: PCell) = + c.refcount = c.refcount or escapedBit + +template didEscape(c: PCell): bool = + (c.refCount and escapedBit) != 0 + proc writeCell(file: File; msg: cstring, c: PCell) = var kind = -1 if c.typ != nil: kind = ord(c.typ.kind) @@ -189,18 +194,18 @@ proc writeCell(file: File; msg: cstring, c: PCell) = else: let id = c when leakDetector: - c_fprintf(file, "%s %p %d rc=%ld color=%c from %s(%ld)\n", - msg, id, kind, c.refcount shr rcShift, col, c.filename, c.line) + c_fprintf(file, "%s %p %d escaped=%ld color=%c from %s(%ld)\n", + msg, id, kind, didEscape(c), col, c.filename, c.line) else: - c_fprintf(file, "%s %p %d rc=%ld color=%c\n", - msg, id, kind, c.refcount shr rcShift, col) + c_fprintf(file, "%s %p %d escaped=%ld color=%c\n", + msg, id, kind, didEscape(c), col) proc writeCell(msg: cstring, c: PCell) = stdout.writeCell(msg, c) proc myastToStr[T](x: T): string {.magic: "AstToStr", noSideEffect.} -template gcTrace(cell, state: expr): stmt {.immediate.} = +template gcTrace(cell, state: untyped) = when traceGC: writeCell(myastToStr(state), cell) # forward declarations: @@ -211,52 +216,17 @@ proc doOperation(p: pointer, op: WalkOp) {.benign.} proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) {.benign.} # we need the prototype here for debugging purposes -when hasThreadSupport and hasSharedHeap: - template `--`(x: expr): expr = atomicDec(x, rcIncrement) <% rcIncrement - template `++`(x: expr): stmt = discard atomicInc(x, rcIncrement) -else: - template `--`(x: expr): expr = - dec(x, rcIncrement) - x <% rcIncrement - template `++`(x: expr): stmt = inc(x, rcIncrement) - -proc prepareDealloc(cell: PCell) = - if cell.typ.finalizer != nil: - # the finalizer could invoke something that - # allocates memory; this could trigger a garbage - # collection. Since we are already collecting we - # prevend recursive entering here by a lock. - # XXX: we should set the cell's children to nil! - inc(gch.recGcLock) - (cast[Finalizer](cell.typ.finalizer))(cellToUsr(cell)) - dec(gch.recGcLock) - proc rtlAddCycleRoot(c: PCell) {.rtl, inl.} = # we MUST access gch as a global here, because this crosses DLL boundaries! discard -proc rtlAddZCT(c: PCell) {.rtl, inl.} = - # we MUST access gch as a global here, because this crosses DLL boundaries! - addZCT(gch.zct, c) - -proc decRef(c: PCell) {.inline.} = - gcAssert(isAllocatedPtr(gch.region, c), "decRef: interiorPtr") - gcAssert(c.refcount >=% rcIncrement, "decRef") - if --c.refcount: - rtlAddZCT(c) - -proc incRef(c: PCell) {.inline.} = - gcAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr") - c.refcount = c.refcount +% rcIncrement - proc nimGCref(p: pointer) {.compilerProc.} = let cell = usrToCell(p) - incRef(cell) + markAsEscaped(cell) add(gch.additionalRoots, cell) proc nimGCunref(p: pointer) {.compilerProc.} = let cell = usrToCell(p) - decRef(cell) var L = gch.additionalRoots.len-1 var i = L let d = gch.additionalRoots.d @@ -267,6 +237,12 @@ proc nimGCunref(p: pointer) {.compilerProc.} = break dec(i) +proc nimGCunrefNoCycle(p: pointer) {.compilerProc, inline.} = + discard "can we do some freeing here?" + +proc nimGCunrefRC1(p: pointer) {.compilerProc, inline.} = + discard "can we do some freeing here?" + template markGrey(x: PCell) = if x.color != 1-gch.black and gch.phase == Phase.Marking: if not isAllocatedPtr(gch.region, x): @@ -280,59 +256,32 @@ proc GC_addCycleRoot*[T](p: ref T) {.inline.} = ## adds 'p' to the cycle candidate set for the cycle collector. It is ## necessary if you used the 'acyclic' pragma for optimization ## purposes and need to break cycles manually. - rtlAddCycleRoot(usrToCell(cast[pointer](p))) - -proc nimGCunrefNoCycle(p: pointer) {.compilerProc, inline.} = - sysAssert(allocInv(gch.region), "begin nimGCunrefNoCycle") - var c = usrToCell(p) - gcAssert(isAllocatedPtr(gch.region, c), "nimGCunrefNoCycle: isAllocatedPtr") - if --c.refcount: - rtlAddZCT(c) - sysAssert(allocInv(gch.region), "end nimGCunrefNoCycle 2") - sysAssert(allocInv(gch.region), "end nimGCunrefNoCycle 5") + discard -proc asgnRef(dest: PPointer, src: pointer) {.compilerProc, inline.} = - # the code generator calls this proc! +template asgnRefImpl = gcAssert(not isOnStack(dest), "asgnRef") # BUGFIX: first incRef then decRef! if src != nil: let s = usrToCell(src) - incRef(s) + markAsEscaped(s) markGrey(s) - if dest[] != nil: decRef(usrToCell(dest[])) dest[] = src +proc asgnRef(dest: PPointer, src: pointer) {.compilerProc, inline.} = + # the code generator calls this proc! + asgnRefImpl() + proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerProc, inline.} = - # the code generator calls this proc if it is known at compile time that no - # cycle is possible. - gcAssert(not isOnStack(dest), "asgnRefNoCycle") - if src != nil: - var c = usrToCell(src) - ++c.refcount - markGrey(c) - if dest[] != nil: - var c = usrToCell(dest[]) - if --c.refcount: - rtlAddZCT(c) - dest[] = src + asgnRefImpl() proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerProc.} = - # unsureAsgnRef updates the reference counters only if dest is not on the + # unsureAsgnRef marks 'src' as grey only if dest is not on the # stack. It is used by the code generator if it cannot decide wether a # reference is in the stack or not (this can happen for var parameters). - if not isOnStack(dest): - if src != nil: - let s = usrToCell(src) - incRef(s) - markGrey(s) - # XXX finally use assembler for the stack checking instead! - # the test for '!= nil' is correct, but I got tired of the segfaults - # resulting from the crappy stack checking: - if cast[int](dest[]) >=% PageSize: decRef(usrToCell(dest[])) - else: - # can't be an interior pointer if it's a stack location! - gcAssert(interiorAllocatedPtr(gch.region, dest) == nil, - "stack loc AND interior pointer") + if src != nil: + let s = usrToCell(src) + markAsEscaped(s) + if not isOnStack(dest): markGrey(s) dest[] = src type @@ -366,7 +315,7 @@ proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) = if dest == nil: return # nothing to do if ntfNoRefs notin mt.flags: case mt.kind - of tyRef, tyString, tySequence: # leaf: + of tyRef, tyOptAsRef, tyString, tySequence: # leaf: doOperation(cast[PPointer](d)[], op) of tyObject, tyTuple: forAllSlotsAux(dest, mt.node, op) @@ -379,13 +328,13 @@ proc forAllChildren(cell: PCell, op: WalkOp) = gcAssert(cell != nil, "forAllChildren: 1") gcAssert(isAllocatedPtr(gch.region, cell), "forAllChildren: 2") gcAssert(cell.typ != nil, "forAllChildren: 3") - gcAssert cell.typ.kind in {tyRef, tySequence, tyString}, "forAllChildren: 4" + gcAssert cell.typ.kind in {tyRef, tyOptAsRef, tySequence, tyString}, "forAllChildren: 4" let marker = cell.typ.marker if marker != nil: marker(cellToUsr(cell), op.int) else: case cell.typ.kind - of tyRef: # common case + of tyRef, tyOptAsRef: # common case forAllChildrenAux(cellToUsr(cell), cell.typ.base, op) of tySequence: var d = cast[ByteAddress](cellToUsr(cell)) @@ -396,50 +345,6 @@ proc forAllChildren(cell: PCell, op: WalkOp) = GenericSeqSize), cell.typ.base, op) else: discard -proc addNewObjToZCT(res: PCell, gch: var GcHeap) {.inline.} = - # we check the last 8 entries (cache line) for a slot that could be reused. - # In 63% of all cases we succeed here! But we have to optimize the heck - # out of this small linear search so that ``newObj`` is not slowed down. - # - # Slots to try cache hit - # 1 32% - # 4 59% - # 8 63% - # 16 66% - # all slots 68% - var L = gch.zct.len - var d = gch.zct.d - when true: - # loop unrolled for performance: - template replaceZctEntry(i: expr) = - c = d[i] - if c.refcount >=% rcIncrement: - c.refcount = c.refcount and not ZctFlag - d[i] = res - return - if L > 8: - var c: PCell - replaceZctEntry(L-1) - replaceZctEntry(L-2) - replaceZctEntry(L-3) - replaceZctEntry(L-4) - replaceZctEntry(L-5) - replaceZctEntry(L-6) - replaceZctEntry(L-7) - replaceZctEntry(L-8) - add(gch.zct, res) - else: - d[L] = res - inc(gch.zct.len) - else: - for i in countdown(L-1, max(0, L-8)): - var c = d[i] - if c.refcount >=% rcIncrement: - c.refcount = c.refcount and not ZctFlag - d[i] = res - return - add(gch.zct, res) - {.push stackTrace: off, profiler:off.} proc gcInvariant*() = sysAssert(allocInv(gch.region), "injected") @@ -447,10 +352,12 @@ proc gcInvariant*() = markForDebug(gch) {.pop.} +include gc_common + proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = # generates a new object and sets its reference counter to 0 sysAssert(allocInv(gch.region), "rawNewObj begin") - gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") + gcAssert(typ.kind in {tyRef, tyOptAsRef, tyString, tySequence}, "newObj: 1") collectCT(gch) var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell))) gcAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2") @@ -461,10 +368,8 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = res.filename = framePtr.prev.filename res.line = framePtr.prev.line # refcount is zero, color is black, but mark it to be in the ZCT - res.refcount = ZctFlag or allocColor() + res.refcount = allocColor() sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") - # its refcount is zero, so add it to the ZCT: - addNewObjToZCT(res, gch) when logGC: writeCell("new cell", res) gcTrace(res, csAllocated) when useCellIds: @@ -493,95 +398,38 @@ proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = when defined(memProfiler): nimProfile(size) proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = - # generates a new object and sets its reference counter to 1 - sysAssert(allocInv(gch.region), "newObjRC1 begin") - gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") - collectCT(gch) - sysAssert(allocInv(gch.region), "newObjRC1 after collectCT") - - var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell))) - sysAssert(allocInv(gch.region), "newObjRC1 after rawAlloc") - sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2") - # now it is buffered in the ZCT - res.typ = typ - when leakDetector: - if framePtr != nil and framePtr.prev != nil: - res.filename = framePtr.prev.filename - res.line = framePtr.prev.line - res.refcount = rcIncrement or allocColor() # refcount is 1 - sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") - when logGC: writeCell("new cell", res) - gcTrace(res, csAllocated) - when useCellIds: - inc gch.idGenerator - res.id = gch.idGenerator - result = cellToUsr(res) - zeroMem(result, size) - sysAssert(allocInv(gch.region), "newObjRC1 end") - when defined(memProfiler): nimProfile(size) + result = newObj(typ, size) proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = - let size = addInt(mulInt(len, typ.base.size), GenericSeqSize) - result = newObjRC1(typ, size) - cast[PGenericSeq](result).len = len - cast[PGenericSeq](result).reserved = len - when defined(memProfiler): nimProfile(size) + result = newSeq(typ, len) proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = + acquire(gch) collectCT(gch) var ol = usrToCell(old) - gcAssert(isAllocatedPtr(gch.region, ol), "growObj: freed pointer?") - sysAssert(ol.typ != nil, "growObj: 1") gcAssert(ol.typ.kind in {tyString, tySequence}, "growObj: 2") - sysAssert(allocInv(gch.region), "growObj begin") var res = cast[PCell](rawAlloc(gch.region, newsize + sizeof(Cell))) var elemSize = 1 if ol.typ.kind != tyString: elemSize = ol.typ.base.size + incTypeSize ol.typ, newsize - let oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize + var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize copyMem(res, ol, oldsize + sizeof(Cell)) - zeroMem(cast[pointer](cast[ByteAddress](res) +% oldsize +% sizeof(Cell)), + zeroMem(cast[pointer](cast[ByteAddress](res)+% oldsize +% sizeof(Cell)), newsize-oldsize) sysAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "growObj: 3") - # This can be wrong for intermediate temps that are nevertheless on the - # heap because of lambda lifting: - #gcAssert(res.refcount shr rcShift <=% 1, "growObj: 4") - when logGC: - writeCell("growObj old cell", ol) - writeCell("growObj new cell", res) - gcTrace(ol, csZctFreed) - gcTrace(res, csAllocated) - when reallyDealloc: - sysAssert(allocInv(gch.region), "growObj before dealloc") - if ol.refcount shr rcShift <=% 1: - # free immediately to save space: - if (ol.refcount and ZctFlag) != 0: - var j = gch.zct.len-1 - var d = gch.zct.d - while j >= 0: - if d[j] == ol: - d[j] = res - break - dec(j) - rawDealloc(gch.region, ol) + when false: + # this is wrong since seqs can be shared via 'shallow': + when reallyDealloc: rawDealloc(gch.region, ol) else: - # we split the old refcount in 2 parts. XXX This is still not entirely - # correct if the pointer that receives growObj's result is on the stack. - # A better fix would be to emit the location specific write barrier for - # 'growObj', but this is lots of more work and who knows what new problems - # this would create. - res.refcount = rcIncrement or allocColor() - decRef(ol) - else: - sysAssert(ol.typ != nil, "growObj: 5") - zeroMem(ol, sizeof(Cell)) + zeroMem(ol, sizeof(Cell)) when useCellIds: inc gch.idGenerator res.id = gch.idGenerator + release(gch) result = cellToUsr(res) - sysAssert(allocInv(gch.region), "growObj end") when defined(memProfiler): nimProfile(newsize-oldsize) proc growObj(old: pointer, newsize: int): pointer {.rtl.} = @@ -637,12 +485,13 @@ proc GC_dumpHeap*(file: File) = ## can be translated into "dot" syntax via the "heapdump2dot" tool. gch.pDumpHeapFile = file var spaceIter: ObjectSpaceIter - var d = gch.decStack.d - for i in 0 .. < gch.decStack.len: - if isAllocatedPtr(gch.region, d[i]): - c_fprintf(file, "onstack %p\n", d[i]) - else: - c_fprintf(file, "onstack_invalid %p\n", d[i]) + when false: + var d = gch.decStack.d + for i in 0 .. < gch.decStack.len: + if isAllocatedPtr(gch.region, d[i]): + c_fprintf(file, "onstack %p\n", d[i]) + else: + c_fprintf(file, "onstack_invalid %p\n", d[i]) for i in 0 .. < globalMarkersLen: globalMarkers[i]() while true: let x = allObjectsAsProc(gch.region, addr spaceIter) @@ -667,14 +516,6 @@ proc GC_dumpHeap() = proc freeCyclicCell(gch: var GcHeap, c: PCell) = gcAssert(isAllocatedPtr(gch.region, c), "freeCyclicCell: freed pointer?") - - var d = gch.decStack.d - for i in 0..gch.decStack.len-1: - if d[i] == c: - writeCell("freeing ", c) - GC_dumpHeap() - gcAssert d[i] != c, "wtf man, freeing obviously alive stuff?!!" - prepareDealloc(c) gcTrace(c, csCycFreed) when logGC: writeCell("cycle collector dealloc cell", c) @@ -713,15 +554,6 @@ proc markRoot(gch: var GcHeap, c: PCell) {.inline.} = if c.color == 1-gch.black: c.setColor(rcGrey) add(gch.greyStack, c) - elif c.color == rcGrey: - var isGrey = false - var d = gch.decStack.d - for i in 0..gch.decStack.len-1: - if d[i] == c: - isGrey = true - break - if not isGrey: - gcAssert false, "markRoot: root is already grey?!" proc markIncremental(gch: var GcHeap): bool = var L = addr(gch.greyStack.len) @@ -741,30 +573,14 @@ proc markIncremental(gch: var GcHeap): bool = c.setColor(gch.black) forAllChildren(c, waMarkGrey) elif c.color == (1-gch.black): - gcAssert false, "wtf why are there white object in the greystack?" + gcAssert false, "wtf why are there white objects in the greystack?" checkTime() gcAssert gch.greyStack.len == 0, "markIncremental: greystack not empty " - - # assert that all local roots are black by now: - var d = gch.decStack.d - var errors = false - for i in 0..gch.decStack.len-1: - gcAssert(isAllocatedPtr(gch.region, d[i]), "markIncremental: isAllocatedPtr 2") - if d[i].color != gch.black: - writeCell("not black ", d[i]) - errors = true - gcAssert(not errors, "wtf something wrong hre") result = true proc markGlobals(gch: var GcHeap) = for i in 0 .. < globalMarkersLen: globalMarkers[i]() -proc markLocals(gch: var GcHeap) = - var d = gch.decStack.d - for i in 0 .. < gch.decStack.len: - sysAssert isAllocatedPtr(gch.region, d[i]), "markLocals" - markRoot(gch, d[i]) - proc doOperation(p: pointer, op: WalkOp) = if p == nil: return var c: PCell = usrToCell(p) @@ -776,11 +592,7 @@ proc doOperation(p: pointer, op: WalkOp) = #if not isAllocatedPtr(gch.region, c): # c_fprintf(stdout, "[GC] decref bug: %p", c) gcAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef") - gcAssert(c.refcount >=% rcIncrement, "doOperation 2") - #c.refcount = c.refcount -% rcIncrement - when logGC: writeCell("decref (from doOperation)", c) - decRef(c) - #if c.refcount <% rcIncrement: addZCT(gch.zct, c) + discard "use me for nimEscape?" of waMarkGlobal: template handleRoot = if gch.dumpHeapFile.isNil: @@ -811,107 +623,54 @@ proc doOperation(p: pointer, op: WalkOp) = proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = doOperation(d, WalkOp(op)) -proc collectZCT(gch: var GcHeap): bool {.benign.} - -proc collectCycles(gch: var GcHeap): bool = - when hasThreadSupport: - for c in gch.toDispose: - nimGCunref(c) +proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = + # the addresses are not as cells on the stack, so turn them to cells: + sysAssert(allocInv(gch.region), "gcMark begin") + var cell = usrToCell(p) + var c = cast[ByteAddress](cell) + if c >% PageSize: + # fast check: does it look like a cell? + var objStart = cast[PCell](interiorAllocatedPtr(gch.region, cell)) + if objStart != nil: + # mark the cell: + markRoot(gch, objStart) + sysAssert(allocInv(gch.region), "gcMark end") - # ensure the ZCT 'color' is not used: - while gch.zct.len > 0: discard collectZCT(gch) +proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = + forEachStackSlot(gch, gcMark) +proc collectALittle(gch: var GcHeap): bool = case gch.phase of Phase.None: - gch.phase = Phase.Marking - markGlobals(gch) - - c_fprintf(stdout, "collectCycles: introduced bug E %ld\n", gch.phase) - discard allocInv(gch.region) + if getOccupiedMem(gch.region) >= gch.cycleThreshold: + gch.phase = Phase.Marking + markGlobals(gch) + result = collectALittle(gch) + #when false: c_fprintf(stdout, "collectALittle: introduced bug E %ld\n", gch.phase) + #discard allocInv(gch.region) of Phase.Marking: - # since locals do not have a write barrier, we need - # to keep re-scanning them :-( but there is really nothing we can - # do about that. - markLocals(gch) + when hasThreadSupport: + for c in gch.toDispose: + nimGCunref(c) + prepareForInteriorPointerChecking(gch.region) + markStackAndRegisters(gch) + inc(gch.stat.stackScans) if markIncremental(gch): gch.phase = Phase.Sweeping gch.red = 1 - gch.red of Phase.Sweeping: gcAssert gch.greyStack.len == 0, "greystack not empty" + when hasThreadSupport: + for c in gch.toDispose: + nimGCunref(c) if sweep(gch): gch.phase = Phase.None # flip black/white meanings: gch.black = 1 - gch.black gcAssert gch.red == 1 - gch.black, "red color is wrong" + inc(gch.stat.completedCollections) result = true -proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = - # the addresses are not as cells on the stack, so turn them to cells: - sysAssert(allocInv(gch.region), "gcMark begin") - var cell = usrToCell(p) - var c = cast[ByteAddress](cell) - if c >% PageSize: - # fast check: does it look like a cell? - var objStart = cast[PCell](interiorAllocatedPtr(gch.region, cell)) - if objStart != nil: - # mark the cell: - objStart.refcount = objStart.refcount +% rcIncrement - add(gch.decStack, objStart) - sysAssert(allocInv(gch.region), "gcMark end") - -include gc_common - -proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = - forEachStackSlot(gch, gcMark) - -proc collectZCT(gch: var GcHeap): bool = - # Note: Freeing may add child objects to the ZCT! So essentially we do - # deep freeing, which is bad for incremental operation. In order to - # avoid a deep stack, we move objects to keep the ZCT small. - # This is performance critical! - var L = addr(gch.zct.len) - takeStartTime(100) - - while L[] > 0: - var c = gch.zct.d[0] - sysAssert(isAllocatedPtr(gch.region, c), "CollectZCT: isAllocatedPtr") - # remove from ZCT: - gcAssert((c.refcount and ZctFlag) == ZctFlag, "collectZCT") - - c.refcount = c.refcount and not ZctFlag - gch.zct.d[0] = gch.zct.d[L[] - 1] - dec(L[]) - takeTime() - if c.refcount <% rcIncrement and c.color != rcGrey: - # It may have a RC > 0, if it is in the hardware stack or - # it has not been removed yet from the ZCT. This is because - # ``incref`` does not bother to remove the cell from the ZCT - # as this might be too slow. - # In any case, it should be removed from the ZCT. But not - # freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!** - when logGC: writeCell("zct dealloc cell", c) - gcTrace(c, csZctFreed) - # We are about to free the object, call the finalizer BEFORE its - # children are deleted as well, because otherwise the finalizer may - # access invalid memory. This is done by prepareDealloc(): - prepareDealloc(c) - forAllChildren(c, waZctDecRef) - when reallyDealloc: - sysAssert(allocInv(gch.region), "collectZCT: rawDealloc") - rawDealloc(gch.region, c) - else: - sysAssert(c.typ != nil, "collectZCT 2") - zeroMem(c, sizeof(Cell)) - checkTime() - result = true - -proc unmarkStackAndRegisters(gch: var GcHeap) = - var d = gch.decStack.d - for i in 0..gch.decStack.len-1: - sysAssert isAllocatedPtr(gch.region, d[i]), "unmarkStackAndRegisters" - decRef(d[i]) - gch.decStack.len = 0 - proc collectCTBody(gch: var GcHeap) = when withRealTime: let t0 = getticks() @@ -919,22 +678,12 @@ proc collectCTBody(gch: var GcHeap) = when not nimCoroutines: gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize()) - sysAssert(gch.decStack.len == 0, "collectCT") - prepareForInteriorPointerChecking(gch.region) - markStackAndRegisters(gch) - gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len) - inc(gch.stat.stackScans) - if collectZCT(gch): - when cycleGC: - if getOccupiedMem(gch.region) >= gch.cycleThreshold or alwaysCycleGC: - if collectCycles(gch): - inc(gch.stat.cycleCollections) - gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() * - CycleIncrease) - gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold) - unmarkStackAndRegisters(gch) + #gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len) + if collectALittle(gch): + gch.cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() * + CycleIncrease) + gch.stat.maxThreshold = max(gch.stat.maxThreshold, gch.cycleThreshold) sysAssert(allocInv(gch.region), "collectCT: end") - when withRealTime: let duration = getticks() - t0 gch.stat.maxPause = max(gch.stat.maxPause, duration) @@ -955,7 +704,7 @@ proc collectCT(gch: var GcHeap) = let stackMarkCosts = max(currentStackSizes() div (16*sizeof(int)), ZctThreshold) else: let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold) - if (gch.zct.len >= stackMarkCosts or (cycleGC and + if (gch.greyStack.len >= stackMarkCosts or (cycleGC and getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and gch.recGcLock == 0: collectCTBody(gch) @@ -969,10 +718,9 @@ when withRealTime: proc GC_step(gch: var GcHeap, us: int, strongAdvice: bool) = gch.maxPause = us.toNano - if (gch.zct.len >= ZctThreshold or (cycleGC and - getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) or - strongAdvice: - collectCTBody(gch) + #if (getOccupiedMem(gch.region)>=gch.cycleThreshold) or + # alwaysGC or strongAdvice: + collectCTBody(gch) proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} = if stackSize >= 0: @@ -1010,12 +758,8 @@ when not defined(useNimRtl): proc GC_setStrategy(strategy: GC_Strategy) = discard - proc GC_enableMarkAndSweep() = - gch.cycleThreshold = InitialCycleThreshold - - proc GC_disableMarkAndSweep() = - gch.cycleThreshold = high(gch.cycleThreshold)-1 - # set to the max value to suppress the cycle detector + proc GC_enableMarkAndSweep() = discard + proc GC_disableMarkAndSweep() = discard proc GC_fullCollect() = var oldThreshold = gch.cycleThreshold @@ -1029,17 +773,17 @@ when not defined(useNimRtl): "[GC] occupied memory: " & $(getOccupiedMem()) & "\n" & "[GC] stack scans: " & $gch.stat.stackScans & "\n" & "[GC] stack cells: " & $gch.stat.maxStackCells & "\n" & - "[GC] cycle collections: " & $gch.stat.cycleCollections & "\n" & + "[GC] completed collections: " & $gch.stat.completedCollections & "\n" & "[GC] max threshold: " & $gch.stat.maxThreshold & "\n" & - "[GC] zct capacity: " & $gch.zct.cap & "\n" & + "[GC] grey stack capacity: " & $gch.greyStack.cap & "\n" & "[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" & - "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) + "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) & "\n" when nimCoroutines: - result = result & "[GC] number of stacks: " & $gch.stack.len & "\n" + result.add "[GC] number of stacks: " & $gch.stack.len & "\n" for stack in items(gch.stack): - result = result & "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & $stack.maxStackSize & "\n" + result.add "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & $stack.maxStackSize & "\n" else: - result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" + result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" GC_enable() {.pop.} diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index 220331e96..484a4db9a 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -373,12 +373,22 @@ proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) = ## is true. If ``allowGcAfterwards`` is true, a minimal amount of allocation ## happens to ensure the GC can continue to work after the call ## to ``deallocHeap``. + template deallocCell(x) = + if isCell(x): + # cast to PCell is correct here: + prepareDealloc(cast[PCell](x)) + if runFinalizers: - for x in allObjects(gch.region): - if isCell(x): - # cast to PCell is correct here: - var c = cast[PCell](x) - prepareDealloc(c) + when not declared(allObjectsAsProc): + for x in allObjects(gch.region): + deallocCell(x) + else: + var spaceIter: ObjectSpaceIter + while true: + let x = allObjectsAsProc(gch.region, addr spaceIter) + if spaceIter.state < 0: break + deallocCell(x) + deallocOsPages(gch.region) zeroMem(addr gch.region, sizeof(gch.region)) if allowGcAfterwards: diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index e03140d05..cfc0dfa8a 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -252,7 +252,7 @@ proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) = if dest == nil: return # nothing to do if ntfNoRefs notin mt.flags: case mt.kind - of tyRef, tyString, tySequence: # leaf: + of tyRef, tyOptAsRef, tyString, tySequence: # leaf: doOperation(cast[PPointer](d)[], op) of tyObject, tyTuple: forAllSlotsAux(dest, mt.node, op) @@ -264,13 +264,13 @@ proc forAllChildrenAux(dest: pointer, mt: PNimType, op: WalkOp) = proc forAllChildren(cell: PCell, op: WalkOp) = gcAssert(cell != nil, "forAllChildren: 1") gcAssert(cell.typ != nil, "forAllChildren: 2") - gcAssert cell.typ.kind in {tyRef, tySequence, tyString}, "forAllChildren: 3" + gcAssert cell.typ.kind in {tyRef, tyOptAsRef, tySequence, tyString}, "forAllChildren: 3" let marker = cell.typ.marker if marker != nil: marker(cellToUsr(cell), op.int) else: case cell.typ.kind - of tyRef: # common case + of tyRef, tyOptAsRef: # common case forAllChildrenAux(cellToUsr(cell), cell.typ.base, op) of tySequence: var d = cast[ByteAddress](cellToUsr(cell)) @@ -285,7 +285,7 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = # generates a new object and sets its reference counter to 0 incTypeSize typ, size acquire(gch) - gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") + gcAssert(typ.kind in {tyRef, tyOptAsRef, tyString, tySequence}, "newObj: 1") collectCT(gch) var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell))) gcAssert((cast[ByteAddress](res) and (MemAlign-1)) == 0, "newObj: 2") @@ -526,10 +526,10 @@ when not defined(useNimRtl): "[GC] max threshold: " & $gch.stat.maxThreshold & "\n" & "[GC] freed objects: " & $gch.stat.freedObjects & "\n" when nimCoroutines: - result = result & "[GC] number of stacks: " & $gch.stack.len & "\n" + result.add "[GC] number of stacks: " & $gch.stack.len & "\n" for stack in items(gch.stack): - result = result & "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & $stack.maxStackSize & "\n" + result.add "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & $stack.maxStackSize & "\n" else: - result = result & "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" + result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" {.pop.} diff --git a/lib/system/hti.nim b/lib/system/hti.nim index 69f4f9508..45b1d1cd3 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -62,6 +62,21 @@ type tyUInt16, tyUInt32, tyUInt64, + tyOptAsRef, tyUnused1, tyUnused2, + tyVarargsHidden, + tyUnusedHidden, + tyProxyHidden, + tyBuiltInTypeClassHidden, + tyUserTypeClassHidden, + tyUserTypeClassInstHidden, + tyCompositeTypeClassHidden, + tyInferredHidden, + tyAndHidden, tyOrHidden, tyNotHidden, + tyAnythingHidden, + tyStaticHidden, + tyFromExprHidden, + tyOpt, + tyVoidHidden TNimNodeKind = enum nkNone, nkSlot, nkList, nkCase TNimNode {.codegenType.} = object diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index d2160fdac..824934966 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -564,7 +564,11 @@ else: when not declared(nimNewSeqOfCap): proc nimNewSeqOfCap(typ: PNimType, cap: int): pointer {.compilerproc.} = - result = newObj(typ, addInt(mulInt(cap, typ.base.size), GenericSeqSize)) + let s = addInt(mulInt(cap, typ.base.size), GenericSeqSize) + when declared(newObjNoInit): + result = if ntfNoRefs in typ.base.flags: newObjNoInit(typ, s) else: newObj(typ, s) + else: + result = newObj(typ, s) cast[PGenericSeq](result).len = 0 cast[PGenericSeq](result).reserved = cap diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index 73bb91fef..f5b9cf3ed 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -11,6 +11,15 @@ # Nim's configuration system now uses Nim for scripting. This module provides # a few things that are required for this to work. +const + buildOS* {.magic: "BuildOS".}: string = "" + ## The OS this build is running on. Can be different from ``system.hostOS`` + ## for cross compilations. + + buildCPU* {.magic: "BuildCPU".}: string = "" + ## The CPU this build is running on. Can be different from ``system.hostCPU`` + ## for cross compilations. + template builtin = discard # We know the effects better than the compiler: diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index c3150cb07..90201202c 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -95,6 +95,9 @@ proc cstrToNimstr(str: cstring): NimString {.compilerRtl.} = if str == nil: NimString(nil) else: toNimStr(str, str.len) +template wasMoved(x: NimString): bool = false +# (x.reserved and seqShallowFlag) != 0 + proc copyString(src: NimString): NimString {.compilerRtl.} = if src != nil: if (src.reserved and seqShallowFlag) != 0: @@ -103,6 +106,16 @@ proc copyString(src: NimString): NimString {.compilerRtl.} = result = rawNewStringNoInit(src.len) result.len = src.len copyMem(addr(result.data), addr(src.data), src.len + 1) + sysAssert((seqShallowFlag and result.reserved) == 0, "copyString") + when defined(nimShallowStrings): + if (src.reserved and strlitFlag) != 0: + result.reserved = (result.reserved and not strlitFlag) or seqShallowFlag + +proc newOwnedString(src: NimString; n: int): NimString = + result = rawNewStringNoInit(n) + result.len = n + copyMem(addr(result.data), addr(src.data), n) + result.data[n] = '\0' proc copyStringRC1(src: NimString): NimString {.compilerRtl.} = if src != nil: @@ -116,6 +129,10 @@ proc copyStringRC1(src: NimString): NimString {.compilerRtl.} = result = rawNewStringNoInit(src.len) result.len = src.len copyMem(addr(result.data), addr(src.data), src.len + 1) + sysAssert((seqShallowFlag and result.reserved) == 0, "copyStringRC1") + when defined(nimShallowStrings): + if (src.reserved and strlitFlag) != 0: + result.reserved = (result.reserved and not strlitFlag) or seqShallowFlag proc copyDeepString(src: NimString): NimString {.inline.} = if src != nil: @@ -140,9 +157,12 @@ proc addChar(s: NimString, c: char): NimString = # is compilerproc! result = s if result.len >= result.space: - result.reserved = resize(result.space) + let r = resize(result.space) result = cast[NimString](growObj(result, - sizeof(TGenericSeq) + result.reserved + 1)) + sizeof(TGenericSeq) + r + 1)) + result.reserved = r + elif wasMoved(s): + result = newOwnedString(s, s.len) result.data[result.len] = c result.data[result.len+1] = '\0' inc(result.len) @@ -179,7 +199,7 @@ proc addChar(s: NimString, c: char): NimString = # s = rawNewString(0); proc resizeString(dest: NimString, addlen: int): NimString {.compilerRtl.} = - if dest.len + addlen <= dest.space: + if dest.len + addlen <= dest.space and not wasMoved(dest): result = dest else: # slow path: var sp = max(resize(dest.space), dest.len + addlen) @@ -200,7 +220,9 @@ proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} = proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} = var n = max(newLen, 0) - if n <= s.space: + if wasMoved(s): + result = newOwnedString(s, n) + elif n <= s.space: result = s else: result = resizeString(s, n) @@ -218,26 +240,29 @@ proc incrSeq(seq: PGenericSeq, elemSize: int): PGenericSeq {.compilerProc.} = # seq[seq->len-1] = x; result = seq if result.len >= result.space: - result.reserved = resize(result.space) - result = cast[PGenericSeq](growObj(result, elemSize * result.reserved + + let r = resize(result.space) + result = cast[PGenericSeq](growObj(result, elemSize * r + GenericSeqSize)) + result.reserved = r inc(result.len) proc incrSeqV2(seq: PGenericSeq, elemSize: int): PGenericSeq {.compilerProc.} = # incrSeq version 2 result = seq if result.len >= result.space: - result.reserved = resize(result.space) - result = cast[PGenericSeq](growObj(result, elemSize * result.reserved + + let r = resize(result.space) + result = cast[PGenericSeq](growObj(result, elemSize * r + GenericSeqSize)) + result.reserved = r proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {. compilerRtl.} = result = seq if result.space < newLen: - result.reserved = max(resize(result.space), newLen) - result = cast[PGenericSeq](growObj(result, elemSize * result.reserved + + let r = max(resize(result.space), newLen) + result = cast[PGenericSeq](growObj(result, elemSize * r + GenericSeqSize)) + result.reserved = r elif newLen < result.len: # we need to decref here, otherwise the GC leaks! when not defined(boehmGC) and not defined(nogc) and diff --git a/lib/system/threads.nim b/lib/system/threads.nim index a7a811844..96c045e6b 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -127,7 +127,8 @@ elif defined(genode): proc initThread(s: var SysThread, stackSize: culonglong, entry: GenodeThreadProc, - arg: pointer) {. + arg: pointer, + affinity: cuint) {. importcpp: "#.initThread(genodeEnv, @)".} proc threadVarAlloc(): ThreadVarSlot = 0 @@ -567,6 +568,9 @@ when hostOS == "windows": setThreadAffinityMask(t.sys, uint(1 shl cpu)) elif defined(genode): + var affinityOffset: cuint = 1 + # CPU affinity offset for next thread, safe to roll-over + proc createThread*[TArg](t: var Thread[TArg], tp: proc (arg: TArg) {.thread, nimcall.}, param: TArg) = @@ -577,7 +581,8 @@ elif defined(genode): when hasSharedHeap: t.stackSize = ThreadStackSize t.sys.initThread( ThreadStackSize.culonglong, - threadProcWrapper[TArg], addr(t)) + threadProcWrapper[TArg], addr(t), affinityOffset) + inc affinityOffset proc pinToCpu*[Arg](t: var Thread[Arg]; cpu: Natural) = {.hint: "cannot change Genode thread CPU affinity after initialization".} diff --git a/tests/array/tarraylen.nim b/tests/array/tarraylen.nim new file mode 100644 index 000000000..e9612de58 --- /dev/null +++ b/tests/array/tarraylen.nim @@ -0,0 +1,18 @@ +discard """ + output: "" +""" +var a: array[0, int] +doAssert a.len == 0 +doAssert array[0..0, int].len == 1 +doAssert array[0..0, int]([1]).len == 1 +doAssert array[1..1, int].len == 1 +doAssert array[1..1, int]([1]).len == 1 +doAssert array[2, int].len == 2 +doAssert array[2, int]([1, 2]).len == 2 +doAssert array[1..3, int].len == 3 +doAssert array[1..3, int]([1, 2, 3]).len == 3 +doAssert array[0..2, int].len == 3 +doAssert array[0..2, int]([1, 2, 3]).len == 3 +doAssert array[-2 .. -2, int].len == 1 +doAssert([1, 2, 3].len == 3) +doAssert([42].len == 1) \ No newline at end of file diff --git a/tests/casestmt/tcomputedgoto.nim b/tests/casestmt/tcomputedgoto.nim index f567174af..149072964 100644 --- a/tests/casestmt/tcomputedgoto.nim +++ b/tests/casestmt/tcomputedgoto.nim @@ -18,7 +18,7 @@ type enumA, enumB, enumC, enumD, enumE, enumLast proc vm() = - var instructions: array [0..100, MyEnum] + var instructions: array[0..100, MyEnum] instructions[2] = enumC instructions[3] = enumD instructions[4] = enumA diff --git a/tests/concepts/t5642.nim b/tests/concepts/t5642.nim index d1e7bd1dd..f08b4629b 100644 --- a/tests/concepts/t5642.nim +++ b/tests/concepts/t5642.nim @@ -19,7 +19,7 @@ proc nrow*(dt: DataTable) : Natural = return totalLen let - stud = Students (id : @[1,2,3], name : @["Vas", "Pas", "NafNaf"], age : @[10,16,32]) + stud = Students(id : @[1,2,3], name : @["Vas", "Pas", "NafNaf"], age : @[10,16,32]) echo nrow(stud) diff --git a/tests/constr/tconstr1.nim b/tests/constr/tconstr1.nim index 28431287c..b9cf5d00b 100644 --- a/tests/constr/tconstr1.nim +++ b/tests/constr/tconstr1.nim @@ -14,13 +14,13 @@ type proc testSem = var - things: array [0..1, TComplexRecord] = [ + things: array[0..1, TComplexRecord] = [ (s: "hi", x: 69, y: 45, z: 0.0, chars: {'a', 'b', 'c'}), (s: "hi", x: 69, y: 45, z: 1.0, chars: {'a', 'b', 'c'})] write(stdout, things[0].x) const - things: array [0..1, TComplexRecord] = [ + things: array[0..1, TComplexRecord] = [ (s: "hi", x: 69, y: 45, z: 0.0, chars: {'a', 'b', 'c'}), (s: "hi", x: 69, y: 45, z: 1.0)] #ERROR otherThings = [ # the same diff --git a/tests/constr/tconstr2.nim b/tests/constr/tconstr2.nim index cd00681b8..b16be6c50 100644 --- a/tests/constr/tconstr2.nim +++ b/tests/constr/tconstr2.nim @@ -12,7 +12,7 @@ type chars: set[char]] const - things: array [0..1, TComplexRecord] = [ + things: array[0..1, TComplexRecord] = [ (s: "hi", x: 69, y: 45, z: 0.0, chars: {'a', 'b', 'c'}), (s: "hi", x: 69, y: 45, z: 1.0, chars: {})] otherThings = [ # the same diff --git a/tests/js/tclosures.nim b/tests/js/tclosures.nim index 0ec4f4743..67243c937 100644 --- a/tests/js/tclosures.nim +++ b/tests/js/tclosures.nim @@ -23,29 +23,29 @@ asm """ """ proc consoleprint (str:cstring): void {.importc: "print", noDecl.} -proc print* (a: varargs[string, `$`]) = consoleprint "$1: $2" % [consolePrefix, join (a, " ")] +proc print* (a: varargs[string, `$`]) = consoleprint "$1: $2" % [consolePrefix, join(a, " ")] type CallbackProc {.importc.} = proc () : cstring proc regCallback (fn:CallbackProc) {.importc.} proc runCallbacks ():cstring {.importc.} -proc `*` (s:string, n:Natural) : string = s.repeat (n) +proc `*` (s:string, n:Natural) : string = s.repeat(n) proc outer (i:Natural) : (string, int) = - let c = $char (random (93) + 33) - let n = random (40) + let c = $char(random(93) + 33) + let n = random(40) let s = c * n - proc inner () : cstring = ("[$1]" % $n) & s & " <--" - regCallback (inner) + proc inner(): cstring = ("[$1]" % $n) & s & " <--" + regCallback(inner) return (s, n) var expected = "\n" for i in 1 .. 10: - let (s, n) = outer (i) + let (s, n) = outer(i) expected &= ("($1)[$2]" % [$i, $n]) & s & " <--" expected &= "\n" -let results = runCallbacks () +let results = runCallbacks() doAssert(expected == results) diff --git a/tests/macros/tdebugstmt.nim b/tests/macros/tdebugstmt.nim index 740ae7b05..69e610075 100644 --- a/tests/macros/tdebugstmt.nim +++ b/tests/macros/tdebugstmt.nim @@ -21,7 +21,7 @@ macro debug(n: varargs[untyped]): untyped = add(result, newCall("writeLine", newIdentNode("stdout"), n[i])) var - a: array [0..10, int] + a: array[0..10, int] x = "some string" a[0] = 42 a[1] = 45 diff --git a/tests/macros/tvtable.nim b/tests/macros/tvtable.nim index cc5d7a5d9..dc07a726b 100644 --- a/tests/macros/tvtable.nim +++ b/tests/macros/tvtable.nim @@ -58,10 +58,10 @@ proc create(T: typedesc): T = result.vtbl = getVTable(T) proc baseFoo(o: var TBase): int = - return cast[fooProc[TBase]](o.vtbl[0]) (o) + return cast[fooProc[TBase]](o.vtbl[0])(o) proc baseBar(o: var TBase) = - cast[barProc[TBase]](o.vtbl[1]) (o) + cast[barProc[TBase]](o.vtbl[1])(o) var a = TUserObject1.create var b = TUserObject2.create diff --git a/tests/magics/tlowhigh.nim b/tests/magics/tlowhigh.nim index 4998b73dc..60ed13c99 100644 --- a/tests/magics/tlowhigh.nim +++ b/tests/magics/tlowhigh.nim @@ -8,7 +8,7 @@ type myEnum = enum e1, e2, e3, e4, e5 var - a: array [myEnum, int] + a: array[myEnum, int] for i in low(a) .. high(a): a[i] = 0 diff --git a/tests/method/tnildispatcher.nim b/tests/method/tnildispatcher.nim new file mode 100644 index 000000000..017e8155f --- /dev/null +++ b/tests/method/tnildispatcher.nim @@ -0,0 +1,21 @@ +discard """ + outputsub: '''Error: unhandled exception: cannot dispatch; dispatcher is nil [NilAccessError]''' + exitcode: 1 +""" +# bug #5599 +type + Base = ref object of RootObj + Derived = ref object of Base + +method inner(obj: Base) {.base.} = + quit "to override" + +method outer(obj: Base) {.base.} = + echo "outer" + obj.inner() + +method inner(obj: Derived) = + echo "inner Derived" + +var x: Derived +x.outer() diff --git a/tests/misc/mvarious.nim b/tests/misc/mvarious.nim index d1587faec..8efe7c92f 100644 --- a/tests/misc/mvarious.nim +++ b/tests/misc/mvarious.nim @@ -1,6 +1,6 @@ # Test a submodule #type -# TStringArr = array [0.. *] of string +# TStringArr = array[0.. *] of string proc exportme* = discard diff --git a/tests/misc/tmandelbrot.nim b/tests/misc/tmandelbrot.nim index e9b7a3e5a..504628313 100644 --- a/tests/misc/tmandelbrot.nim +++ b/tests/misc/tmandelbrot.nim @@ -20,38 +20,38 @@ proc `*` (a, b: TComplex): TComplex = proc abs2 (a: TComplex): float = return a.re * a.re + a.im * a.im -var size = parseInt (paramStr (1)) +var size = parseInt(paramStr(1)) var bit = 128 var byteAcc = 0 -stdout.writeLine ("P4") -stdout.write ($size) -stdout.write (" ") -stdout.writeLine ($size) +stdout.writeLine("P4") +stdout.write($size) +stdout.write(" ") +stdout.writeLine($size) -var fsize = float (size) +var fsize = float(size) for y in 0 .. size-1: - var fy = 2.0 * float (y) / fsize - 1.0 + var fy = 2.0 * float(y) / fsize - 1.0 for x in 0 .. size-1: var z = (0.0, 0.0) - var c = (float (2*x) / fsize - 1.5, fy) + var c = (float(2*x) / fsize - 1.5, fy) block iter: for i in 0 .. 49: z = z*z + c - if abs2 (z) >= 4.0: + if abs2(z) >= 4.0: break iter byteAcc = byteAcc + bit if bit > 1: bit = bit div 2 else: - stdout.write (chr (byteAcc)) + stdout.write(chr(byteAcc)) bit = 128 byteAcc = 0 if bit != 128: - stdout.write (chr (byteAcc)) + stdout.write(chr(byteAcc)) bit = 128 byteAcc = 0 diff --git a/tests/misc/tnew.nim b/tests/misc/tnew.nim index 88e8bd02c..89f34a621 100644 --- a/tests/misc/tnew.nim +++ b/tests/misc/tnew.nim @@ -9,7 +9,7 @@ type str: string le, ri: PNode - TStressTest = ref array [0..45, array [1..45, TNode]] + TStressTest = ref array[0..45, array[1..45, TNode]] proc finalizer(n: PNode) = write(stdout, n.data) diff --git a/tests/misc/tradix.nim b/tests/misc/tradix.nim index 36a4f39f6..07674af18 100644 --- a/tests/misc/tradix.nim +++ b/tests/misc/tradix.nim @@ -13,16 +13,16 @@ type kind: TRadixNodeKind TRadixNodeLinear = object of TRadixNode len: int8 - keys: array [0..31, int8] - vals: array [0..31, PRadixNode] + keys: array[0..31, int8] + vals: array[0..31, PRadixNode] TRadixNodeFull = object of TRadixNode - b: array [0..255, PRadixNode] + b: array[0..255, PRadixNode] TRadixNodeLeafBits = object of TRadixNode - b: array [0..7, int] + b: array[0..7, int] TRadixNodeLeafLinear = object of TRadixNode len: int8 - keys: array [0..31, int8] + keys: array[0..31, int8] var root: PRadixNode diff --git a/tests/misc/tstrdesc.nim b/tests/misc/tstrdesc.nim index d23160315..1479fcda8 100644 --- a/tests/misc/tstrdesc.nim +++ b/tests/misc/tstrdesc.nim @@ -1,12 +1,12 @@ var - x: array [0..2, int] + x: array[0..2, int] x = [0, 1, 2] type TStringDesc {.final.} = object len, space: int # len and space without counting the terminating zero - data: array [0..0, char] # for the '\0' character + data: array[0..0, char] # for the '\0' character var emptyString {.exportc: "emptyString".}: TStringDesc diff --git a/tests/niminaction/Chapter3/ChatApp/readme.markdown b/tests/niminaction/Chapter3/ChatApp/readme.markdown new file mode 100644 index 000000000..200b4df1d --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/readme.markdown @@ -0,0 +1,26 @@ +# The ChatApp source code + +This directory contains the ChatApp project, which is the project that is +created as part of Chapter 3 of the Nim in Action book. + +To compile run: + +``` +nim c src/client +nim c src/server +``` + +You can then run the ``server`` in one terminal by executing ``./src/server``. + +After doing so you can execute multiple clients in different terminals and have +them communicate via the server. + +To execute a client, make sure to specify the server address and user name +on the command line: + +```bash +./src/client localhost Peter +``` + +You should then be able to start typing in messages and sending them +by pressing the Enter key. \ No newline at end of file diff --git a/tests/niminaction/Chapter3/ChatApp/src/client.nim b/tests/niminaction/Chapter3/ChatApp/src/client.nim new file mode 100644 index 000000000..4d139655c --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/src/client.nim @@ -0,0 +1,54 @@ +import os, threadpool, asyncdispatch, asyncnet +import protocol + +proc connect(socket: AsyncSocket, serverAddr: string) {.async.} = + ## Connects the specified AsyncSocket to the specified address. + ## Then receives messages from the server continuously. + echo("Connecting to ", serverAddr) + # Pause the execution of this procedure until the socket connects to + # the specified server. + await socket.connect(serverAddr, 7687.Port) + echo("Connected!") + while true: + # Pause the execution of this procedure until a new message is received + # from the server. + let line = await socket.recvLine() + # Parse the received message using ``parseMessage`` defined in the + # protocol module. + let parsed = parseMessage(line) + # Display the message to the user. + echo(parsed.username, " said ", parsed.message) + +echo("Chat application started") +# Ensure that the correct amount of command line arguments was specified. +if paramCount() < 2: + # Terminate the client early with an error message if there was not + # enough command line arguments specified by the user. + quit("Please specify the server address, e.g. ./client localhost username") + +# Retrieve the first command line argument. +let serverAddr = paramStr(1) +# Retrieve the second command line argument. +let username = paramStr(2) +# Initialise a new asynchronous socket. +var socket = newAsyncSocket() + +# Execute the ``connect`` procedure in the background asynchronously. +asyncCheck connect(socket, serverAddr) +# Execute the ``readInput`` procedure in the background in a new thread. +var messageFlowVar = spawn stdin.readLine() +while true: + # Check if the ``readInput`` procedure returned a new line of input. + if messageFlowVar.isReady(): + # If a new line of input was returned, we can safely retrieve it + # without blocking. + # The ``createMessage`` is then used to create a message based on the + # line of input. The message is then sent in the background asynchronously. + asyncCheck socket.send(createMessage(username, ^messageFlowVar)) + # Execute the ``readInput`` procedure again, in the background in a + # new thread. + messageFlowVar = spawn stdin.readLine() + + # Execute the asyncdispatch event loop, to continue the execution of + # asynchronous procedures. + asyncdispatch.poll() diff --git a/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg b/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg new file mode 100644 index 000000000..aed303eef --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/src/client.nim.cfg @@ -0,0 +1 @@ +--threads:on diff --git a/tests/niminaction/Chapter3/ChatApp/src/protocol.nim b/tests/niminaction/Chapter3/ChatApp/src/protocol.nim new file mode 100644 index 000000000..af515861c --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/src/protocol.nim @@ -0,0 +1,55 @@ +import json + +type + Message* = object + username*: string + message*: string + + MessageParsingError* = object of Exception + +proc parseMessage*(data: string): Message {.raises: [MessageParsingError, KeyError].} = + var dataJson: JsonNode + try: + dataJson = parseJson(data) + except JsonParsingError: + raise newException(MessageParsingError, "Invalid JSON: " & + getCurrentExceptionMsg()) + except: + raise newException(MessageParsingError, "Unknown error: " & + getCurrentExceptionMsg()) + + if not dataJson.hasKey("username"): + raise newException(MessageParsingError, "Username field missing") + + result.username = dataJson["username"].getStr() + if result.username.len == 0: + raise newException(MessageParsingError, "Username field is empty") + + if not dataJson.hasKey("message"): + raise newException(MessageParsingError, "Message field missing") + result.message = dataJson["message"].getStr() + if result.message.len == 0: + raise newException(MessageParsingError, "Message field is empty") + +proc createMessage*(username, message: string): string = + result = $(%{ + "username": %username, + "message": %message + }) & "\c\l" + +when isMainModule: + block: + let data = """{"username": "dom", "message": "hello"}""" + let parsed = parseMessage(data) + doAssert parsed.message == "hello" + doAssert parsed.username == "dom" + + # Test failure + block: + try: + let parsed = parseMessage("asdasd") + except MessageParsingError: + doAssert true + except: + doAssert false + diff --git a/tests/niminaction/Chapter3/ChatApp/src/server.nim b/tests/niminaction/Chapter3/ChatApp/src/server.nim new file mode 100644 index 000000000..8c572aeb0 --- /dev/null +++ b/tests/niminaction/Chapter3/ChatApp/src/server.nim @@ -0,0 +1,84 @@ +import asyncdispatch, asyncnet + +type + Client = ref object + socket: AsyncSocket + netAddr: string + id: int + connected: bool + + Server = ref object + socket: AsyncSocket + clients: seq[Client] + +proc newServer(): Server = + ## Constructor for creating a new ``Server``. + Server(socket: newAsyncSocket(), clients: @[]) + +proc `$`(client: Client): string = + ## Converts a ``Client``'s information into a string. + $client.id & "(" & client.netAddr & ")" + +proc processMessages(server: Server, client: Client) {.async.} = + ## Loops while ``client`` is connected to this server, and checks + ## whether as message has been received from ``client``. + while true: + # Pause execution of this procedure until a line of data is received from + # ``client``. + let line = await client.socket.recvLine() + + # The ``recvLine`` procedure returns ``""`` (i.e. a string of length 0) + # when ``client`` has disconnected. + if line.len == 0: + echo(client, " disconnected!") + client.connected = false + # When a socket disconnects it must be closed. + client.socket.close() + return + + # Display the message that was sent by the client. + echo(client, " sent: ", line) + + # Send the message to other clients. + for c in server.clients: + # Don't send it to the client that sent this or to a client that is + # disconnected. + if c.id != client.id and c.connected: + await c.socket.send(line & "\c\l") + +proc loop(server: Server, port = 7687) {.async.} = + ## Loops forever and checks for new connections. + + # Bind the port number specified by ``port``. + server.socket.bindAddr(port.Port) + # Ready the server socket for new connections. + server.socket.listen() + echo("Listening on localhost:", port) + + while true: + # Pause execution of this procedure until a new connection is accepted. + let (netAddr, clientSocket) = await server.socket.acceptAddr() + echo("Accepted connection from ", netAddr) + + # Create a new instance of Client. + let client = Client( + socket: clientSocket, + netAddr: netAddr, + id: server.clients.len, + connected: true + ) + # Add this new instance to the server's list of clients. + server.clients.add(client) + # Run the ``processMessages`` procedure asynchronously in the background, + # this procedure will continuously check for new messages from the client. + asyncCheck processMessages(server, client) + +# Check whether this module has been imported as a dependency to another +# module, or whether this module is the main module. +when isMainModule: + # Initialise a new server. + var server = newServer() + echo("Server initialised!") + # Execute the ``loop`` procedure. The ``waitFor`` procedure will run the + # asyncdispatch event loop until the ``loop`` procedure finishes executing. + waitFor loop(server) \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim new file mode 100644 index 000000000..478f533d9 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim @@ -0,0 +1,79 @@ +# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites +import tables, parseutils, strutils, threadpool + +const filename = "pagecounts-20160101-050000" + +type + Stats = ref object + projectName, pageTitle: string + requests, contentSize: int + +proc `$`(stats: Stats): string = + "(projectName: $#, pageTitle: $#, requests: $#, contentSize: $#)" % [ + stats.projectName, stats.pageTitle, $stats.requests, $stats.contentSize + ] + +proc parse(chunk: string): Stats = + # Each line looks like: en Main_Page 242332 4737756101 + result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0) + + var projectName = "" + var pageTitle = "" + var requests = "" + var contentSize = "" + for line in chunk.splitLines: + var i = 0 + projectName.setLen(0) + i.inc parseUntil(line, projectName, Whitespace, i) + i.inc skipWhitespace(line, i) + pageTitle.setLen(0) + i.inc parseUntil(line, pageTitle, Whitespace, i) + i.inc skipWhitespace(line, i) + requests.setLen(0) + i.inc parseUntil(line, requests, Whitespace, i) + i.inc skipWhitespace(line, i) + contentSize.setLen(0) + i.inc parseUntil(line, contentSize, Whitespace, i) + i.inc skipWhitespace(line, i) + + if requests.len == 0 or contentSize.len == 0: + # Ignore lines with either of the params that are empty. + continue + + let requestsInt = requests.parseInt + if requestsInt > result.requests and projectName == "en": + result = Stats( + projectName: projectName, + pageTitle: pageTitle, + requests: requestsInt, + contentSize: contentSize.parseInt + ) + +proc readChunks(filename: string, chunksize = 1000000): Stats = + result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0) + var file = open(filename) + var responses = newSeq[FlowVar[Stats]]() + var buffer = newString(chunksize) + var oldBufferLen = 0 + while not endOfFile(file): + let readSize = file.readChars(buffer, oldBufferLen, chunksize - oldBufferLen) + oldBufferLen + var chunkLen = readSize + + while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines: + # Find where the last line ends + chunkLen.dec + + responses.add(spawn parse(buffer[0 .. <chunkLen])) + oldBufferLen = readSize - chunkLen + buffer[0 .. <oldBufferLen] = buffer[readSize - oldBufferLen .. ^1] + + for resp in responses: + let statistic = ^resp + if statistic.requests > result.requests: + result = statistic + + file.close() + + +when isMainModule: + echo readChunks(filename) diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg new file mode 100644 index 000000000..aed303eef --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency.nim.cfg @@ -0,0 +1 @@ +--threads:on diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim new file mode 100644 index 000000000..8df3b6aeb --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim @@ -0,0 +1,64 @@ +# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites +import tables, parseutils, strutils, threadpool, re + +const filename = "pagecounts-20160101-050000" + +type + Stats = ref object + projectName, pageTitle: string + requests, contentSize: int + +proc `$`(stats: Stats): string = + "(projectName: $#, pageTitle: $#, requests: $#, contentSize: $#)" % [ + stats.projectName, stats.pageTitle, $stats.requests, $stats.contentSize + ] + +proc parse(chunk: string): Stats = + # Each line looks like: en Main_Page 242332 4737756101 + result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0) + + var matches: array[4, string] + var reg = re"([^\s]+)\s([^\s]+)\s(\d+)\s(\d+)" + for line in chunk.splitLines: + + let start = find(line, reg, matches) + if start == -1: continue + + let requestsInt = matches[2].parseInt + if requestsInt > result.requests and matches[0] == "en": + result = Stats( + projectName: matches[0], + pageTitle: matches[1], + requests: requestsInt, + contentSize: matches[3].parseInt + ) + +proc readChunks(filename: string, chunksize = 1000000): Stats = + result = Stats(projectName: "", pageTitle: "", requests: 0, contentSize: 0) + var file = open(filename) + var responses = newSeq[FlowVar[Stats]]() + var buffer = newString(chunksize) + var oldBufferLen = 0 + while not endOfFile(file): + let readSize = file.readChars(buffer, oldBufferLen, chunksize - oldBufferLen) + oldBufferLen + var chunkLen = readSize + + while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines: + # Find where the last line ends + chunkLen.dec + + responses.add(spawn parse(buffer[0 .. <chunkLen])) + oldBufferLen = readSize - chunkLen + buffer[0 .. <oldBufferLen] = buffer[readSize - oldBufferLen .. ^1] + + echo("Spawns: ", responses.len) + for resp in responses: + let statistic = ^resp + if statistic.requests > result.requests: + result = statistic + + file.close() + + +when isMainModule: + echo readChunks(filename) diff --git a/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg new file mode 100644 index 000000000..aed303eef --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/concurrency_regex.nim.cfg @@ -0,0 +1 @@ +--threads:on diff --git a/tests/niminaction/Chapter6/WikipediaStats/naive.nim b/tests/niminaction/Chapter6/WikipediaStats/naive.nim new file mode 100644 index 000000000..ed4fba8e2 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/naive.nim @@ -0,0 +1,29 @@ +# See this page for info about the format https://wikitech.wikimedia.org/wiki/Analytics/Data/Pagecounts-all-sites +import tables, parseutils, strutils + +const filename = "pagecounts-20150101-050000" + +proc parse(filename: string): tuple[projectName, pageTitle: string, + requests, contentSize: int] = + # Each line looks like: en Main_Page 242332 4737756101 + var file = open(filename) + for line in file.lines: + var i = 0 + var projectName = "" + i.inc parseUntil(line, projectName, Whitespace, i) + i.inc + var pageTitle = "" + i.inc parseUntil(line, pageTitle, Whitespace, i) + i.inc + var requests = 0 + i.inc parseInt(line, requests, i) + i.inc + var contentSize = 0 + i.inc parseInt(line, contentSize, i) + if requests > result[2] and projectName == "en": + result = (projectName, pageTitle, requests, contentSize) + + file.close() + +when isMainModule: + echo parse(filename) diff --git a/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim new file mode 100644 index 000000000..7181145e9 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim @@ -0,0 +1,72 @@ +import os, parseutils, threadpool, strutils + +type + Stats = ref object + domainCode, pageTitle: string + countViews, totalSize: int + +proc newStats(): Stats = + Stats(domainCode: "", pageTitle: "", countViews: 0, totalSize: 0) + +proc `$`(stats: Stats): string = + "(domainCode: $#, pageTitle: $#, countViews: $#, totalSize: $#)" % [ + stats.domainCode, stats.pageTitle, $stats.countViews, $stats.totalSize + ] + +proc parse(line: string, domainCode, pageTitle: var string, + countViews, totalSize: var int) = + if line.len == 0: return + var i = 0 + domainCode.setLen(0) + i.inc parseUntil(line, domainCode, {' '}, i) + i.inc + pageTitle.setLen(0) + i.inc parseUntil(line, pageTitle, {' '}, i) + i.inc + countViews = 0 + i.inc parseInt(line, countViews, i) + i.inc + totalSize = 0 + i.inc parseInt(line, totalSize, i) + +proc parseChunk(chunk: string): Stats = + result = newStats() + var domainCode = "" + var pageTitle = "" + var countViews = 0 + var totalSize = 0 + for line in splitLines(chunk): + parse(line, domainCode, pageTitle, countViews, totalSize) + if domainCode == "en" and countViews > result.countViews: + result = Stats(domainCode: domainCode, pageTitle: pageTitle, + countViews: countViews, totalSize: totalSize) + +proc readPageCounts(filename: string, chunkSize = 1_000_000) = + var file = open(filename) + var responses = newSeq[FlowVar[Stats]]() + var buffer = newString(chunksize) + var oldBufferLen = 0 + while not endOfFile(file): + let reqSize = chunksize - oldBufferLen + let readSize = file.readChars(buffer, oldBufferLen, reqSize) + oldBufferLen + var chunkLen = readSize + + while chunkLen >= 0 and buffer[chunkLen - 1] notin NewLines: + chunkLen.dec + + responses.add(spawn parseChunk(buffer[0 .. <chunkLen])) + oldBufferLen = readSize - chunkLen + buffer[0 .. <oldBufferLen] = buffer[readSize - oldBufferLen .. ^1] + + var mostPopular = newStats() + for resp in responses: + let statistic = ^resp + if statistic.countViews > mostPopular.countViews: + mostPopular = statistic + + echo("Most popular is: ", mostPopular) + +when isMainModule: + const file = "pagecounts-20160101-050000" + let filename = getCurrentDir() / file + readPageCounts(filename) \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg new file mode 100644 index 000000000..9d57ecf93 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/parallel_counts.nim.cfg @@ -0,0 +1 @@ +--threads:on \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim new file mode 100644 index 000000000..c62b2f93e --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim @@ -0,0 +1,13 @@ +import threadpool + +var counter = 0 + +proc increment(x: int) = + for i in 0 .. <x: + let value = counter + 1 + counter = value + +spawn increment(10_000) +spawn increment(10_000) +sync() +echo(counter) \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg new file mode 100644 index 000000000..9d57ecf93 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/race_condition.nim.cfg @@ -0,0 +1 @@ +--threads:on \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim b/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim new file mode 100644 index 000000000..25ad7d5f4 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/sequential_counts.nim @@ -0,0 +1,34 @@ +import os, parseutils + +proc parse(line: string, domainCode, pageTitle: var string, + countViews, totalSize: var int) = + var i = 0 + domainCode.setLen(0) + i.inc parseUntil(line, domainCode, {' '}, i) + i.inc + pageTitle.setLen(0) + i.inc parseUntil(line, pageTitle, {' '}, i) + i.inc + countViews = 0 + i.inc parseInt(line, countViews, i) + i.inc + totalSize = 0 + i.inc parseInt(line, totalSize, i) + +proc readPageCounts(filename: string) = + var domainCode = "" + var pageTitle = "" + var countViews = 0 + var totalSize = 0 + var mostPopular = ("", "", 0, 0) + for line in filename.lines: + parse(line, domainCode, pageTitle, countViews, totalSize) + if domainCode == "en" and countViews > mostPopular[2]: + mostPopular = (domainCode, pageTitle, countViews, totalSize) + + echo("Most popular is: ", mostPopular) + +when isMainModule: + const file = "pagecounts-20160101-050000" + let filename = getCurrentDir() / file + readPageCounts(filename) \ No newline at end of file diff --git a/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim new file mode 100644 index 000000000..72e8bff12 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim @@ -0,0 +1,15 @@ +import threadpool, locks + +var counterLock: Lock +initLock(counterLock) +var counter {.guard: counterLock.} = 0 + +proc increment(x: int) = + for i in 0 .. <x: + let value = counter + 1 + counter = value + +spawn increment(10_000) +spawn increment(10_000) +sync() +echo(counter) diff --git a/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg new file mode 100644 index 000000000..9d57ecf93 --- /dev/null +++ b/tests/niminaction/Chapter6/WikipediaStats/unguarded_access.nim.cfg @@ -0,0 +1 @@ +--threads:on \ No newline at end of file diff --git a/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble b/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble new file mode 100644 index 000000000..0a0ffad1a --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/Tweeter.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Dominik Picheta" +description = "A simple Twitter clone developed in Nim in Action." +license = "MIT" + +bin = @["tweeter"] +skipExt = @["nim"] + +# Dependencies + +requires "nim >= 0.13.1" +requires "jester >= 0.0.1" diff --git a/tests/niminaction/Chapter7/Tweeter/public/style.css b/tests/niminaction/Chapter7/Tweeter/public/style.css new file mode 100644 index 000000000..baacfaf9d --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/public/style.css @@ -0,0 +1,117 @@ +body { + background-color: #f1f9ea; + margin: 0; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; +} + +div#main { + width: 80%; + margin-left: auto; + margin-right: auto; +} + +div#user { + background-color: #66ac32; + width: 100%; + color: #c7f0aa; + padding: 5pt; +} + +div#user > h1 { + color: #ffffff; +} + +h1 { + margin: 0; + display: inline; + padding-left: 10pt; + padding-right: 10pt; +} + +div#user > form { + float: right; + margin-right: 10pt; +} + +div#user > form > input[type="submit"] { + border: 0px none; + padding: 5pt; + font-size: 108%; + color: #ffffff; + background-color: #515d47; + border-radius: 5px; + cursor: pointer; +} + +div#user > form > input[type="submit"]:hover { + background-color: #538c29; +} + + +div#messages { + background-color: #a2dc78; + width: 90%; + margin-left: auto; + margin-right: auto; + color: #1a1a1a; +} + +div#messages > div { + border-left: 1px solid #869979; + border-right: 1px solid #869979; + border-bottom: 1px solid #869979; + padding: 5pt; +} + +div#messages > div > a, div#messages > div > span { + color: #475340; +} + +div#messages > div > a:hover { + text-decoration: none; + color: #c13746; +} + +h3 { + margin-bottom: 0; + font-weight: normal; +} + +div#login { + width: 200px; + margin-left: auto; + margin-right: auto; + margin-top: 20%; + + font-size: 130%; +} + +div#login span.small { + display: block; + font-size: 56%; +} + +div#newMessage { + background-color: #538c29; + width: 90%; + margin-left: auto; + margin-right: auto; + color: #ffffff; + padding: 5pt; +} + +div#newMessage span { + padding-right: 5pt; +} + +div#newMessage form { + display: inline; +} + +div#newMessage > form > input[type="text"] { + width: 80%; +} + +div#newMessage > form > input[type="submit"] { + font-size: 80%; +} diff --git a/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim new file mode 100644 index 000000000..c7aee1b44 --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/createDatabase.nim @@ -0,0 +1,6 @@ +import database + +var db = newDatabase() +db.setup() +echo("Database created successfully!") +db.close() \ No newline at end of file diff --git a/tests/niminaction/Chapter7/Tweeter/src/database.nim b/tests/niminaction/Chapter7/Tweeter/src/database.nim new file mode 100644 index 000000000..4faba3f6a --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/database.nim @@ -0,0 +1,93 @@ +import times, db_sqlite, strutils #<1> +type #<2> + Database* = ref object + db*: DbConn + + User* = object #<3> + username*: string #<4> + following*: seq[string] #<5> + + Message* = object #<6> + username*: string #<7> + time*: Time #<8> + msg*: string #<9> + +proc newDatabase*(filename = "tweeter.db"): Database = + new result + result.db = open(filename, "", "", "") + +proc close*(database: Database) = + database.db.close() + +proc setup*(database: Database) = + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS User( + username text PRIMARY KEY + ); + """) + + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS Following( + follower text, + followed_user text, + PRIMARY KEY (follower, followed_user), + FOREIGN KEY (follower) REFERENCES User(username), + FOREIGN KEY (followed_user) REFERENCES User(username) + ); + """) + + database.db.exec(sql""" + CREATE TABLE IF NOT EXISTS Message( + username text, + time integer, + msg text NOT NULL, + FOREIGN KEY (username) REFERENCES User(username) + ); + """) + +proc post*(database: Database, message: Message) = + if message.msg.len > 140: #<1> + raise newException(ValueError, "Message has to be less than 140 characters.") + + database.db.exec(sql"INSERT INTO Message VALUES (?, ?, ?);", #<2> + message.username, $message.time.toSeconds().int, message.msg) #<3> + +proc follow*(database: Database, follower: User, user: User) = + database.db.exec(sql"INSERT INTO Following VALUES (?, ?);",#<2> + follower.username, user.username) + +proc create*(database: Database, user: User) = + database.db.exec(sql"INSERT INTO User VALUES (?);", user.username) #<2> + +proc findUser*(database: Database, username: string, user: var User): bool = + let row = database.db.getRow( + sql"SELECT username FROM User WHERE username = ?;", username) + if row[0].len == 0: return false + else: user.username = row[0] + + let following = database.db.getAllRows( + sql"SELECT followed_user FROM Following WHERE follower = ?;", username) + user.following = @[] + for row in following: + if row[0].len != 0: + user.following.add(row[0]) + + return true + +proc findMessages*(database: Database, usernames: seq[string], + limit = 10): seq[Message] = + result = @[] + if usernames.len == 0: return + var whereClause = " WHERE " + for i in 0 .. <usernames.len: + whereClause.add("username = ? ") + if i != <usernames.len: + whereClause.add("or ") + + let messages = database.db.getAllRows( + sql("SELECT username, time, msg FROM Message" & + whereClause & + "ORDER BY time DESC LIMIT " & $limit), + usernames) + for row in messages: + result.add(Message(username: row[0], time: fromSeconds(row[1].parseInt), msg: row[2])) diff --git a/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim new file mode 100644 index 000000000..b8a36306e --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/tweeter.nim @@ -0,0 +1,62 @@ +import asyncdispatch, times + +import jester + +import database, views/user, views/general + +proc userLogin(db: Database, request: Request, user: var User): bool = + if request.cookies.hasKey("username"): + if not db.findUser(request.cookies["username"], user): + user = User(username: request.cookies["username"], following: @[]) + db.create(user) + return true + else: + return false + +let db = newDatabase() +routes: + get "/": + var user: User + if db.userLogin(request, user): + let messages = db.findMessages(user.following & user.username) + resp renderMain(renderTimeline(user.username, messages)) + else: + resp renderMain(renderLogin()) + + get "/@name": + cond '.' notin @"name" + var user: User + if not db.findUser(@"name", user): + halt "User not found" + let messages = db.findMessages(@[user.username]) + + var currentUser: User + if db.userLogin(request, currentUser): + resp renderMain(renderUser(user, currentUser) & renderMessages(messages)) + else: + resp renderMain(renderUser(user) & renderMessages(messages)) + + post "/follow": + var follower: User + var target: User + if not db.findUser(@"follower", follower): + halt "Follower not found" + if not db.findUser(@"target", target): + halt "Follow target not found" + db.follow(follower, target) + redirect(uri("/" & @"target")) + + post "/login": + setCookie("username", @"username", getTime().getGMTime() + 2.hours) + redirect("/") + + post "/createMessage": + let message = Message( + username: @"username", + time: getTime(), + msg: @"message" + ) + db.post(message) + redirect("/") + +runForever() diff --git a/tests/niminaction/Chapter7/Tweeter/src/views/general.nim b/tests/niminaction/Chapter7/Tweeter/src/views/general.nim new file mode 100644 index 000000000..0e920b1de --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/views/general.nim @@ -0,0 +1,51 @@ +#? stdtmpl(subsChar = '$', metaChar = '#') +#import "../database" +#import user +#import xmltree +# +#proc `$!`(text: string): string = escape(text) +#end proc +# +#proc renderMain*(body: string): string = +# result = "" +<!DOCTYPE html> +<html> + <head> + <title>Tweeter written in Nim</title> + <link rel="stylesheet" type="text/css" href="style.css"> + </head> + + <body> + ${body} + </body> + +</html> +#end proc +# +#proc renderLogin*(): string = +# result = "" +<div id="login"> + <span>Login</span> + <span class="small">Please type in your username...</span> + <form action="login" method="post"> + <input type="text" name="username"> + <input type="submit" value="Login"> + </form> +</div> +#end proc +# +#proc renderTimeline*(username: string, messages: seq[Message]): string = +# result = "" +<div id="user"> + <h1>${$!username}'s timeline</h1> +</div> +<div id="newMessage"> + <span>New message</span> + <form action="createMessage" method="post"> + <input type="text" name="message"> + <input type="hidden" name="username" value="${$!username}"> + <input type="submit" value="Tweet"> + </form> +</div> +${renderMessages(messages)} +#end proc \ No newline at end of file diff --git a/tests/niminaction/Chapter7/Tweeter/src/views/user.nim b/tests/niminaction/Chapter7/Tweeter/src/views/user.nim new file mode 100644 index 000000000..f3791b493 --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/src/views/user.nim @@ -0,0 +1,49 @@ +#? stdtmpl(subsChar = '$', metaChar = '#', toString = "xmltree.escape") +#import "../database" +#import xmltree +#import times +# +#proc renderUser*(user: User): string = +# result = "" +<div id="user"> + <h1>${user.username}</h1> + <span>Following: ${$user.following.len}</span> +</div> +#end proc +# +#proc renderUser*(user: User, currentUser: User): string = +# result = "" +<div id="user"> + <h1>${user.username}</h1> + <span>Following: ${$user.following.len}</span> + #if user.username notin currentUser.following: + <form action="follow" method="post"> + <input type="hidden" name="follower" value="${currentUser.username}"> + <input type="hidden" name="target" value="${user.username}"> + <input type="submit" value="Follow"> + </form> + #end if +</div> +# +#end proc +# +#proc renderMessages*(messages: seq[Message]): string = +# result = "" +<div id="messages"> + #for message in messages: + <div> + <a href="/${message.username}">${message.username}</a> + <span>${message.time.getGMTime().format("HH:mm MMMM d',' yyyy")}</span> + <h3>${message.msg}</h3> + </div> + #end for +</div> +#end proc +# +#when isMainModule: +# echo renderUser(User(username: "d0m96<>", following: @[])) +# echo renderMessages(@[ +# Message(username: "d0m96", time: getTime(), msg: "Hello World!"), +# Message(username: "d0m96", time: getTime(), msg: "Testing") +# ]) +#end when diff --git a/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim new file mode 100644 index 000000000..926ca452c --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nim @@ -0,0 +1,28 @@ +import database, os, times + +when isMainModule: + removeFile("tweeter_test.db") + var db = newDatabase("tweeter_test.db") + db.setup() + + db.create(User(username: "d0m96")) + db.create(User(username: "nim_lang")) + + db.post(Message(username: "nim_lang", time: getTime() - 4.seconds, + msg: "Hello Nim in Action readers")) + db.post(Message(username: "nim_lang", time: getTime(), + msg: "99.9% off Nim in Action for everyone, for the next minute only!")) + + var dom: User + doAssert db.findUser("d0m96", dom) + var nim: User + doAssert db.findUser("nim_lang", nim) + db.follow(dom, nim) + + doAssert db.findUser("d0m96", dom) + + let messages = db.findMessages(dom.following) + echo(messages) + doAssert(messages[0].msg == "99.9% off Nim in Action for everyone, for the next minute only!") + doAssert(messages[1].msg == "Hello Nim in Action readers") + echo("All tests finished successfully!") diff --git a/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims new file mode 100644 index 000000000..226905fbf --- /dev/null +++ b/tests/niminaction/Chapter7/Tweeter/tests/database_test.nims @@ -0,0 +1,2 @@ +--path:"../src" +#switch("path", "./src") diff --git a/tests/niminaction/Chapter8/canvas/canvas.nim b/tests/niminaction/Chapter8/canvas/canvas.nim new file mode 100644 index 000000000..713d1e9e2 --- /dev/null +++ b/tests/niminaction/Chapter8/canvas/canvas.nim @@ -0,0 +1,19 @@ +import dom + +type + CanvasRenderingContext* = ref object + fillStyle* {.importc.}: cstring + strokeStyle* {.importc.}: cstring + +{.push importcpp.} + +proc getContext*(canvasElement: Element, + contextType: cstring): CanvasRenderingContext + +proc fillRect*(context: CanvasRenderingContext, x, y, width, height: int) + +proc moveTo*(context: CanvasRenderingContext, x, y: int) + +proc lineTo*(context: CanvasRenderingContext, x, y: int) + +proc stroke*(context: CanvasRenderingContext) diff --git a/tests/niminaction/Chapter8/canvas/canvas_test.nim b/tests/niminaction/Chapter8/canvas/canvas_test.nim new file mode 100644 index 000000000..42d222b7b --- /dev/null +++ b/tests/niminaction/Chapter8/canvas/canvas_test.nim @@ -0,0 +1,19 @@ +import canvas, dom + +proc onLoad() {.exportc.} = + var canvas = document.getElementById("canvas").EmbedElement + canvas.width = window.innerWidth + canvas.height = window.innerHeight + var ctx = canvas.getContext("2d") + + ctx.fillStyle = "#1d4099" + ctx.fillRect(0, 0, window.innerWidth, window.innerHeight) + + ctx.strokeStyle = "#ffffff" + let letterWidth = 100 + let letterLeftPos = (window.innerWidth div 2) - (letterWidth div 2) + ctx.moveTo(letterLeftPos, 320) + ctx.lineTo(letterLeftPos, 110) + ctx.lineTo(letterLeftPos + letterWidth, 320) + ctx.lineTo(letterLeftPos + letterWidth, 110) + ctx.stroke() diff --git a/tests/niminaction/Chapter8/sdl/sdl.nim b/tests/niminaction/Chapter8/sdl/sdl.nim new file mode 100644 index 000000000..a1b30281b --- /dev/null +++ b/tests/niminaction/Chapter8/sdl/sdl.nim @@ -0,0 +1,34 @@ +when defined(Windows): + const libName* = "SDL2.dll" +elif defined(Linux): + const libName* = "libSDL2.so" +elif defined(MacOsX): + const libName* = "libSDL2.dylib" + +type + SdlWindow = object + SdlWindowPtr* = ptr SdlWindow + SdlRenderer = object + SdlRendererPtr* = ptr SdlRenderer + +const INIT_VIDEO* = 0x00000020 + +{.push dynlib: libName.} +proc init*(flags: uint32): cint {.importc: "SDL_Init".} + +proc createWindowAndRenderer*(width, height: cint, window_flags: cuint, + window: var SdlWindowPtr, renderer: var SdlRendererPtr): cint + {.importc: "SDL_CreateWindowAndRenderer".} + +proc pollEvent*(event: pointer): cint {.importc: "SDL_PollEvent".} + +proc setDrawColor*(renderer: SdlRendererPtr, r, g, b, a: uint8): cint + {.importc: "SDL_SetRenderDrawColor", discardable.} + +proc present*(renderer: SdlRendererPtr) {.importc: "SDL_RenderPresent".} + +proc clear*(renderer: SdlRendererPtr) {.importc: "SDL_RenderClear".} + +proc drawLines*(renderer: SdlRendererPtr, points: ptr tuple[x, y: cint], + count: cint): cint {.importc: "SDL_RenderDrawLines", discardable.} +{.pop.} diff --git a/tests/niminaction/Chapter8/sdl/sdl_test.nim b/tests/niminaction/Chapter8/sdl/sdl_test.nim new file mode 100644 index 000000000..a572d5231 --- /dev/null +++ b/tests/niminaction/Chapter8/sdl/sdl_test.nim @@ -0,0 +1,25 @@ +import os +import sdl + +if sdl.init(INIT_VIDEO) == -1: + quit("Couldn't initialise SDL") + +var window: SdlWindowPtr +var renderer: SdlRendererPtr +if createWindowAndRenderer(640, 480, 0, window, renderer) == -1: + quit("Couldn't create a window or renderer") + +discard pollEvent(nil) +renderer.setDrawColor 29, 64, 153, 255 +renderer.clear +renderer.setDrawColor 255, 255, 255, 255 +var points = [ + (260'i32, 320'i32), + (260'i32, 110'i32), + (360'i32, 320'i32), + (360'i32, 110'i32) +] +renderer.drawLines(addr points[0], points.len.cint) + +renderer.present +sleep(5000) diff --git a/tests/niminaction/Chapter8/sfml/sfml.nim b/tests/niminaction/Chapter8/sfml/sfml.nim new file mode 100644 index 000000000..fea85fcd4 --- /dev/null +++ b/tests/niminaction/Chapter8/sfml/sfml.nim @@ -0,0 +1,26 @@ +{.passL: "-lsfml-graphics -lsfml-system -lsfml-window".} + +type + VideoMode* {.importcpp: "sf::VideoMode".} = object + RenderWindowObj {.importcpp: "sf::RenderWindow".} = object + RenderWindow* = ptr RenderWindowObj + Color* {.importcpp: "sf::Color".} = object + Event* {.importcpp: "sf::Event".} = object + +{.push cdecl, header: "<SFML/Graphics.hpp>".} + +proc videoMode*(modeWidth, modeHeight: cuint, modeBitsPerPixel: cuint = 32): VideoMode + {.importcpp: "sf::VideoMode(@)", constructor.} + +proc newRenderWindow*(mode: VideoMode, title: cstring): RenderWindow + {.importcpp: "new sf::RenderWindow(@)", constructor.} + +proc pollEvent*(window: RenderWindow, event: var Event): bool + {.importcpp: "#.pollEvent(@)".} + +proc newColor*(red, green, blue, alpha: uint8): Color + {.importcpp: "sf::Color(@)", constructor.} + +proc clear*(window: RenderWindow, color: Color) {.importcpp: "#.clear(@)".} + +proc display*(window: RenderWindow) {.importcpp: "#.display()".} diff --git a/tests/niminaction/Chapter8/sfml/sfml_test.nim b/tests/niminaction/Chapter8/sfml/sfml_test.nim new file mode 100644 index 000000000..49a8176e5 --- /dev/null +++ b/tests/niminaction/Chapter8/sfml/sfml_test.nim @@ -0,0 +1,9 @@ +import sfml, os +var window = newRenderWindow(videoMode(800, 600), "SFML works!") + +var event: Event +discard window.pollEvent(event) +window.clear(newColor(29, 64, 153, 255)) +window.display() + +sleep(1000) diff --git a/tests/niminaction/Chapter9/configurator/configurator.nim b/tests/niminaction/Chapter9/configurator/configurator.nim new file mode 100644 index 000000000..0d5627889 --- /dev/null +++ b/tests/niminaction/Chapter9/configurator/configurator.nim @@ -0,0 +1,84 @@ +import macros + +proc createRefType(ident: NimIdent, identDefs: seq[NimNode]): NimNode = + result = newTree(nnkTypeSection, + newTree(nnkTypeDef, + newIdentNode(ident), + newEmptyNode(), + newTree(nnkRefTy, + newTree(nnkObjectTy, + newEmptyNode(), + newEmptyNode(), + newTree(nnkRecList, + identDefs + ) + ) + ) + ) + ) + +proc toIdentDefs(stmtList: NimNode): seq[NimNode] = + expectKind(stmtList, nnkStmtList) + result = @[] + + for child in stmtList: + expectKind(child, nnkCall) + result.add(newIdentDefs(child[0], child[1][0])) + +template constructor(ident: untyped): untyped = + proc `new ident`(): `ident` = + new result + +proc createLoadProc(typeName: NimIdent, identDefs: seq[NimNode]): NimNode = + var cfgIdent = newIdentNode("cfg") + var filenameIdent = newIdentNode("filename") + var objIdent = newIdentNode("obj") + + var body = newStmtList() + body.add quote do: + var `objIdent` = parseFile(`filenameIdent`) + + for identDef in identDefs: + let fieldNameIdent = identDef[0] + let fieldName = $fieldNameIdent.ident + case $identDef[1].ident + of "string": + body.add quote do: + `cfgIdent`.`fieldNameIdent` = `objIdent`[`fieldName`].getStr + of "int": + body.add quote do: + `cfgIdent`.`fieldNameIdent` = `objIdent`[`fieldName`].getNum().int + else: + doAssert(false, "Not Implemented") + + return newProc(newIdentNode("load"), + [newEmptyNode(), + newIdentDefs(cfgIdent, newIdentNode(typeName)), + newIdentDefs(filenameIdent, newIdentNode("string"))], + body) + +macro config*(typeName: untyped, fields: untyped): untyped = + result = newStmtList() + + let identDefs = toIdentDefs(fields) + result.add createRefType(typeName.ident, identDefs) + result.add getAst(constructor(typeName.ident)) + result.add createLoadProc(typeName.ident, identDefs) + + echo treeRepr(typeName) + echo treeRepr(fields) + + echo treeRepr(result) + echo toStrLit(result) + # TODO: Verify that we can export fields in config type so that it can be + # used in another module. + +import json +config MyAppConfig: + address: string + port: int + +var myConf = newMyAppConfig() +myConf.load("myappconfig.cfg") +echo("Address: ", myConf.address) +echo("Port: ", myConf.port) diff --git a/tests/stdlib/mjsonexternproc.nim b/tests/stdlib/mjsonexternproc.nim new file mode 100644 index 000000000..2967a1168 --- /dev/null +++ b/tests/stdlib/mjsonexternproc.nim @@ -0,0 +1,10 @@ +# Test case for https://github.com/nim-lang/Nim/issues/6385 + +import json +# export json + +proc foo*[T](a: T) = + let params = %*{ + "data": [ 1 ] + } + echo $params \ No newline at end of file diff --git a/tests/stdlib/tjsonexternproc.nim b/tests/stdlib/tjsonexternproc.nim new file mode 100644 index 000000000..ec90e580d --- /dev/null +++ b/tests/stdlib/tjsonexternproc.nim @@ -0,0 +1,5 @@ +# Test case for https://github.com/nim-lang/Nim/issues/6385 + +import mjsonexternproc +# import json +foo(1) \ No newline at end of file diff --git a/tests/stdlib/tmath2.nim b/tests/stdlib/tmath2.nim index 84a49efa9..eb0506f5f 100644 --- a/tests/stdlib/tmath2.nim +++ b/tests/stdlib/tmath2.nim @@ -64,7 +64,7 @@ proc TestLoops() = var glob: int - a: array [0..5, int] + a: array[0..5, int] proc main() = #glob = 0 diff --git a/tests/stdlib/trepr2.nim b/tests/stdlib/trepr2.nim index a92024851..300df565d 100644 --- a/tests/stdlib/trepr2.nim +++ b/tests/stdlib/trepr2.nim @@ -6,7 +6,7 @@ type TPoint {.final.} = object x, y, z: int - s: array [0..1, string] + s: array[0..1, string] e: TEnum var diff --git a/tests/stdlib/tstrset.nim b/tests/stdlib/tstrset.nim index 15024c3f1..1b253f862 100644 --- a/tests/stdlib/tstrset.nim +++ b/tests/stdlib/tstrset.nim @@ -7,10 +7,10 @@ type kind: TRadixNodeKind TRadixNodeLinear = object of TRadixNode len: int8 - keys: array [0..31, char] - vals: array [0..31, PRadixNode] + keys: array[0..31, char] + vals: array[0..31, PRadixNode] TRadixNodeFull = object of TRadixNode - b: array [char, PRadixNode] + b: array[char, PRadixNode] TRadixNodeLeaf = object of TRadixNode s: string PRadixNodeLinear = ref TRadixNodeLinear diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index 674ce50dd..e4a801871 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -1,12 +1,23 @@ discard """ - nimout: "compile start\ncompile end" + output: '''[Suite] suite with only teardown + +[Suite] suite with only setup + +[Suite] suite with none + +[Suite] suite with both + +[Suite] bug #4494 + +[Suite] bug #5571 + +[Suite] bug #5784 + +''' """ import unittest, sequtils -static: - echo "compile start" - proc doThings(spuds: var int): int = spuds = 24 return 99 @@ -103,5 +114,9 @@ suite "bug #5571": check: line == "a" doTest() -static: - echo "compile end" +suite "bug #5784": + test "`or` should short circuit": + type Obj = ref object + field: int + var obj: Obj + check obj.isNil or obj.field == 0 diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index 7b1dd0df0..f71a4a1e7 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -238,6 +238,48 @@ proc jsTests(r: var TResults, cat: Category, options: string) = for testfile in ["strutils", "json", "random", "times", "logging"]: test "lib/pure/" & testfile & ".nim" +# ------------------------- nim in action ----------- + +proc testNimInAction(r: var TResults, cat: Category, options: string) = + template test(filename: untyped, action: untyped) = + testSpec r, makeTest(filename, options, cat, action) + + template testJS(filename: untyped) = + testSpec r, makeTest(filename, options, cat, actionCompile, targetJS) + + template testCPP(filename: untyped) = + testSpec r, makeTest(filename, options, cat, actionCompile, targetCPP) + + let tests = [ + "niminaction/Chapter3/ChatApp/src/server", + "niminaction/Chapter3/ChatApp/src/client", + "niminaction/Chapter6/WikipediaStats/concurrency_regex", + "niminaction/Chapter6/WikipediaStats/concurrency", + "niminaction/Chapter6/WikipediaStats/naive", + "niminaction/Chapter6/WikipediaStats/parallel_counts", + "niminaction/Chapter6/WikipediaStats/race_condition", + "niminaction/Chapter6/WikipediaStats/sequential_counts", + "niminaction/Chapter7/Tweeter/src/tweeter", + "niminaction/Chapter7/Tweeter/src/createDatabase", + "niminaction/Chapter7/Tweeter/tests/database_test", + "niminaction/Chapter8/sdl/sdl_test", + ] + for testfile in tests: + test "tests/" & testfile & ".nim", actionCompile + + # TODO: This doesn't work for some reason ;\ + # let reject = "tests/niminaction/Chapter6/WikipediaStats" & + # "/unguarded_access.nim" + # test reject, actionReject + + let jsFile = "tests/niminaction/Chapter8/canvas/canvas_test.nim" + testJS jsFile + + let cppFile = "tests/niminaction/Chapter8/sfml/sfml_test.nim" + testCPP cppFile + + + # ------------------------- manyloc ------------------------------------------- #proc runSpecialTests(r: var TResults, options: string) = # for t in ["lib/packages/docutils/highlite"]: @@ -420,6 +462,8 @@ proc processCategory(r: var TResults, cat: Category, options: string) = testNimblePackages(r, cat, pfExtraOnly) of "nimble-all": testNimblePackages(r, cat, pfAll) + of "niminaction": + testNimInAction(r, cat, options) of "untestable": # We can't test it because it depends on a third party. discard # TODO: Move untestable tests to someplace else, i.e. nimble repo. diff --git a/tests/threads/tthreadanalysis.nim b/tests/threads/tthreadanalysis.nim index 5b0b666ac..8aacc71d1 100644 --- a/tests/threads/tthreadanalysis.nim +++ b/tests/threads/tthreadanalysis.nim @@ -9,7 +9,7 @@ discard """ import os var - thr: array [0..5, Thread[tuple[a, b: int]]] + thr: array[0..5, Thread[tuple[a, b: int]]] proc doNothing() = discard diff --git a/tests/threads/tthreadanalysis2.nim b/tests/threads/tthreadanalysis2.nim index 93d169f0b..c1ec3ae39 100644 --- a/tests/threads/tthreadanalysis2.nim +++ b/tests/threads/tthreadanalysis2.nim @@ -8,7 +8,7 @@ discard """ import os var - thr: array [0..5, Thread[tuple[a, b: int]]] + thr: array[0..5, Thread[tuple[a, b: int]]] proc doNothing() = discard diff --git a/tests/varres/tvarres3.nim b/tests/varres/tvarres3.nim index 9fcd79bfc..9a46bfb4e 100644 --- a/tests/varres/tvarres3.nim +++ b/tests/varres/tvarres3.nim @@ -6,7 +6,7 @@ var g = 5 proc p(): var int = - var bla = addr(g) #: array [0..7, int] + var bla = addr(g) #: array[0..7, int] result = bla[] p() = 45 diff --git a/tests/vm/tarrayboundeval.nim b/tests/vm/tarrayboundeval.nim index af9e33339..dc8c7ebdc 100644 --- a/tests/vm/tarrayboundeval.nim +++ b/tests/vm/tarrayboundeval.nim @@ -25,7 +25,7 @@ echo myconst, " ", int((KeyMax + 31) / 32) #bug 1304 or something: -const constArray: array [-3..2, int] = [-3, -2, -1, 0, 1, 2] +const constArray: array[-3..2, int] = [-3, -2, -1, 0, 1, 2] echo constArray[-2] diff --git a/tests/vm/tnimnode.nim b/tests/vm/tnimnode.nim index e5a41e3c2..60e3189b0 100644 --- a/tests/vm/tnimnode.nim +++ b/tests/vm/tnimnode.nim @@ -15,9 +15,9 @@ static: proc checkNode(arg: NimNode; name: string): void {. compileTime .} = echo "checking ", name - + assertEq arg.lispRepr , "StmtList(DiscardStmt(Empty()))" - + node = arg nodeArray = [arg] nodeSeq[0] = arg @@ -38,9 +38,9 @@ proc checkNode(arg: NimNode; name: string): void {. compileTime .} = static: # the root node that is used to generate the Ast var stmtList: NimNode - + stmtList = newStmtList(nnkDiscardStmt.newTree(newEmptyNode())) - + checkNode(stmtList, "direct construction") @@ -50,12 +50,12 @@ macro foo(stmtList: untyped): untyped = foo: discard - + static: stmtList = quote do: discard - checkNode(stmtList, "create with quote") + checkNode(newTree(nnkStmtList, stmtList), "create with quote") static: @@ -64,13 +64,13 @@ static: for i in 0 ..< 10: discard - let innerBody = loop[0][2] + let innerBody = loop[2] innerBody.add newCall(ident"echo", newLit("Hello World")) - assertEq loop[0][2].lispRepr, innerBody.lispRepr + assertEq loop[2].lispRepr, innerBody.lispRepr echo "OK" - + static: echo "testing creation of comment node" diff --git a/tests/vm/tvmmisc.nim b/tests/vm/tvmmisc.nim index b7112b099..42a58fa8f 100644 --- a/tests/vm/tvmmisc.nim +++ b/tests/vm/tvmmisc.nim @@ -1,16 +1,66 @@ - - # bug #4462 import macros -proc foo(t: typedesc) {.compileTime.} = - echo getType(t).treeRepr +block: + proc foo(t: typedesc) {.compileTime.} = + assert sameType(getType(t), getType(int)) -static: - foo(int) + static: + foo(int) # #4412 -proc default[T](t: typedesc[T]): T {.inline.} = discard +block: + proc default[T](t: typedesc[T]): T {.inline.} = discard + + static: + var x = default(type(0)) +# #6379 static: - var x = default(type(0)) + import algorithm + + var numArray = [1, 2, 3, 4, -1] + numArray.sort(cmp) + assert numArray == [-1, 1, 2, 3, 4] + + var str = "cba" + str.sort(cmp) + assert str == "abc" + +# #6086 +import math, sequtils, future + +block: + proc f: int = + toSeq(10..<10_000).filter( + a => a == ($a).map( + d => (d.ord-'0'.ord).int^4 + ).sum + ).sum + + var a = f() + const b = f() + + assert a == b + +block: + proc f(): seq[char] = + result = "hello".map(proc(x: char): char = x) + + var runTime = f() + const compTime = f() + assert runTime == compTime + +# #6083 +block: + proc abc(): seq[int] = + result = @[0] + result.setLen(2) + var tmp: int + + for i in 0 .. <2: + inc tmp + result[i] = tmp + + const fact1000 = abc() + assert fact1000 == @[1, 2] diff --git a/tools/finish.nim b/tools/finish.nim index a6f689eac..45d7dd3a8 100644 --- a/tools/finish.nim +++ b/tools/finish.nim @@ -91,16 +91,25 @@ when defined(windows): except IOError: echo "Could not access 'config/nim.cfg' [Error]" - proc addToPathEnv*(e: string) = - var p: string + proc tryGetUnicodeValue(path, key: string; handle: HKEY): string = try: - p = getUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER) - except OSError: - p = getUnicodeValue( + result = getUnicodeValue(path, key, handle) + except: + result = "" + + proc addToPathEnv*(e: string) = + var p = tryGetUnicodeValue(r"Environment", "Path", HKEY_CURRENT_USER) + if p.len == 0: + p = tryGetUnicodeValue( r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", "Path", HKEY_LOCAL_MACHINE) let x = if e.contains(Whitespace): "\"" & e & "\"" else: e - setUnicodeValue(r"Environment", "Path", p & ";" & x, HKEY_CURRENT_USER) + if p.len > 0: + p.add ";" + p.add x + else: + p = x + setUnicodeValue(r"Environment", "Path", p, HKEY_CURRENT_USER) proc createShortcut(src, dest: string; icon = "") = var cmd = "bin\\makelink.exe \"" & src & "\" \"\" \"" & dest & diff --git a/tools/nimresolve.nim b/tools/nimresolve.nim new file mode 100644 index 000000000..9af24df5a --- /dev/null +++ b/tools/nimresolve.nim @@ -0,0 +1,158 @@ +# +# +# The Nim Compiler +# (c) Copyright 2017 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Standard tool that resolves import paths. + +import + os, strutils, parseopt + +import "../compiler/nimblecmd" + +# You can change these constants to build you own adapted resolver. +const + considerParentDirs = not defined(noParentProjects) + considerNimbleDirs = not defined(noNimbleDirs) + +const + Version = "1.0" + Usage = "nimresolve - Nim Resolve Package Path Version " & Version & """ + + (c) 2017 Andreas Rumpf +Usage: + nimresolve [options] package +Options: + --source:FILE the file that requests to resolve 'package' + --stdlib:PATH the path to use for the standard library + --project:FILE the main '.nim' file that was passed to the Nim compiler + --subdir:EXPR the subdir part in: 'import $pkg / subdir' + --noNimblePath do not search the Nimble path to resolve the package +""" + +proc writeHelp() = + stdout.write(Usage) + stdout.flushFile() + quit(0) + +proc writeVersion() = + stdout.write(Version & "\n") + stdout.flushFile() + quit(0) + +type + Task = object + source, stdlib, subdir, project, pkg: string + noNimblePath: bool + +proc findInNimbleDir(t: Task; dir: string): bool = + var best = "" + var bestv = "" + for k, p in os.walkDir(dir, relative=true): + if k == pcDir and p.len > t.pkg.len+1 and + p[t.pkg.len] == '-' and p.startsWith(t.pkg): + let (_, a) = getPathVersion(p) + if bestv.len == 0 or bestv < a: + bestv = a + best = dir / p + + if best.len > 0: + var f: File + if open(f, best / changeFileExt(t.pkg, ".nimble-link")): + # the second line contains what we're interested in, see: + # https://github.com/nim-lang/nimble#nimble-link + var override = "" + discard readLine(f, override) + discard readLine(f, override) + close(f) + if not override.isAbsolute(): + best = best / override + else: + best = override + let f = if t.subdir.len == 0: t.pkg else: t.subdir + let res = addFileExt(best / f, "nim") + if best.len > 0 and fileExists(res): + echo res + result = true + +const stdlibDirs = [ + "pure", "core", "arch", + "pure/collections", + "pure/concurrency", "impure", + "wrappers", "wrappers/linenoise", + "windows", "posix", "js"] + +proc resolve(t: Task) = + template attempt(a) = + let x = addFileExt(a, "nim") + if fileExists(x): + echo x + return + + case t.pkg + of "stdlib": + if t.subdir.len == 0: + echo t.stdlib + return + else: + for candidate in stdlibDirs: + attempt(t.stdlib / candidate / t.subdir) + of "root": + let root = t.project.splitFile.dir + if t.subdir.len == 0: + echo root + return + else: + attempt(root / t.subdir) + else: + when considerParentDirs: + var p = parentDir(t.source.splitFile.dir) + # support 'import $karax': + let f = if t.subdir.len == 0: t.pkg else: t.subdir + + while p.len > 0: + let dir = p / t.pkg + if dirExists(dir): + attempt(dir / f) + # 2nd attempt: try to use 'karax/karax' + attempt(dir / t.pkg / f) + # 3rd attempt: try to use 'karax/src/karax' + attempt(dir / "src" / f) + attempt(dir / "src" / t.pkg / f) + p = parentDir(p) + + when considerNimbleDirs: + if not t.noNimblePath: + if findInNimbleDir(t, getHomeDir() / ".nimble" / "pkgs"): return + when not defined(windows): + if findInNimbleDir(t, "/opt/nimble/pkgs"): return + + quit "cannot resolve: " & (t.pkg / t.subdir) + +proc main = + var t: Task + t.subdir = "" + for kind, key, val in getopt(): + case kind + of cmdArgument: + t.pkg = key + of cmdLongoption, cmdShortOption: + case normalize(key) + of "source": t.source = val + of "stdlib": t.stdlib = val + of "project": t.project = val + of "subdir": t.subdir = val + of "nonimblepath": t.noNimblePath = true + of "help", "h": writeHelp() + of "version", "v": writeVersion() + else: writeHelp() + of cmdEnd: assert(false) # cannot happen + if t.pkg.len == 0: + quit "[Error] no package to resolve." + resolve(t) + +main() |