// source returns the full source for the generated state machine. func (sg *stateGenerator) source(states []*state) esbuilder.SourceBuilder { if len(states) == 0 { if sg.isGeneratorFunction { return esbuilder.Returns(esbuilder.Identifier("$generator").Member("empty").Call()) } // If there are no states, this is an empty state machine. return esbuilder.Returns(esbuilder.Identifier("$promise").Member("empty").Call()) } // Check if this machine is in fact a single state with no jumps. If so, then we don't // even need the loop and switch. var singleState *state = nil if len(states) == 1 && !sg.hasAsyncJump { singleState = states[0] } data := struct { States []*state SingleState *state Snippets snippets ManagesResources bool Variables map[string]bool }{states, singleState, sg.snippets(), sg.managesResources, sg.variables} if sg.isGeneratorFunction { return esbuilder.Template("generatorstatemachine", generatorStateMachineTemplateStr, data) } return esbuilder.Template("statemachine", stateMachineTemplateStr, data) }
// wrapSynchronousExpression wraps the given synchronous expression and turns it into a promise. func (eg *expressionGenerator) wrapSynchronousExpression(syncExpr esbuilder.ExpressionBuilder) esbuilder.ExpressionBuilder { // Wrap the expression in a resolve of a promise. We need the function wrapping to ensure // that if the expression raises an exception, we can handle that case as well. promiseExpr := esbuilder.Snippet(string(codedom.NewPromiseFunction)).Call( esbuilder.Function("", esbuilder.Identifier("$resolve").Call(syncExpr), "$resolve")) resultName := eg.generateUniqueName("$result") eg.addAsyncWrapper(promiseExpr, resultName) return esbuilder.Identifier(resultName) }
// generateLocalAssignment generates the expression source for a local assignment. func (eg *expressionGenerator) generateLocalAssignment(localAssign *codedom.LocalAssignmentNode, context generationContext) esbuilder.ExpressionBuilder { value := eg.generateExpression(localAssign.Value, context) assignment := esbuilder.Assignment(esbuilder.Identifier(localAssign.Target), value) // If this assignment is under an async expression wrapper, then we add it to the wrapper itself, // rather than doing the assignment inline. This ensures that the variable's value is updated when // expected in the async flow, rather than once all the promises have returned. if wrapper, hasWrapper := eg.currentAsyncWrapper(); hasWrapper { wrapper.addIntermediateExpression(assignment) return esbuilder.Identifier(localAssign.Target) } return assignment }
// addTopLevelExpression generates the source for the given expression and adds it to the // state machine. func (sg *stateGenerator) addTopLevelExpression(expression codedom.Expression) esbuilder.SourceBuilder { // Generate the expression. result := expressiongenerator.GenerateExpression(expression, expressiongenerator.AllowedSync, sg.scopegraph, sg.positionMapper, sg.generateMachine) // Add any variables generated by the expression. for _, varName := range result.Variables() { sg.addVariable(varName) } // If the expression generated is asynchronous, then it will have at least one // promise callback. In order to ensure continued execution, we have to wrap // the resulting expression in a jump to a new state once it has finished executing. if result.IsAsync() { // Add $result, as it is needed below. sg.addVariable("$result") sg.hasAsyncJump = true // Save the current state and create a new state to jump to once the expression's // promise has resolved. currentState := sg.currentState targetState := sg.newState() // Wrap the expression's promise result expression to be stored in $result, // followed by a jump to the new state. data := struct { ResolutionStateId stateId Snippets snippets IsGenerator bool }{targetState.ID, sg.snippets(), sg.isGeneratorFunction} wrappingTemplateStr := ` $result = {{ emit .ResultExpr }}; {{ .Data.Snippets.SetState .Data.ResolutionStateId }} {{ .Data.Snippets.Continue .Data.IsGenerator }} ` promise := result.BuildWrapped(wrappingTemplateStr, data) // Wrap the expression's promise with a catch, in case it fails. catchTemplateStr := ` ({{ emit .Item }}).catch(function(err) { {{ .Snippets.Reject "err" }} }); return; ` currentState.pushBuilt(esbuilder.Template("tlecatch", catchTemplateStr, generatingItem{promise, sg})) return esbuilder.Identifier("$result") } else { // Otherwise, the expression is synchronous and we can just invoke it. return result.Build() } }
// generateAwaitPromise generates the expression source for waiting for a promise. func (eg *expressionGenerator) generateAwaitPromise(awaitPromise *codedom.AwaitPromiseNode, context generationContext) esbuilder.ExpressionBuilder { resultName := eg.generateUniqueName("$result") childExpr := eg.generateExpression(awaitPromise.ChildExpression, context) // Add an asynchronous wrapper for the await that executes the child expression as a promise and then // waits for it to return (via a call to then), at which point the wrapped expression is executed. if context.shortCircuiter != nil { eg.addAsyncWrapper(esbuilder.Binary(context.shortCircuiter, "||", childExpr), resultName) } else { eg.addAsyncWrapper(childExpr, resultName) } // An await expression is a reference to the "result" after waiting on the child expression // and then executing the parent expression inside the await callback. return esbuilder.Identifier(resultName) }
// generateShortCircuitedBinaryOperator generates the expression source for a short circuiting binary operator. func (eg *expressionGenerator) generateShortCircuitedBinaryOperator(binaryOp *codedom.BinaryOperationNode, context generationContext) esbuilder.ExpressionBuilder { compareValue := codedom.LiteralValue("false", binaryOp.BasisNode()) if binaryOp.Operator == "&&" { compareValue = codedom.LiteralValue("true", binaryOp.BasisNode()) } return eg.generateShortCircuiter( binaryOp.LeftExpr, compareValue, binaryOp.RightExpr, context, func(resultName string, rightSide esbuilder.ExpressionBuilder) esbuilder.ExpressionBuilder { return esbuilder.Binary(esbuilder.Identifier(resultName), binaryOp.Operator, rightSide) }) }
// generateTernary generates the expression for a ternary expression. func (eg *expressionGenerator) generateTernary(ternary *codedom.TernaryNode, context generationContext) esbuilder.ExpressionBuilder { // Generate a specialized wrapper which resolves the conditional value of the ternary and // places it into the result. resultName := eg.generateUniqueName("$result") resolveConditionalValue := codedom.RuntimeFunctionCall(codedom.ResolvePromiseFunction, []codedom.Expression{codedom.NominalUnwrapping(ternary.CheckExpr, eg.scopegraph.TypeGraph().BoolTypeReference(), ternary.CheckExpr.BasisNode())}, ternary.BasisNode()) eg.addAsyncWrapper(eg.generateExpression(resolveConditionalValue, context), resultName) // Generate the then and else expressions as short circuited by the value. thenExpr := eg.generateWithShortCircuiting(ternary.ThenExpr, resultName, codedom.LiteralValue("true", ternary.BasisNode()), context) elseExpr := eg.generateWithShortCircuiting(ternary.ElseExpr, resultName, codedom.LiteralValue("false", ternary.BasisNode()), context) // Return an expression which compares the value and either return the then or else values. return esbuilder.Ternary(esbuilder.Identifier(resultName), thenExpr, elseExpr) }
// generateLocalReference generates the expression source for a local reference. func (eg *expressionGenerator) generateLocalReference(localRef *codedom.LocalReferenceNode, context generationContext) esbuilder.ExpressionBuilder { return esbuilder.Identifier(localRef.Name) }
// generateNullComparisonOperator generates the expression for a null comparison operator. func (eg *expressionGenerator) generateNullComparisonOperator(compareOp *codedom.BinaryOperationNode, context generationContext) esbuilder.ExpressionBuilder { return eg.generateShortCircuiter( compareOp.LeftExpr, codedom.LiteralValue("null", compareOp.BasisNode()), compareOp.RightExpr, context, func(resultName string, rightSide esbuilder.ExpressionBuilder) esbuilder.ExpressionBuilder { return esbuilder.Call(esbuilder.Snippet(string(codedom.NullableComparisonFunction)), esbuilder.Identifier(resultName), rightSide) }) }