# # # The Nim Compiler # (c) Copyright 2018 Nim Contributors # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # This file implements closure iterator transformations. # The main idea is to split the closure iterator body to top level statements. # The body is split by yield statement. # # Example: # while a > 0: # echo "hi" # yield a # dec a # # Should be transformed to: # case :state # of 0: # if a > 0: # echo "hi" # :state = 1 # Next state # return a # yield # else: # :state = 2 # Next state # break :stateLoop # Proceed to the next state # of 1: # dec a # :state = 0 # Next state # break :stateLoop # Proceed to the next state # of 2: # :state = -1 # End of execution # else: # return # Lambdalifting treats :state variable specially, it should always end up # as the first field in env. Currently C codegen depends on this behavior. # One special subtransformation is nkStmtListExpr lowering. # Example: # template foo(): int = # yield 1 # 2 # # iterator it(): int {.closure.} = # if foo() == 2: # yield 3 # # If a nkStmtListExpr has yield inside, it has first to be lowered to: # yield 1 # :tmpSlLower = 2 # if :tmpSlLower == 2: # yield 3 # nkTryStmt Transformations: # If the iter has an nkTryStmt with a yield inside # - the closure iter is promoted to have exceptions (ctx.hasExceptions = true) # - exception table is created. This is a const array, where # `exceptionTable[i]` is exception landing state idx to which we should jump from state # `i` should exception be raised in state `i`. For all states in `try` block # the target state is `except` block. For all states in `except` block # the target state is `finally` block. For all other states there is no # target state (0, as the first state can never be except nor finally). # - env var :curExc is created, where "current" exception within the iterator is stored, # also finallies use it to decide their exit logic # - if there are finallies, env var :finallyPath is created. It contains exit state labels # for every finally level, and is changed in runtime in try, except, break, and return # nodes to control finally exit behavior. # - the iter body is wrapped into a # var :tmp: Exception # try: # ...body... # catch: # :state = exceptionTable[:state] # if :state == 0: raise # No state that could handle exception # :tmp = getCurrentException() # pushCurrentException(:tmp) # # nkReturnStmt within a try/except/finally now has to behave differently as we # want parent finallies to be executed before the return, thus it is # transformed to: # :tmpResult = returnValue (if return doesn't have a value, this is skipped) # :finallyPath[0] = 0 # Finally at the bottom should just exit # :finallyPath[N] = finallyNMinus1State # Next finally should exit to its parent # goto finallyNState (or -1 if not exists) # finallyN is the nearest finally # # Example: # # try: # yield 0 # raise ... # except: # yield 1 # return 3 # finally: # yield 2 # somethingElse() # # Is transformed to (yields are left in place for example simplicity, # in reality the code is subdivided even more, as described above): # # case :state # of 0: # Try # :finallyPath[LEVEL] = curExcLandingState # should exception occur our finally # # must jump to its landing # yield 0 # raise ... # :finallyPath[LEVEL] = 3 # Exception did not happen. Our finally can continue to state 3 # :state = 2 # And we continue to our finally # break :stateLoop # of 1: # Except # yield 1 # :tmpResult = 3 # Return # :finalyPath[LEVEL] = 0 # Configure finally path. # :state = 2 # Goto Finally # break :stateLoop # popCurrentException() # XXX: This is likely wrong, see #25031 # :state = 2 # What would happen should we not return # break :stateLoop # of 2: # Finally # yield 2 # if :finallyPath[LEVEL] == 0: # This node is created by `newEndFinallyNode` # if :curExc == nil: # :state = -1 # return result = :tmpResult # else: # raise # :state = :finallyPath[LEVEL] # Go to next state # break :stateLoop # of 3: # somethingElse() # :state = -1 # Exit # break :stateLoop # else: # return import ast, msgs, idents, renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos import std/tables when defined(nimPreviewSlimSystem): import std/assertions type FinallyTarget = object n: PNode # nkWhileStmt, nkBlock, nkFinally label: PNode # exit state for blocks and whiles (used by breaks), # or enter state for finallies (used by breaks and returns) State = object label: PNode # Int literal with state idx. It is filled after state split body: PNode excLandingState: PNode # label of exception landing state (except or finally) inlinable: bool deletable: bool Ctx = object g: ModuleGraph fn: PSym tmpResultSym: PSym # Used when we return, but finally has to interfere finallyPathSym: PSym curExcSym: PSym # Current exception externExcSym: PSym # Extern exception: what would getCurrentException() return outside of closure iter states: seq[State] # The resulting states. Label is int literal. finallyPathStack: seq[FinallyTarget] # Stack of split blocks, whiles and finallies stateLoopLabel: PSym # Label to break on, when jumping between states. tempVarId: int # unique name counter hasExceptions: bool # Does closure have yield in try? curExcLandingState: PNode curFinallyLevel: int idgen: IdGenerator varStates: Table[ItemId, int] # Used to detect if local variable belongs to multiple states finallyPathLen: PNode # int literal nullifyCurExc: PNode # Empty node, if no yields in tries restoreExternExc: PNode # Empty node, id no yields in tries const nkSkip = {nkEmpty..nkNilLit, nkTemplateDef, nkTypeSection, nkStaticStmt, nkCommentStmt, nkMixinStmt, nkBindStmt, nkTypeOfExpr} + procDefs localNotSeen = -1 localRequiresLifting = -2 proc newStateAccess(ctx: var Ctx): PNode = result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)), getStateField(ctx.g, ctx.fn), ctx.fn.info) proc newStateAssgn(ctx: var Ctx, toValue: PNode): PNode = # Creates state assignment: # :state = toValue newTree(nkAsgn, ctx.newStateAccess(), toValue) proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym = result = newSym(skVar, getIdent(ctx.g.cache, name), ctx.idgen, ctx.fn, ctx.fn.info) result.typ = typ result.flagsImpl.incl sfNoInit assert(not typ.isNil, "Env var needs a type") let envParam = getEnvParam(ctx.fn) result = addUniqueField(envParam.typ.elementType, result, ctx.g.cache, ctx.idgen) proc newEnvVarAccess(ctx: Ctx, s: PSym): PNode = result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)), s, ctx.fn.info) proc newTempVarAccess(ctx: Ctx, s: PSym): PNode = result = newSymNode(s, ctx.fn.info) proc newTmpResultAccess(ctx: var Ctx): PNode = if ctx.tmpResultSym.isNil: ctx.tmpResultSym = ctx.newEnvVar(":tmpResult", ctx.fn.typ.returnType) ctx.newEnvVarAccess(ctx.tmpResultSym) proc newArrayType(g: ModuleGraph; len: PNode, t: PType; idgen: IdGenerator; owner: PSym): PType = result = newType(tyArray, idgen, owner) let rng = newType(tyRange, idgen, owner) rng.n = newTree(nkRange, g.newIntLit(owner.info, 0), len) rng.rawAddSon(t) result.rawAddSon(rng) result.rawAddSon(t) proc newFinallyPathAccess(ctx: var Ctx, level: int, info: TLineInfo): PNode = # ctx.:finallyPath[level] let minPathLen = level + 1 if ctx.finallyPathSym.isNil: ctx.finallyPathLen = ctx.g.newIntLit(ctx.fn.info, minPathLen) let ty = ctx.g.newArrayType(ctx.finallyPathLen, ctx.g.getSysType(ctx.fn.info, tyInt16), ctx.idgen, ctx.fn) ctx.finallyPathSym = ctx.newEnvVar(":finallyPath", ty) elif ctx.finallyPathLen.intVal < minPathLen: ctx.finallyPathLen.intVal = minPathLen result = newTreeIT(nkBracketExpr, info, ctx.g.getSysType(info, tyInt), ctx.newEnvVarAccess(ctx.finallyPathSym), ctx.g.newIntLit(ctx.fn.info, level)) proc newFinallyPathAssign(ctx: var Ctx, level: int, label: PNode, info: TLineInfo): PNode = assert(label != nil) let fp = newFinallyPathAccess(ctx, level, info) result = newTree(nkAsgn, fp, label) proc newCurExcAccess(ctx: var Ctx): PNode = if ctx.curExcSym.isNil: let getCurExc = ctx.g.callCodegenProc("getCurrentException") ctx.curExcSym = ctx.newEnvVar(":curExc", getCurExc.typ) ctx.newEnvVarAccess(ctx.curExcSym) proc newStateLabel(ctx: Ctx): PNode = ctx.g.newIntLit(TLineInfo(), 0) proc newState(ctx: var Ctx, n: PNode, inlinable: bool, label: PNode): PNode = # Creates a new state, adds it to the context # Returns label of the newly created state result = label if result.isNil: result = ctx.newStateLabel() assert(result.kind == nkIntLit) ctx.states.add(State(label: result, body: n, excLandingState: ctx.curExcLandingState, inlinable: inlinable)) proc toStmtList(n: PNode): PNode = result = n if result.kind notin {nkStmtList, nkStmtListExpr}: result = newNodeI(nkStmtList, n.info) result.add(n) proc addGotoOut(n: PNode, gotoOut: PNode): PNode = # Make sure `n` is a stmtlist, and ends with `gotoOut` result = toStmtList(n) if result.len == 0 or result[^1].kind != nkGotoState: result.add(gotoOut) proc newTempVarDef(ctx: Ctx, s: PSym, initialValue: PNode): PNode = var v = initialValue if v == nil: v = ctx.g.emptyNode newTree(nkVarSection, newTree(nkIdentDefs, newSymNode(s), ctx.g.emptyNode, v)) proc newTempVar(ctx: var Ctx, typ: PType, parent: PNode, initialValue: PNode = nil): PSym = result = newSym(skVar, getIdent(ctx.g.cache, ":tmpSlLower" & $ctx.tempVarId), ctx.idgen, ctx.fn, ctx.fn.info) inc ctx.tempVarId result.typ = typ assert(not typ.isNil, "Temp var needs a type") parent.add(ctx.newTempVarDef(result, initialValue)) proc newExternExcAccess(ctx: var Ctx): PNode = if ctx.externExcSym == nil: ctx.externExcSym = newSym(skVar, getIdent(ctx.g.cache, ":externExc"), ctx.idgen, ctx.fn, ctx.fn.info) ctx.externExcSym.typ = ctx.curExcSym.typ newSymNode(ctx.externExcSym, ctx.fn.info) proc newRestoreExternException(ctx: var Ctx): PNode = ctx.g.callCodegenProc("closureIterSetExc", ctx.fn.info, ctx.newExternExcAccess()) proc hasYields(n: PNode): bool = # TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt. case n.kind of nkYieldStmt: result = true of nkSkip: result = false else: result = false for i in ord(n.kind == nkCast).. 1: var cond: PNode = nil for i in 0.. 0: result = ctx.newJumpAlongFinallyChain(finallyChain, n.info) else: # Target is not in finally path means that it doesn't have yields (no state split), # so we don't have to transform this break. result = n proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode = # "Returning" involves jumping along all the current finally path. # The last finally should exit to state 0 which is a special case for last exit # (either return or propagating exception to the caller). # It is eccounted for in newEndFinallyNode. result = newNodeI(nkStmtList, n.info) # Returns prevent exception propagation result.add(ctx.nullifyCurExc) var finallyChain = newSeq[PNode]() for i in countdown(ctx.finallyPathStack.high, 0): let b = ctx.finallyPathStack[i].n # echo "STACK ", i, " ", b.kind if b.kind == nkFinally: finallyChain.add(ctx.finallyPathStack[i].label) if finallyChain.len > 0: # Add proc exit state finallyChain.add(ctx.g.newIntLit(n.info, 0)) if n[0].kind != nkEmpty: let asgnTmpResult = newNodeI(nkAsgn, n.info) asgnTmpResult.add(ctx.newTmpResultAccess()) let x = if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}: n[0][1] else: n[0] asgnTmpResult.add(x) result.add(asgnTmpResult) result.add(ctx.newJumpAlongFinallyChain(finallyChain, n.info)) else: # There are no (split) finallies on the path, so we can return right away result.add(ctx.restoreExternExc) result.add(n) proc transformBreaksAndReturns(ctx: var Ctx, n: PNode): PNode = result = n case n.kind of nkSkip: discard of nkBreakStmt: result = ctx.transformBreakStmt(n) # of nkContinueStmt: # By this point all relevant continues should be # lowered to breaks in transf.nim. of nkReturnStmt: if nfNoRewrite notin n.flags: result = ctx.transformReturnStmt(n) else: for i in 0.. # BEGIN_STATE: # if e: # s # goto BEGIN_STATE # else: # goto OUT # result = newNodeI(nkGotoState, n.info) let s = newNodeI(nkStmtList, n.info) let enterLabel = ctx.newState(s, false, nil) let ifNode = newNodeI(nkIfStmt, n.info) let elifBranch = newNodeI(nkElifBranch, n.info) elifBranch.add(n[0]) let gotoBegin = newNodeI(nkGotoState, n.info) gotoBegin.add(enterLabel) result = gotoBegin var body = addGotoOut(n[1], gotoBegin) ctx.finallyPathStack.add(FinallyTarget(n: n, label: gotoOut[0])) body = ctx.transformClosureIteratorBody(body, gotoBegin) discard ctx.finallyPathStack.pop() elifBranch.add(body) ifNode.add(elifBranch) let elseBranch = newTree(nkElse, gotoOut) ifNode.add(elseBranch) s.add(ifNode) of nkBlockStmt: result[1] = addGotoOut(result[1], gotoOut) ctx.finallyPathStack.add(FinallyTarget(n: result, label: gotoOut[0])) result[1] = ctx.transformClosureIteratorBody(result[1], gotoOut) discard ctx.finallyPathStack.pop() of nkTryStmt, nkHiddenTryStmt: # See explanation above about how this works ctx.hasExceptions = true let tryLabel = ctx.newStateLabel() result = newNodeI(nkGotoState, n.info) result.add(tryLabel) var tryBody = toStmtList(n[0]) var exceptBody = ctx.collectExceptState(n) var finallyBody = ctx.getFinallyNode(n) var exceptLabel, finallyLabel = ctx.g.emptyNode if exceptBody.kind != nkEmpty: exceptLabel = ctx.newStateLabel() if finallyBody.kind != nkEmpty: finallyBody = newTree(nkStmtList, finallyBody, ctx.newEndFinallyNode(finallyBody.info)) finallyLabel = ctx.newStateLabel() var tryOut = gotoOut if finallyBody.kind != nkEmpty: # Add finally path to try body # START: # finallyPath[level] = excHandlingState # END: # finallyPath[level] = gotoOut tryBody = newTree(nkStmtList, ctx.newFinallyPathAssign(ctx.curFinallyLevel, ctx.curExcLandingState, tryBody.info), tryBody, ctx.newFinallyPathAssign(ctx.curFinallyLevel, gotoOut[0], tryBody.info)) tryOut = newNodeI(nkGotoState, finallyBody.info) tryOut.add(finallyLabel) block: # Process the states let oldExcLandingState = ctx.curExcLandingState ctx.curExcLandingState = if exceptBody.kind != nkEmpty: exceptLabel elif finallyBody.kind != nkEmpty: finallyLabel else: oldExcLandingState discard ctx.newState(tryBody, false, tryLabel) if finallyBody.kind != nkEmpty: inc ctx.curFinallyLevel ctx.finallyPathStack.add(FinallyTarget(n: n[^1], label: finallyLabel)) tryBody = ctx.transformClosureIteratorBody(tryBody, tryOut) if exceptBody.kind != nkEmpty: ctx.curExcLandingState = if finallyBody.kind != nkEmpty: finallyLabel else: oldExcLandingState discard ctx.newState(exceptBody, false, exceptLabel) let normalOut = if finallyBody.kind != nkEmpty: gotoOut else: nil exceptBody = ctx.addElseToExcept(exceptBody, normalOut) # echo "EXCEPT: ", renderTree(exceptBody) exceptBody = ctx.transformClosureIteratorBody(exceptBody, tryOut) ctx.curExcLandingState = oldExcLandingState if finallyBody.kind != nkEmpty: discard ctx.finallyPathStack.pop() discard ctx.newState(finallyBody, false, finallyLabel) let finallyExit = newTree(nkGotoState, ctx.newFinallyPathAccess(ctx.curFinallyLevel - 1, finallyBody.info)) finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit) dec ctx.curFinallyLevel of nkGotoState, nkForStmt: internalError(ctx.g.config, "closure iter " & $n.kind) else: for i in 0.. # :state = STATE # return e ########################## 2 # goto STATE # -> # :state = STATE # break :stateLoop ########################## 3 # return e # -> # :state = -1 # return e result = n case n.kind of nkStmtList, nkStmtListExpr: if n.len != 0 and n[0].kind == nkYieldStmt: assert(n.len == 2) assert(n[1].kind == nkGotoState) result = newNodeI(nkStmtList, n.info) result.add(ctx.newStateAssgn(stateFromGotoState(n[1]))) var retStmt = newNodeI(nkReturnStmt, n.info) if n[0][0].kind != nkEmpty: var a = newNodeI(nkAsgn, n[0][0].info) var retVal = n[0][0] #liftCapturedVars(n[0], owner, d, c) a.add newSymNode(getClosureIterResult(ctx.g, ctx.fn, ctx.idgen)) a.add retVal retStmt.add(a) else: retStmt.add(ctx.g.emptyNode) result.add(retStmt) else: for i in 0.. 0 and n[i - 1].kind != nkYieldStmt): let stateIdx = c[0].intVal if stateIdx >= 0: inc stateOccurences[stateIdx] elif c.kind == nkIntLit: let idx = c.intVal if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == c: ctx.states[idx].inlinable = false else: ctx.countStateOccurences(c, stateOccurences) proc replaceDeletedStates(ctx: var Ctx, n: PNode): PNode = result = n if n.kind == nkIntLit: let idx = n.intVal if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == n and ctx.states[idx].deletable: let gt = ctx.replaceDeletedStates(skipStmtList(ctx.states[idx].body)) assert(gt.kind == nkGotoState) result = gt[0] else: for i in 0 ..< n.safeLen: n[i] = ctx.replaceDeletedStates(n[i]) proc replaceInlinedStates(ctx: var Ctx, n: PNode): PNode = ## Find all nkGotoState(stateIdx) nodes that do not follow nkYield. ## For every such node increment stateOccurences[stateIdx] result = n for i in 0 ..< n.safeLen: let c = n[i] if c.kind == nkGotoState and c[0].kind == nkIntLit and (i > 0 and n[i - 1].kind != nkYieldStmt): let stateIdx = c[0].intVal if stateIdx >= 0: if ctx.states[stateIdx].inlinable: n[i] = ctx.states[stateIdx].body else: n[i] = ctx.replaceInlinedStates(c) proc optimizeStates(ctx: var Ctx) = # Optimize empty states away and inline inlinable states # This step requires that unique indexes are already assigned to state labels # Find empty states (those consisting only of gotoState node) and mark # them deletable. for i in 0 .. ctx.states.high: let s = ctx.states[i] let body = skipStmtList(s.body) if body.kind == nkGotoState and body[0].kind == nkIntLit and body[0].intVal >= 0: ctx.states[i].deletable = true # Replace deletable state labels to labels of respective non-empty states for i in 0 .. ctx.states.high: ctx.states[i].body = ctx.replaceDeletedStates(ctx.states[i].body) ctx.states[i].excLandingState = ctx.replaceDeletedStates(ctx.states[i].excLandingState) # Remove deletable states var i = 0 while i < ctx.states.len: if ctx.states[i].deletable: ctx.states.delete(i) else: inc i # Reassign state label indexes for i in 0 .. ctx.states.high: ctx.states[i].label.intVal = i # Count state occurences var stateOccurences = newSeq[int](ctx.states.len) for s in ctx.states: ctx.countStateOccurences(s.body, stateOccurences) # If there are inlinable states refered not exactly once, prevent them from inlining for i, o in stateOccurences: if o != 1: ctx.states[i].inlinable = false # echo "States to optimize:" # for i, s in ctx.states: # if s.deletable: echo i, ": delete" # elif s.inlinable: echo i, ": inline" # Inline states for i in 0 .. ctx.states.high: ctx.states[i].body = ctx.replaceInlinedStates(ctx.states[i].body) # Remove inlined states i = 0 while i < ctx.states.len: if ctx.states[i].inlinable: ctx.states.delete(i) else: inc i # Reassign state label indexes one last time for i in 0 .. ctx.states.high: ctx.states[i].label.intVal = i proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) = case n.kind of nkSym: let s = n.sym if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym: let vs = c.varStates.getOrDefault(s.itemId, localNotSeen) if vs == localNotSeen: # First seing this variable c.varStates[s.itemId] = stateIdx elif vs == localRequiresLifting: discard # Sym already marked elif vs != stateIdx: c.captureVar(s) of nkReturnStmt: if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}: # we have a `result = result` expression produced by the closure # transform, let's not touch the LHS in order to make the lifting pass # correct when `result` is lifted detectCapturedVars(c, n[0][1], stateIdx) else: detectCapturedVars(c, n[0], stateIdx) of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkFuncDef, nkCommentStmt, nkTypeOfExpr, nkMixinStmt, nkBindStmt: discard of nkLambdaKinds, nkIteratorDef: if n.typ != nil: detectCapturedVars(c, n[namePos], stateIdx) else: for i in 0 ..< n.safeLen: detectCapturedVars(c, n[i], stateIdx) proc detectCapturedVars(c: var Ctx) = for i, s in c.states: detectCapturedVars(c, s.body, i) proc liftLocals(c: var Ctx, n: PNode): PNode = result = n case n.kind of nkSym: let s = n.sym if c.varStates.getOrDefault(s.itemId) == localRequiresLifting: # lift let e = getEnvParam(c.fn) let field = getFieldFromObj(e.typ.elementType, s) assert(field != nil) result = rawIndirectAccess(newSymNode(e), field, n.info) # elif c.varStates.getOrDefault(s.itemId, localNotSeen) != localNotSeen: # echo "Not lifting ", s.name.s of nkReturnStmt: if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}: # we have a `result = result` expression produced by the closure # transform, let's not touch the LHS in order to make the lifting pass # correct when `result` is lifted n[0][1] = liftLocals(c, n[0][1]) else: n[0] = liftLocals(c, n[0]) of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkFuncDef, nkCommentStmt, nkTypeOfExpr, nkMixinStmt, nkBindStmt, nkLambdaKinds, nkIteratorDef: discard else: for i in 0 ..< n.safeLen: n[i] = liftLocals(c, n[i]) proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n: PNode): PNode = var ctx = Ctx(g: g, fn: fn, idgen: idgen) # The transformation should always happen after at least partial lambdalifting # is performed, so that the closure iter environment is always created upfront. doAssert(getEnvParam(fn) != nil, "Env param not created before iter transformation") ctx.curExcLandingState = ctx.newStateLabel() ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), idgen, fn, fn.info) ctx.nullifyCurExc = newTree(nkStmtList) ctx.restoreExternExc = newTree(nkStmtList) var n = n.toStmtList # echo "transformed into ", n discard ctx.newState(n, false, nil) let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1)) var ns = false n = ctx.lowerStmtListExprs(n, ns) # echo "LOWERED: ", renderTree(n) if n.hasYieldsInExpressions(): internalError(ctx.g.config, n.info, "yield in expr not lowered") # Splitting transformation discard ctx.transformClosureIteratorBody(n, gotoOut) if ctx.hasExceptions: ctx.nullifyCurExc.add(ctx.newNullifyCurExc(fn.info)) ctx.restoreExternExc.add(ctx.newRestoreExternException()) # Assign state label indexes for i in 0 .. ctx.states.high: ctx.states[i].label.intVal = i ctx.optimizeStates() let caseDispatcher = newTreeI(nkCaseStmt, n.info, ctx.newStateAccess()) # Lamdalifting will not touch our locals, it is our responsibility to lift those that # need it. detectCapturedVars(ctx) for s in ctx.states: let body = ctx.transformStateAssignments(s.body) caseDispatcher.add newTreeI(nkOfBranch, body.info, s.label, body) caseDispatcher.add newTreeI(nkElse, n.info, newTree(nkStmtList, ctx.restoreExternExc, newTreeI(nkReturnStmt, n.info, g.emptyNode))) result = wrapIntoStateLoop(ctx, caseDispatcher) result = liftLocals(ctx, result) when false: echo "TRANSFORM TO STATES:" echo renderTree(result) # echo "exception table:" # for i, s in ctx.states: # echo i, " -> ", s.excLandingState # echo "ENV: ", renderTree(getEnvParam(fn).typ.elementType.n)