// jumpToStatement generates an unconditional jump to the target statement. func (sg *stateGenerator) jumpToStatement(target codedom.Statement) string { // Check for resources that will be out of scope once the jump occurs. resources := sg.resources.OutOfScope(target.BasisNode()) if len(resources) == 0 { // No resources are moving out of scope, so simply set the next state. targetState := sg.generateStates(target, generateNewState) return sg.snippets().SetState(targetState.ID) } // Pop off any resources out of scope. data := struct { PopFunction codedom.RuntimeFunction Resources []resource Snippets snippets TargetState *state IsGenerator bool }{codedom.StatePopResourceFunction, resources, sg.snippets(), sg.generateStates(target, generateNewState), sg.isGeneratorFunction} popTemplateStr := ` {{ .PopFunction }}({{ range $index, $resource := .Resources }}{{ if $index }}, {{ end }} '{{ $resource.Name }}' {{ end }}).then(function() { {{ .Snippets.SetState .TargetState.ID }} {{ .Snippets.Continue .IsGenerator }} }).catch(function(err) { {{ .Snippets.Reject "err" }} }); ` return sg.templater.Execute("popjump", popTemplateStr, data) }
// generateStates generates the ES5 states for the given CodeDOM statement. func (sg *stateGenerator) generateStates(statement codedom.Statement, option generateStatesOption) *state { // If already constructed, just return the same state. This handles states that callback to // earlier states. if startState, ok := sg.stateStartMap[statement]; ok { return startState } // Determine whether we have to create a new state for this statement. A new state is required if the // statement is marked as referencable (which means another state will need to jump to it) or if a new // state was explicitly requested. currentState := sg.currentState if option == generateNewState || statement.IsReferenceable() { // Add the new state. newState := sg.newState() // If the statement is referencable, then the reason we added the new state was to allow // for jumping "in between" and otherwise single state. Therefore, control flow must immediately // go from the existing state to the new state (as it would normally naturally flow) if statement.IsReferenceable() { currentState.pushSnippet(sg.snippets().SetState(newState.ID)) currentState.pushSnippet("continue;") } } // Cache the state for the statement. startState := sg.currentState sg.stateStartMap[statement] = startState // Add the mapping comment for the statement. switch e := statement.(type) { case *codedom.EmptyStatementNode: // Nothing to do. break case *codedom.ResolutionNode: sg.generateResolution(e) case *codedom.RejectionNode: sg.generateRejection(e) case *codedom.YieldNode: sg.generateYield(e) case *codedom.ExpressionStatementNode: sg.generateExpressionStatement(e) case *codedom.VarDefinitionNode: sg.generateVarDefinition(e) case *codedom.ResourceBlockNode: sg.generateResourceBlock(e) case *codedom.ConditionalJumpNode: sg.generateConditionalJump(e) case *codedom.UnconditionalJumpNode: sg.generateUnconditionalJump(e) case *codedom.ArrowPromiseNode: sg.generateArrowPromise(e) case *codedom.ResolveExpressionNode: sg.generateResolveExpression(e) default: panic(fmt.Sprintf("Unknown CodeDOM statement: %T", statement)) } // If the statement releases flow, then we immediately add a new state and jump to it // before returning. if statement.ReleasesFlow() { endState := sg.currentState newState := sg.newState() endState.pushSnippet(sg.snippets().SetState(newState.ID)) endState.pushSnippet("return;") } // Handle generation of chained statements. if n, ok := statement.(codedom.HasNextStatement); ok && n.GetNext() != nil { next := n.GetNext() sg.generateStates(next, generateNextState) } else if option != generateImplicitState { sg.currentState.leafState = true } return startState }