Example #1
0
// 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)
}
Example #2
0
// 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)
}
Example #3
0
// 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
}
Example #4
0
// 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()
	}
}
Example #5
0
// 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)
}
Example #6
0
// 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)
		})
}
Example #7
0
// 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)
}
Example #8
0
// 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)
}
Example #9
0
// 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)
		})
}