diff options
-rwxr-xr-x | compiler/ast.nim | 10 | ||||
-rw-r--r-- | compiler/ccgcalls.nim | 71 | ||||
-rwxr-xr-x | compiler/ccgexprs.nim | 30 | ||||
-rwxr-xr-x | compiler/ccgstmts.nim | 19 | ||||
-rwxr-xr-x | compiler/ccgtypes.nim | 32 | ||||
-rwxr-xr-x | compiler/cgen.nim | 33 | ||||
-rw-r--r-- | compiler/lambdalifting.nim | 183 | ||||
-rwxr-xr-x | compiler/msgs.nim | 3 | ||||
-rwxr-xr-x | compiler/renderer.nim | 4 | ||||
-rwxr-xr-x | compiler/semexprs.nim | 14 | ||||
-rwxr-xr-x | compiler/semstmts.nim | 5 | ||||
-rwxr-xr-x | compiler/semthreads.nim | 2 | ||||
-rwxr-xr-x | compiler/transf.nim | 10 | ||||
-rwxr-xr-x | compiler/trees.nim | 2 | ||||
-rwxr-xr-x | compiler/types.nim | 11 | ||||
-rwxr-xr-x | doc/intern.txt | 30 | ||||
-rwxr-xr-x | doc/manual.txt | 37 | ||||
-rwxr-xr-x | lib/system.nim | 3 | ||||
-rwxr-xr-x | todo.txt | 14 | ||||
-rwxr-xr-x | web/news.txt | 4 |
20 files changed, 379 insertions, 138 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 45b1ebfd0..65d7bccf1 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -189,7 +189,8 @@ type nkProcTy, # proc type nkEnumTy, # enum body nkEnumFieldDef, # `ident = expr` in an enumeration - nkReturnToken # token used for interpretation + nkReturnToken, # token used for interpretation + nkClosure # (prc, env)-pair (internally used for code gen) TNodeKinds* = set[TNodeKind] type @@ -977,11 +978,12 @@ proc hasSonWith(n: PNode, kind: TNodeKind): bool = result = false proc containsNode*(n: PNode, kinds: TNodeKinds): bool = + if n == nil: return case n.kind of nkEmpty..nkNilLit: result = n.kind in kinds else: for i in countup(0, sonsLen(n) - 1): - if containsNode(n.sons[i], kinds): return true + if n.kind in kinds or containsNode(n.sons[i], kinds): return true proc hasSubnodeWith(n: PNode, kind: TNodeKind): bool = case n.kind @@ -1037,6 +1039,10 @@ proc isGenericRoutine*(s: PSym): bool = result = s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty else: nil +proc isRoutine*(s: PSym): bool {.inline.} = + result = s.kind in {skProc, skTemplate, skMacro, skIterator, skMethod, + skConverter} + iterator items*(n: PNode): PNode = for i in 0.. <n.len: yield n.sons[i] diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index b468c4dee..d6e2d67b7 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -48,7 +48,7 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc, pl: PRope) = 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, d.t, OnUnknown) list.r = pl genAssignment(p, d, list, {}) # no need for deep copying else: @@ -137,6 +137,67 @@ proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) = if i < length - 1: app(pl, ", ") fixupCall(p, le, ri, d, pl) +proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = + + proc getRawProcType(p: BProc, t: PType): PRope = + var d = copyType(t, t.owner, false) + d.callConv = ccDefault + result = getTypeDesc(p.module, d) + + proc addComma(r: PRope): PRope = + result = if r == nil: r else: con(r, ", ") + + const CallPattern = "$1.ClEnv? $1.ClPrc($3$1.ClEnv) : (($4)($1.ClPrc))($2)" + var op: TLoc + initLocExpr(p, ri.sons[0], op) + var pl: PRope + var typ = ri.sons[0].typ + assert(typ.kind == tyProc) + var length = sonsLen(ri) + for i in countup(1, length - 1): + assert(sonsLen(typ) == sonsLen(typ.n)) + if i < sonsLen(typ): + assert(typ.n.sons[i].kind == nkSym) + app(pl, genArg(p, ri.sons[i], typ.n.sons[i].sym)) + else: + app(pl, genArgNoParam(p, ri.sons[i])) + if i < length - 1: app(pl, ", ") + + template genCallPattern = + appf(p.s[cpsStmts], CallPattern, op.r, pl, pl.addComma, rawProc) + + let rawProc = getRawProcType(p, typ) + if typ.sons[0] != nil: + if isInvalidReturnType(typ.sons[0]): + if sonsLen(ri) > 1: app(pl, ", ") + # beware of 'result = p(result)'. We may need to allocate a temporary: + if d.k in {locTemp, locNone} or not leftAppearsOnRightSide(le, ri): + # Great, we can use 'd': + if d.k == locNone: getTemp(p, typ.sons[0], d) + elif d.k notin {locExpr, locTemp} and not hasNoInit(ri): + # reset before pass as 'result' var: + resetLoc(p, d) + app(pl, addrLoc(d)) + genCallPattern() + appf(p.s[cpsStmts], ";$n") + else: + var tmp: TLoc + getTemp(p, typ.sons[0], tmp) + app(pl, addrLoc(tmp)) + genCallPattern() + appf(p.s[cpsStmts], ";$n") + genAssignment(p, d, tmp, {}) # no need for deep copying + else: + 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) + list.r = ropef(CallPattern, op.r, pl, pl.addComma, rawProc) + genAssignment(p, d, list, {}) # no need for deep copying + else: + genCallPattern() + appf(p.s[cpsStmts], ";$n") + proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) = var op, a: TLoc initLocExpr(p, ri.sons[0], op) @@ -224,7 +285,9 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) = appf(p.s[cpsStmts], ";$n") proc genCall(p: BProc, e: PNode, d: var TLoc) = - if e.sons[0].kind == nkSym and sfInfixCall in e.sons[0].sym.flags and + if e.sons[0].typ.callConv == ccClosure: + genClosureCall(p, nil, e, d) + elif e.sons[0].kind == nkSym and sfInfixCall in e.sons[0].sym.flags and e.len >= 2: genInfixCall(p, nil, e, d) elif e.sons[0].kind == nkSym and sfNamedParamCall in e.sons[0].sym.flags: @@ -235,7 +298,9 @@ proc genCall(p: BProc, e: PNode, d: var TLoc) = if d.s == onStack and containsGarbageCollectedRef(d.t): keepAlive(p, d) proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) = - if ri.sons[0].kind == nkSym and sfInfixCall in ri.sons[0].sym.flags and + if ri.sons[0].typ.callConv == ccClosure: + genClosureCall(p, le, ri, d) + elif ri.sons[0].kind == nkSym and sfInfixCall in ri.sons[0].sym.flags and ri.len >= 2: genInfixCall(p, le, ri, d) elif ri.sons[0].kind == nkSym and sfNamedParamCall in ri.sons[0].sym.flags: diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index ef412d753..6a8156220 100755 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -244,7 +244,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = appcg(p, cpsStmts, "#unsureAsgnRef((void**) $1, #copyString($2));$n", [addrLoc(dest), rdLoc(src)]) if needToKeepAlive in flags: keepAlive(p, dest) - of tyTuple, tyObject: + of tyTuple, tyObject, tyProc: # XXX: check for subtyping? if needsComplexAssignment(dest.t): genGenericAsgn(p, dest, src, flags) @@ -274,7 +274,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = [rdLoc(dest), rdLoc(src), toRope(getSize(dest.t))]) else: appcg(p, cpsStmts, "$1 = $2;$n", [rdLoc(dest), rdLoc(src)]) - of tyPtr, tyPointer, tyChar, tyBool, tyProc, tyEnum, tyCString, + of tyPtr, tyPointer, tyChar, tyBool, tyEnum, tyCString, tyInt..tyFloat128, tyRange: appcg(p, cpsStmts, "$1 = $2;$n", [rdLoc(dest), rdLoc(src)]) else: InternalError("genAssignment(" & $ty.kind & ')') @@ -1308,7 +1308,6 @@ proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) = [rdLoc(a)])) proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = - # XXX we don't generate keep alive info here var a: TLoc initLocExpr(p, n.sons[0], a) putIntoDest(p, d, skipTypes(n.typ, abstractVar), @@ -1515,6 +1514,28 @@ proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) = [rdLoc(d), mangleRecFieldName(t.n.sons[i].sym, t)]) expr(p, it, rec) +proc IsConstClosure(n: PNode): bool {.inline.} = + result = n.sons[0].kind == nkSym and isRoutine(n.sons[0].sym) and + n.sons[1].kind == nkNilLit + +proc genClosure(p: BProc, n: PNode, d: var TLoc) = + assert n.kind == nkClosure + + if IsConstClosure(n): + inc(p.labels) + var tmp = con("LOC", toRope(p.labels)) + appf(p.module.s[cfsData], "NIM_CONST $1 $2 = $3;$n", + [getTypeDesc(p.module, n.typ), tmp, genConstExpr(p, n)]) + putIntoDest(p, d, n.typ, tmp) + else: + var tmp, a, b: TLoc + initLocExpr(p, n.sons[0], a) + initLocExpr(p, n.sons[1], b) + getTemp(p, n.typ, tmp) + appcg(p, cpsStmts, "$1.ClPrc = $2; $1.ClEnv = $3;$n", + tmp.rdLoc, a.rdLoc, b.rdLoc) + putLocIntoDest(p, d, tmp) + proc genArrayConstr(p: BProc, n: PNode, d: var TLoc) = var arr: TLoc if not handleConstExpr(p, n, d): @@ -1705,6 +1726,7 @@ proc expr(p: BProc, e: PNode, d: var TLoc) = if sym.loc.r == nil or sym.loc.t == nil: InternalError(e.info, "expr: proc not init " & sym.name.s) putLocIntoDest(p, d, sym.loc) + of nkClosure: genClosure(p, e, d) of nkMetaNode: expr(p, e.sons[0], d) else: InternalError(e.info, "expr(" & $e.kind & "); unknown node kind") @@ -1751,7 +1773,7 @@ proc genConstExpr(p: BProc, n: PNode): PRope = var cs: TBitSet toBitSet(n, cs) result = genRawSetData(cs, int(getSize(n.typ))) - of nkBracket, nkPar: + of nkBracket, nkPar, nkClosure: var t = skipTypes(n.typ, abstractInst) if t.kind == tySequence: result = genConstSeq(p, n, t) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index ad1b5646f..8e7b05c0f 100755 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -57,13 +57,24 @@ proc genSingleVar(p: BProc, a: PNode) = genLineDir(p, a) loadInto(p, a.sons[0], a.sons[2], v.loc) +proc genClosureVar(p: BProc, a: PNode) = + var immediateAsgn = a.sons[2].kind != nkEmpty + if immediateAsgn: + var v: TLoc + initLocExpr(p, a.sons[0], v) + genLineDir(p, a) + loadInto(p, a.sons[0], a.sons[2], v) + proc genVarStmt(p: BProc, n: PNode) = for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] if a.kind == nkCommentStmt: continue - if a.kind == nkIdentDefs: - assert(a.sons[0].kind == nkSym) - genSingleVar(p, a) + if a.kind == nkIdentDefs: + # can be a lifted var nowadays ... + if a.sons[0].kind == nkSym: + genSingleVar(p, a) + else: + genClosureVar(p, a) else: genVarTuple(p, a) @@ -704,7 +715,7 @@ proc genStmts(p: BProc, t: PNode) = of nkReturnStmt: genReturnStmt(p, t) of nkBreakStmt: genBreakStmt(p, t) of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkPostfix, nkCommand, - nkCallStrLit: + nkCallStrLit, nkClosure: genLineDir(p, t) initLocExpr(p, t, a) of nkAsgn: genAsgn(p, t, fastAsgn=false) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index a2faf3cbf..c7002954a 100755 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -94,7 +94,7 @@ proc mapType(typ: PType): TCTypeKind = else: result = ctPtr of tyPointer: result = ctPtr of tySequence: result = ctNimSeq - of tyProc: result = ctProc + of tyProc: result = if typ.callConv != ccClosure: ctProc else: ctStruct of tyString: result = ctNimStr of tyCString: result = ctCString of tyInt..tyFloat128: @@ -215,7 +215,7 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var PRope, appff(params, " Result", " @Result", []) if t.callConv == ccClosure: if params != nil: app(params, ", ") - app(params, "void* ClPart") + app(params, "void* ClEnv") if tfVarargs in t.flags: if params != nil: app(params, ", ") app(params, "...") @@ -331,7 +331,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode, appf(result, "} $1;$n", [uname]) of nkSym: field = n.sym - assert(field.ast == nil) + #assert(field.ast == nil) sname = mangleRecFieldName(field, rectype) if accessExpr != nil: ae = ropef("$1.$2", [accessExpr, sname]) else: ae = sname @@ -436,9 +436,10 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var TIntSet): PRope = if t.callConv != ccClosure: # procedure vars may need a closure! appf(m.s[cfsTypes], "typedef $1_PTR($2, $3) $4;$n", [toRope(CallingConvToStr[t.callConv]), rettype, result, desc]) - else: - appf(m.s[cfsTypes], "typedef struct $1 {$n" & - "N_CDECL_PTR($2, PrcPart) $3;$n" & "void* ClPart;$n};$n", + else: + appf(m.s[cfsTypes], "typedef struct {$n" & + "N_CDECL_PTR($2, ClPrc) $3;$n" & + "void* ClEnv;$n} $1;$n", [result, rettype, desc]) of tySequence: # we cannot use getTypeForward here because then t would be associated @@ -673,7 +674,8 @@ proc genTupleInfo(m: BModule, typ: PType, name: PRope) = var tmp2 = getNimNode(m) appf(m.s[cfsTypeInit3], "$1[$2] = &$3;$n", [tmp, toRope(i), tmp2]) appf(m.s[cfsTypeInit3], "$1.kind = 1;$n" & - "$1.offset = offsetof($2, Field$3);$n" & "$1.typ = $4;$n" & + "$1.offset = offsetof($2, Field$3);$n" & + "$1.typ = $4;$n" & "$1.name = \"Field$3\";$n", [tmp2, getTypeDesc(m, typ), toRope(i), genTypeInfo(m, a)]) appf(m.s[cfsTypeInit3], "$1.len = $2; $1.kind = 2; $1.sons = &$3[0];$n", @@ -736,6 +738,14 @@ proc genSetInfo(m: BModule, typ: PType, name: PRope) = proc genArrayInfo(m: BModule, typ: PType, name: PRope) = genTypeInfoAuxBase(m, typ, name, genTypeInfo(m, typ.sons[1])) +proc fakeClosureType(owner: PSym): PType = + # we generate the same RTTI as for a tuple[pointer, ref tuple[]] + result = newType(tyTuple, owner) + result.addSon(newType(tyPointer, owner)) + var r = newType(tyRef, owner) + r.addSon(newType(tyTuple, owner)) + result.addSon(r) + proc genTypeInfo(m: BModule, typ: PType): PRope = var t = getUniqueType(typ) # gNimDat contains all the type information nowadays: @@ -750,9 +760,13 @@ proc genTypeInfo(m: BModule, typ: PType): PRope = if dataGenerated: return case t.kind of tyEmpty: result = toRope"0" - of tyPointer, tyProc, tyBool, tyChar, tyCString, tyString, tyInt..tyFloat128, - tyVar: + of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyFloat128, tyVar: genTypeInfoAuxBase(gNimDat, t, result, toRope"0") + of tyProc: + if t.callConv != ccClosure: + genTypeInfoAuxBase(gNimDat, t, result, toRope"0") + else: + genTupleInfo(gNimDat, fakeClosureType(t.owner), result) of tyRef, tyPtr, tySequence, tyRange: genTypeInfoAux(gNimDat, t, result) of tyArrayConstr, tyArray: genArrayInfo(gNimDat, t, result) of tySet: genSetInfo(gNimDat, t, result) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index d60f11639..9784b21bb 100755 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -75,8 +75,10 @@ proc fillLoc(a: var TLoc, k: TLocKind, typ: PType, r: PRope, s: TStorageLoc) = if a.r == nil: a.r = r proc isSimpleConst(typ: PType): bool = - result = skipTypes(typ, abstractVar).kind notin - {tyTuple, tyObject, tyArray, tyArrayConstr, tySet, tySequence} + let t = skipTypes(typ, abstractVar) + result = t.kind notin + {tyTuple, tyObject, tyArray, tyArrayConstr, tySet, tySequence} and not + (t.kind == tyProc and t.callConv == ccClosure) proc useHeader(m: BModule, sym: PSym) = if lfHeader in sym.loc.Flags: @@ -187,7 +189,7 @@ proc rdLoc(a: TLoc): PRope = proc addrLoc(a: TLoc): PRope = result = a.r - if lfIndirect notin a.flags and mapType(a.t) != ctArray: + if lfIndirect notin a.flags and mapType(a.t) != ctArray: result = con("&", result) proc rdCharLoc(a: TLoc): PRope = @@ -196,7 +198,7 @@ proc rdCharLoc(a: TLoc): PRope = if skipTypes(a.t, abstractRange).kind == tyChar: result = ropef("((NU8)($1))", [result]) -proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc, +proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc, takeAddr: bool) = case analyseObjectWithTypeField(t) of frNone: @@ -223,11 +225,12 @@ type proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) -const - complexValueType = {tyArray, tyArrayConstr, tySet, tyTuple, tyObject} +proc isComplexValueType(t: PType): bool {.inline.} = + result = t.kind in {tyArray, tyArrayConstr, tySet, tyTuple, tyObject} or + (t.kind == tyProc and t.callConv == ccClosure) proc zeroVar(p: BProc, loc: TLoc, containsGCref: bool) = - if skipTypes(loc.t, abstractVarRange).Kind notin ComplexValueType: + if not isComplexValueType(skipTypes(loc.t, abstractVarRange)): if containsGcref and p.WithInLoop > 0: appf(p.s[cpsInit], "$1 = 0;$n", [rdLoc(loc)]) var nilLoc: TLoc @@ -249,8 +252,8 @@ proc zeroVar(p: BProc, loc: TLoc, containsGCref: bool) = [addrLoc(loc), rdLoc(loc)]) genObjectInit(p, cpsStmts, loc.t, loc, true) -proc zeroTemp(p: BProc, loc: TLoc) = - if skipTypes(loc.t, abstractVarRange).Kind notin complexValueType: +proc zeroTemp(p: BProc, loc: TLoc) = + if not isComplexValueType(skipTypes(loc.t, abstractVarRange)): appf(p.s[cpsStmts], "$1 = 0;$n", [rdLoc(loc)]) when false: var nilLoc: TLoc @@ -313,7 +316,7 @@ proc keepAlive(p: BProc, toKeepAlive: TLoc) = result.s = OnStack result.flags = {} - if skipTypes(toKeepAlive.t, abstractVarRange).Kind notin complexValueType: + if not isComplexValueType(skipTypes(toKeepAlive.t, abstractVarRange)): appf(p.s[cpsStmts], "$1 = $2;$n", [rdLoc(result), rdLoc(toKeepAlive)]) else: appcg(p, cpsStmts, @@ -571,6 +574,15 @@ proc initFrame(p: BProc, procname, filename: PRope): PRope = proc deinitFrame(p: BProc): PRope = result = ropecg(p.module, "#popFrame();$n") +proc closureSetup(p: BProc, prc: PSym) = + if prc.typ.callConv != ccClosure: return + # prc.ast[paramsPos].last contains the type we're after: + var env = lastSon(prc.ast[paramsPos]).sym + assignLocalVar(p, env) + # generate cast assignment: + appcg(p, cpsStmts, "$1 = ($2) ClEnv;$n", rdLoc(env.loc), + getTypeDesc(p.module, env.typ)) + proc genProcAux(m: BModule, prc: PSym) = var p = newProc(prc, m) var header = genProcHeader(m, prc) @@ -594,6 +606,7 @@ proc genProcAux(m: BModule, prc: PSym) = for i in countup(1, sonsLen(prc.typ.n) - 1): var param = prc.typ.n.sons[i].sym assignParam(p, param) + closureSetup(p, prc) genStmts(p, prc.getBody) # modifies p.locals, p.init, etc. var generatedProc: PRope if sfPure in prc.flags: diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 8aa490bbb..4a2a8997c 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -9,34 +9,46 @@ # This include file implements lambda lifting for the transformator. -# - Things to consider: Does capturing of 'result' work? (unknown) -# - Do generic inner procs work? (should) -# - Does nesting of closures work? (not yet) -# - Test that iterators within closures work etc. - const procDefs = {nkLambda, nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkConverterDef} -proc indirectAccess(a, b: PSym): PNode = +proc indirectAccess(a, b: PSym, info: TLineInfo): PNode = # returns a[].b as a node - var x = newSymNode(a) - var y = newSymNode(b) - var deref = newNodeI(nkHiddenDeref, x.info) + let x = newSymNode(a) + var deref = newNodeI(nkHiddenDeref, info) deref.typ = x.typ.sons[0] + + let field = getSymFromList(deref.typ.n, b.name) addSon(deref, x) - result = newNodeI(nkDotExpr, x.info) + result = newNodeI(nkDotExpr, info) addSon(result, deref) - addSon(result, y) - result.typ = y.typ + addSon(result, newSymNode(field)) + result.typ = field.typ + +type + TCapture = seq[PSym] -proc Incl(container: PNode, s: PSym) = - for x in container: - if x.sym.id == s.id: return - container.add(newSymNode(s)) +proc Capture(cap: var TCapture, s: PSym) = + for x in cap: + if x.name.id == s.name.id: return + cap.add(s) -proc gatherVars(c: PTransf, n: PNode, owner: PSym, container: PNode) = - # gather used vars for closure generation +proc captureToTuple(cap: TCapture, owner: PSym): PType = + result = newType(tyTuple, owner) + result.n = newNodeI(nkRecList, owner.info) + for s in cap: + var field = newSym(skField, s.name, s.owner) + + let typ = s.typ + field.typ = typ + field.position = sonsLen(result) + + addSon(result.n, newSymNode(field)) + addSon(result, typ) + +proc gatherVars(c: PTransf, n: PNode, outerProc: PSym, cap: var TCapture) = + # gather used vars for closure generation into 'cap' case n.kind of nkSym: var s = n.sym @@ -45,82 +57,88 @@ proc gatherVars(c: PTransf, n: PNode, owner: PSym, container: PNode) = of skVar, skLet: found = sfGlobal notin s.flags of skTemp, skForVar, skParam, skResult: found = true else: nil - if found and owner.id != s.owner.id: - incl(container, s) + if found and outerProc.id == s.owner.id: + #echo "captured: ", s.name.s + Capture(cap, s) of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil - else: + else: for i in countup(0, sonsLen(n) - 1): - gatherVars(c, n.sons[i], owner, container) + gatherVars(c, n.sons[i], outerProc, cap) -proc replaceVars(c: PTransf, n: PNode, owner, env: PSym) = +proc replaceVars(c: PTransf, n: PNode, outerProc, env: PSym) = for i in countup(0, safeLen(n) - 1): - if n.kind == nkSym: - let s = n.sym + let a = n.sons[i] + if a.kind == nkSym: + let s = a.sym var found = false case s.kind of skVar, skLet: found = sfGlobal notin s.flags of skTemp, skForVar, skParam, skResult: found = true else: nil - if found and owner.id != s.owner.id: + if found and outerProc.id == s.owner.id: # access through the closure param: - n.sons[i] = indirectAccess(env, s) + n.sons[i] = indirectAccess(env, s, n.info) else: - replaceVars(c, n.sons[i], owner, env) - + replaceVars(c, a, outerProc, env) + +proc addFormalParam(routine: PType, param: PSym) = + addSon(routine, param.typ) + addSon(routine.n, newSymNode(param)) + proc addFormalParam(routine: PSym, param: PSym) = - addSon(routine.typ, param.typ) + #addFormalParam(routine.typ, param) addSon(routine.ast.sons[paramsPos], newSymNode(param)) -proc isInnerProc(s, owner: PSym): bool {.inline.} = +proc isInnerProc(s, outerProc: PSym): bool {.inline.} = result = s.kind in {skProc, skMacro, skIterator, skMethod, skConverter} and - s.owner.id == owner.id and not isGenericRoutine(s) + s.owner.id == outerProc.id and not isGenericRoutine(s) and + s.typ.callConv == ccClosure -proc searchForInnerProcs(c: PTransf, n: PNode, owner: PSym, container: PNode) = +proc searchForInnerProcs(c: PTransf, n: PNode, outerProc: PSym, + cap: var TCapture) = case n.kind of nkSym: let s = n.sym - if isInnerProc(s, owner): - gatherVars(c, s.getBody, owner, container) + if isInnerProc(s, outerProc): + gatherVars(c, s.getBody, outerProc, cap) of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil else: for i in 0.. <len(n): - searchForInnerProcs(c, n.sons[i], owner, container) + searchForInnerProcs(c, n.sons[i], outerProc, cap) -proc makeClosure(c: PTransf, prc, env: PSym): PNode = - var tup = newType(tyTuple, c.module) - tup.addson(prc.typ) - tup.addson(env.typ) - result = newNodeIT(nkPar, prc.info, tup) +proc makeClosure(c: PTransf, prc, env: PSym, info: TLineInfo): PNode = + result = newNodeIT(nkClosure, info, prc.typ) result.add(newSymNode(prc)) result.add(newSymNode(env)) -proc transformInnerProcs(c: PTransf, n: PNode, owner, env: PSym) = +proc transformInnerProcs(c: PTransf, n: PNode, outerProc, env: PSym) = case n.kind of nkSym: let innerProc = n.sym - if isInnerProc(innerProc, owner): + if isInnerProc(innerProc, outerProc): # inner proc could capture outer vars: var param = newTemp(c, env.typ, n.info) param.kind = skParam addFormalParam(innerProc, param) # 'anon' should be replaced by '(anon, env)': IdNodeTablePut(c.transCon.mapping, innerProc, - makeClosure(c, innerProc, env)) - + makeClosure(c, innerProc, env, n.info)) # access all non-local vars through the 'env' param: var body = innerProc.getBody - replaceVars(c, body, innerProc, param) + # XXX does not work with recursion! + replaceVars(c, body, outerProc, param) + innerProc.ast.sons[bodyPos] = body of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit: nil else: for i in 0.. <len(n): - transformInnerProcs(c, n.sons[i], owner, env) + transformInnerProcs(c, n.sons[i], outerProc, env) proc newCall(a, b: PSym): PNode = result = newNodeI(nkCall, a.info) result.add newSymNode(a) result.add newSymNode(b) -proc createEnvStmt(c: PTransf, varList: PNode, env: PSym): PTransNode = +proc createEnvStmt(c: PTransf, varList: TCapture, env: PSym): PTransNode = # 'varlist' can contain parameters or variables. We don't eliminate yet # local vars that end up in an environment. This could even be a for loop # var! @@ -129,17 +147,33 @@ proc createEnvStmt(c: PTransf, varList: PNode, env: PSym): PTransNode = addVar(v, newSymNode(env)) result.add(v.ptransNode) # add 'new' statement: - result.add(newCall(getSysSym"new", env).ptransnode) + result.add(newCall(getSysSym"internalNew", env).ptransnode) # add assignment statements: for v in varList: - assert v.kind == nkSym - let fieldAccess = indirectAccess(env, v.sym) - if v.sym.kind == skParam: + let fieldAccess = indirectAccess(env, v, env.info) + if v.kind == skParam: # add ``env.param = param`` - result.add(newAsgnStmt(c, fieldAccess, v.ptransNode)) - IdNodeTablePut(c.transCon.mapping, v.sym, fieldAccess) + result.add(newAsgnStmt(c, fieldAccess, newSymNode(v).ptransNode)) + IdNodeTablePut(c.transCon.mapping, v, fieldAccess) + +proc transformProcFin(c: PTransf, n: PNode, s: PSym): PTransNode = + # to be safe: XXX this a mystery how it could ever happen that: s.ast != n. + s.ast.sons[bodyPos] = n.sons[bodyPos] + if n.kind == nkMethodDef: methodDef(s, false) + # should 's' be replaced by a tuple ('s', env)? + var tc = c.transCon + var repl: PNode = nil + while tc != nil: + repl = IdNodeTableGet(tc.mapping, s) + if repl != nil: break + tc = tc.next + if repl != nil: + result = PTransNode(repl) + else: + result = PTransNode(n) + proc transformProc(c: PTransf, n: PNode): PTransNode = # don't process generics: if n.sons[genericParamsPos].kind != nkEmpty: @@ -147,34 +181,32 @@ proc transformProc(c: PTransf, n: PNode): PTransNode = var s = n.sons[namePos].sym var body = s.getBody + if body.kind == nkEmpty: + return PTransNode(n) + if not containsNode(body, procDefs): # fast path: no inner procs, so no closure needed: n.sons[bodyPos] = PNode(transform(c, body)) - if n.kind == nkMethodDef: methodDef(s, false) - return PTransNode(n) + return transformProcFin(c, n, s) - var closure = newNodeI(nkRecList, n.info) - searchForInnerProcs(c, body, s, closure) + # create environment: + var cap: TCapture = @[] + searchForInnerProcs(c, body, s, cap) - if closure.len == 0: + if cap.len == 0: # fast path: no captured variables, so no closure needed: n.sons[bodyPos] = PNode(transform(c, body)) - if n.kind == nkMethodDef: methodDef(s, false) - return PTransNode(n) + return transformProcFin(c, n, s) - # create environment: - var envDesc = newType(tyObject, s) - envDesc.n = closure - addSon(envDesc, nil) # no super class var envType = newType(tyRef, s) - addSon(envType, envDesc) + addSon(envType, captureToTuple(cap, s)) - # XXX currently we always do a heap allocation. A simple escape analysis - # could turn the closure into a stack allocation. Later versions will - # implement that. + # Currently we always do a heap allocation. A simple escape analysis + # could turn the closure into a stack allocation. Later versions might + # implement that. This would require backend changes too though. var envSym = newTemp(c, envType, s.info) - var newBody = createEnvStmt(c, closure, envSym) + var newBody = createEnvStmt(c, cap, envSym) # modify any local proc to gain a new parameter; this also creates the # mapping entries that turn (localProc) into (localProc, env): transformInnerProcs(c, body, s, envSym) @@ -183,19 +215,18 @@ proc transformProc(c: PTransf, n: PNode): PTransNode = # Careful this transforms the inner procs too! newBody.add(transform(c, body)) n.sons[bodyPos] = newBody.pnode - if n.kind == nkMethodDef: methodDef(s, false) - result = newBody + result = transformProcFin(c, n, s) -proc generateThunk(c: PTransf, prc: PNode, closure: PType): PNode = +proc generateThunk(c: PTransf, prc: PNode, dest: PType): PNode = ## Converts 'prc' into '(thunk, nil)' so that it's compatible with ## a closure. # XXX we hack around here by generating a 'cast' instead of a proper thunk. - result = newNodeIT(nkPar, prc.info, closure) - var conv = newNodeIT(nkHiddenStdConv, prc.info, closure.sons[0]) + result = newNodeIT(nkClosure, prc.info, dest) + var conv = newNodeIT(nkHiddenStdConv, prc.info, dest) conv.add(emptyNode) conv.add(prc) result.add(conv) - result.add(newNodeIT(nkNilLit, prc.info, closure.sons[1])) + result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil))) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index e1deb6e35..43ab7a192 100755 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -93,7 +93,7 @@ type errAssertionFailed, errCannotGenerateCodeForX, errXRequiresOneArgument, errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate, errXhasSideEffects, errIteratorExpected, errLetNeedsInit, - errThreadvarCannotInit, errWrongSymbolX, + errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, errUser, warnCannotOpenFile, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, @@ -325,6 +325,7 @@ const errLetNeedsInit: "'let' symbol requires an initialization", errThreadvarCannotInit: "a thread var cannot be initialized explicitly", errWrongSymbolX: "usage of \'$1\' is a user-defined error", + errIllegalCaptureX: "illegal capture '$1'", errUser: "$1", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 6fb5da2a9..dd62363e1 100755 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -339,7 +339,7 @@ proc lsub(n: PNode): int = of nkHiddenAddr, nkHiddenDeref: result = lsub(n.sons[0]) of nkCommand: result = lsub(n.sons[0]) + lcomma(n, 1) + 1 of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(n) + 3 - of nkPar, nkCurly, nkBracket: result = lcomma(n) + 2 + of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(n) + 2 of nkTableConstr: result = if n.len > 0: lcomma(n) + 2 else: len("{:}") of nkSymChoice: result = lsons(n) + len("()") + sonsLen(n) - 1 @@ -759,7 +759,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = if i > 0: put(g, tkOpr, "|") gsub(g, n.sons[i], c) put(g, tkParRi, ")") - of nkPar: + of nkPar, nkClosure: put(g, tkParLe, "(") gcomma(g, n, c) put(g, tkParRi, ")") diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 32244af85..24161e85e 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -50,6 +50,10 @@ proc inlineConst(n: PNode, s: PSym): PNode {.inline.} = result.typ = s.typ result.info = n.info +proc illegalCapture(s: PSym): bool {.inline.} = + result = skipTypes(s.typ, abstractInst).kind in {tyVar, tyOpenArray} or + s.kind == skResult + proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = case s.kind of skProc, skMethod, skIterator, skConverter: @@ -83,10 +87,16 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = result = newSymNode(s, n.info) of skMacro: result = semMacroExpr(c, n, s) of skTemplate: result = semTemplateExpr(c, n, s) - of skVar, skLet, skResult: + of skVar, skLet, skResult, skParam: markUsed(n, s) # if a proc accesses a global variable, it is not side effect free: - if sfGlobal in s.flags: incl(c.p.owner.flags, sfSideEffect) + if sfGlobal in s.flags: + incl(c.p.owner.flags, sfSideEffect) + elif s.owner != c.p.owner and s.owner.kind != skModule: + c.p.owner.typ.callConv = ccClosure + if illegalCapture(s) or c.p.next.owner != s.owner: + # Currently captures are restricted to a single level of nesting: + GlobalError(n.info, errIllegalCaptureX, s.name.s) result = newSymNode(s, n.info) of skGenericParam: if s.ast == nil: InternalError(n.info, "no default for") diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index ab8081031..424950056 100755 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -644,8 +644,6 @@ proc semLambda(c: PContext, n: PNode): PNode = else: s.typ = newTypeS(tyProc, c) addSon(s.typ, nil) - # no! do a proper analysis to determine calling convention - when false: s.typ.callConv = ccClosure if n.sons[pragmasPos].kind != nkEmpty: pragma(c, s, n.sons[pragmasPos], lambdaPragmas) s.options = gOptions @@ -697,9 +695,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, # open for parameters if proto == nil: s.typ.callConv = lastOptionEntry(c).defaultCC - when false: - # do a proper analysis here: - if c.p.owner.kind != skModule: s.typ.callConv = ccClosure # add it here, so that recursive procs are possible: # -2 because we have a scope open for parameters if kind in OverloadableSyms: diff --git a/compiler/semthreads.nim b/compiler/semthreads.nim index 59ff4e652..f542e04d6 100755 --- a/compiler/semthreads.nim +++ b/compiler/semthreads.nim @@ -360,7 +360,7 @@ proc analyse(c: PProcCtx, n: PNode): TThreadOwner = if n.sons[0].kind != nkEmpty: result = analyse(c, n.sons[0]) else: result = toVoid of nkAsmStmt, nkPragma, nkIteratorDef, nkProcDef, nkMethodDef, - nkConverterDef, nkMacroDef, nkTemplateDef: + nkConverterDef, nkMacroDef, nkTemplateDef, nkLambda: result = toVoid of nkExprColonExpr: result = analyse(c, n.sons[1]) diff --git a/compiler/transf.nim b/compiler/transf.nim index 51188928c..be8f7aeb5 100755 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -15,6 +15,7 @@ # * performes contant folding # * converts "continue" to "break" # * introduces method dispatchers +# * performs lambda lifting for closure support import intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os, @@ -355,6 +356,8 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode = if n.sons[0].kind == a or n.sons[0].kind == b: # addr ( deref ( x )) --> x result = PTransNode(n.sons[0].sons[0]) + +include lambdalifting proc transformConv(c: PTransf, n: PNode): PTransNode = # numeric types need range checks: @@ -428,6 +431,11 @@ proc transformConv(c: PTransf, n: PNode): PTransNode = result[0] = transform(c, n.sons[1]) else: result = transform(c, n.sons[1]) + of tyProc: + if dest.callConv == ccClosure and source.callConv == ccDefault: + result = generateThunk(c, n.sons[1], dest).ptransnode + else: + result = transformSons(c, n) of tyGenericParam, tyOrdinal: result = transform(c, n.sons[1]) # happens sometimes for generated assignments, etc. @@ -510,8 +518,6 @@ proc getMagicOp(call: PNode): TMagic = else: result = mNone -include lambdalifting - proc transformCase(c: PTransf, n: PNode): PTransNode = # removes `elif` branches of a case stmt # adds ``else: nil`` if needed for the code generator diff --git a/compiler/trees.nim b/compiler/trees.nim index f393bfc66..57acfba8a 100755 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -115,7 +115,7 @@ proc isDeepConstExpr*(n: PNode): bool = result = true of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv: result = isDeepConstExpr(n.sons[1]) - of nkCurly, nkBracket, nkPar: + of nkCurly, nkBracket, nkPar, nkClosure: for i in 0 .. <n.len: if not isDeepConstExpr(n.sons[i]): return false result = true diff --git a/compiler/types.nim b/compiler/types.nim index 42acae831..d2359faae 100755 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -276,16 +276,17 @@ proc analyseObjectWithTypeField(t: PType): TTypeFieldResult = var marker = InitIntSet() result = analyseObjectWithTypeFieldAux(t, marker) -proc isGBCRef(t: PType): bool = - result = t.kind in GcTypeKinds +proc isGCRef(t: PType): bool = + result = t.kind in GcTypeKinds or + (t.kind == tyProc and t.callConv == ccClosure) 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, isGBCRef) + result = searchTypeFor(typ, isGCRef) proc isTyRef(t: PType): bool = - result = t.kind == tyRef + result = t.kind == tyRef or (t.kind == tyProc and t.callConv == ccClosure) proc containsTyRef*(typ: PType): bool = # returns true if typ contains a 'ref' @@ -332,6 +333,8 @@ proc canFormAcycleAux(marker: var TIntSet, typ: PType, startId: int): bool = nil proc canFormAcycle(typ: PType): bool = + # XXX as I expect cycles introduced by closures are very rare, we pretend + # they can't happen here. var marker = InitIntSet() result = canFormAcycleAux(marker, typ, typ.id) diff --git a/doc/intern.txt b/doc/intern.txt index 550976d6f..748b648fb 100755 --- a/doc/intern.txt +++ b/doc/intern.txt @@ -373,7 +373,7 @@ Design A ``closure`` proc var can call ordinary procs of the default Nimrod calling convention. But not the other way round! A closure is implemented as a -``tuple[prc, data]``. ``data`` can be nil implying a call without a closure. +``tuple[prc, env]``. ``env`` can be nil implying a call without a closure. This means that a call through a closure generates an ``if`` but the interoperability is worth the cost of the ``if``. Thunk generation would be possible too, but it's slightly more effort to implement. @@ -381,6 +381,18 @@ possible too, but it's slightly more effort to implement. Tests with GCC on Amd64 showed that it's really beneficical if the 'environment' pointer is passed as the last argument, not as the first argument. +Proper thunk generation is harder because the proc that is to wrap +could stem from a complex expression: + +.. code-block:: nimrod + receivesClosure(returnsDefaultCC[i]) + +A thunk would need to call 'returnsDefaultCC[i]' somehow and that would require +an *additional* closure generation... Ok, not really, but it requires to pass +the function to call. So we'd end up with 2 indirect calls instead of one. +Another much more severe problem which this solution is that it's not GC-safe +to pass a proc pointer around via a generic ``ref`` type. + Example code: @@ -492,3 +504,19 @@ Accumulator echo a() + b() +Internals +--------- + +Lambda lifting is implemented as part of the ``transf`` pass. The ``transf`` +pass generates code to setup the environment and to pass it around. However, +this pass does not change the types! So we have some kind of mismatch here; on +the one hand the proc expression becomes an explicit tuple, on the other hand +the tyProc(ccClosure) type is not changed. For C code generation it's also +important the hidden formal param is ``void*`` and not something more +specialized. However the more specialized env type needs to passed to the +backend somehow. We deal with this by modifying ``s.ast[paramPos]`` to contain +the formal hidden parameter, but not ``s.typ``! + + + + diff --git a/doc/manual.txt b/doc/manual.txt index abdcc05d5..231aaf2ce 100755 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -1037,7 +1037,7 @@ A `procedural type`:idx: is internally a pointer to a procedure. ``nil`` is an allowed value for variables of a procedural type. Nimrod uses procedural types to achieve `functional`:idx: programming techniques. -Example: +Examples: .. code-block:: nimrod @@ -1051,12 +1051,30 @@ Example: forEach(printItem) # this will NOT work because calling conventions differ + +.. code-block:: nimrod + + type + TOnMouseMove = proc (x, y: int) {.closure.} + + proc onMouseMove(mouseX, mouseY: int) = + # has default calling convention + echo "x: ", mouseX, " y: ", mouseY + + proc setOnMouseMove(mouseMoveEvent: TOnMouseMove) = nil + + # ok, 'onMouseMove' has the default calling convention, which is compatible + # to 'closure': + setOnMouseMove(onMouseMove) + + A subtle issue with procedural types is that the calling convention of the procedure influences the type compatibility: procedural types are only -compatible if they have the same calling convention. +compatible if they have the same calling convention. As a special extension, +a procedure of the calling convention ``nimcall`` can be passed to a parameter +that expects a proc of the calling convention ``closure``. -Nimrod supports these `calling conventions`:idx:, which are all incompatible to -each other: +Nimrod supports these `calling conventions`:idx:\: `stdcall`:idx: This the stdcall convention as specified by Microsoft. The generated C @@ -1089,8 +1107,10 @@ each other: same as ``fastcall``, but only for C compilers that support ``fastcall``. `closure`:idx: - indicates that the procedure expects a context, a closure that needs - to be passed to the procedure. + indicates that the procedure has a hidden implicit parameter + (an *environment*). Proc vars that have the calling convention ``closure`` + take up two machine words: One for the proc pointer and another one for + the pointer to implicitely passed environment. `syscall`:idx: The syscall convention is the same as ``__syscall`` in C. It is used for @@ -1114,6 +1134,11 @@ of the following conditions hold: The rules' purpose is to prevent the case that extending a non-``procvar`` procedure with default parameters breaks client code. +The default calling convention is ``nimcall``, unless it is an inner proc ( +a proc inside of a proc). For an inner proc an analysis is performed wether it +accesses its environment. If it does so, it has the calling convention +``closure``, otherwise it has the calling convention ``nimcall``. + Distinct type ~~~~~~~~~~~~~ diff --git a/lib/system.nim b/lib/system.nim index 1e3509174..55abcaea6 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -83,6 +83,9 @@ proc new*[T](a: var ref T) {.magic: "New", noSideEffect.} ## creates a new object of type ``T`` and returns a safe (traced) ## reference to it in ``a``. +proc internalNew*[T](a: var ref T) {.magic: "New", noSideEffect.} + ## leaked implementation detail. Do not use. + proc new*[T](a: var ref T, finalizer: proc (x: ref T)) {. magic: "NewFinalize", noSideEffect.} ## creates a new object of type ``T`` and returns a safe (traced) diff --git a/todo.txt b/todo.txt index 6e238b811..b43357007 100755 --- a/todo.txt +++ b/todo.txt @@ -2,6 +2,13 @@ version 0.8.14 ============== - implement closures + - test evals.nim with closures + - deactivate lambda lifting for JS backend + - Test that iterators within closures work etc; test generics; + test recursion + - test constant closures + - 'closureEnv' magic for easy interfacing with C + - object {.pure, final.} does not work again! - bug: tsortdev does not run with native GC? - ``=`` should be overloadable; requires specialization for ``=``? @@ -45,8 +52,7 @@ Bugs result = forward(x) - bug: stress testing basic method example (eval example) - without ``-d:release`` leaks memory; good way to figure out how a - fixed amount of stack can hold an arbitrary number of GC roots! + without ``-d:release`` leaks memory? - bug: temp2.nim triggers weird compiler and except.nim bug - bug: negative array indexes fail to index check @@ -131,6 +137,8 @@ Low priority - warning for implicit openArray -> varargs conversion - implement explicit varargs; **but** ``len(varargs)`` problem remains! --> solve by implicit conversion from varargs to openarray +- implement closures that support nesting > 1 + Version 2 ========= @@ -166,6 +174,4 @@ Version 2 a full blown statement; a ``try`` expression might be a good idea to make error handling more light-weight people also want ``inc a; inc b`` - --> solved by providing an expr version of most control structures? - diff --git a/web/news.txt b/web/news.txt index 61f84b70f..35b10019e 100755 --- a/web/news.txt +++ b/web/news.txt @@ -2,7 +2,7 @@ News ==== -2012-XX-XX Version 0.8.14 released +2012-02-XX Version 0.8.14 released ================================== Version 0.8.14 has been released! Get it `here <download.html>`_. @@ -125,6 +125,8 @@ Compiler Additions for ``on|off`` switches in pragmas. In order to not break existing code, ``on`` and ``off`` are now aliases for ``true`` and ``false`` and declared in the system module. +- The compiler finally supports **closures**. This is a preliminary + implementation, which does not yet support nestings deeper than 1 level. Library Additions |