// 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) }
// generateYield generates the code for a generator yield. func (sg *stateGenerator) generateYield(yield *codedom.YieldNode) { if yield.Value != nil { value := sg.addTopLevelExpression(yield.Value) templateStr := ` $yield({{ emit .Item }}); ` template := esbuilder.Template("yieldvalue", templateStr, generatingItem{value, sg}) sg.currentState.pushBuilt(sg.addMapping(template, yield)) return } if yield.StreamValue != nil { value := sg.addTopLevelExpression(yield.StreamValue) templateStr := ` $yieldin({{ emit .Item }}); ` template := esbuilder.Template("yieldin", templateStr, generatingItem{value, sg}) sg.currentState.pushBuilt(sg.addMapping(template, yield)) return } templateStr := ` $done(); return; ` template := esbuilder.Template("yieldbreak", templateStr, generatingItem{yield, sg}) sg.currentState.pushBuilt(sg.addMapping(template, yield)) }
// BuildWrapped returns the builder for this expression. If specified, the expression will be wrapped // via the given template string. The original expression builder reference will be placed into // a template field with name "ResultExpr", while any data passed into this method will be placed // into "Data". func (er ExpressionResult) BuildWrapped(wrappingTemplateStr string, data interface{}) esbuilder.SourceBuilder { var result esbuilder.SourceBuilder = er.inlineExpr // If specified, wrap the expression via the template string. if wrappingTemplateStr != "" { fullData := struct { ResultExpr esbuilder.SourceBuilder Data interface{} }{result, data} result = esbuilder.Template("getbuilder", wrappingTemplateStr, fullData) } // For each expression wrapper (in *reverse order*), wrap the result expression via the wrapper. // This is used to generate promise and other async wrappings. for rindex, _ := range er.wrappers { wrapper := er.wrappers[len(er.wrappers)-rindex-1] templateStr := `({{ emit .PromisingExpression }}).then(function({{ .ResultName }}) { {{ range $idx, $expr := .IntermediateExpressions }} {{ emit $expr }}; {{ end }} {{ if .IsTopLevel }} {{ emit .WrappedExpression }} {{ else }} return ({{ emit .WrappedExpression }}); {{ end }} })` data := struct { // PromisingExpression is the expression of type promise. PromisingExpression esbuilder.ExpressionBuilder // ResultName is the name of the result of the promising expression. ResultName string // WrappedExpression is the expression being wrapped. WrappedExpression esbuilder.SourceBuilder // IsTopLevel returns whether this is the top-level wrapping of the expression. IsTopLevel bool // IntermediateExpressions returns the intermediate expression that should be emitted // before the wrapped expression is invoked. IntermediateExpressions []esbuilder.ExpressionBuilder }{wrapper.promisingExpr, wrapper.resultName, result, rindex == 0, wrapper.intermediateExpressions} result = esbuilder.Template("wrapper", templateStr, data) } return result }
// generateObjectLiteral generates the expression source for a literal object value. func (eg *expressionGenerator) generateObjectLiteral(objectLiteral *codedom.ObjectLiteralNode, context generationContext) esbuilder.ExpressionBuilder { // Sort the entries by key to ensure a consistent ordering. sortedEntries := objectLiteral.Entries sort.Sort(ByKey(sortedEntries)) // Determine whether we can use the compact form of object literals. The compact form is only // possible if all the keys are string literals. var compactFormAllowed = true entries := make([]interface{}, len(objectLiteral.Entries)) for index, entry := range sortedEntries { if _, ok := entry.KeyExpression.(*codedom.LiteralValueNode); !ok { compactFormAllowed = false } entries[index] = struct { Key esbuilder.ExpressionBuilder Value esbuilder.ExpressionBuilder }{eg.generateExpression(entry.KeyExpression, context), eg.generateExpression(entry.ValueExpression, context)} } data := struct { Entries []interface{} }{entries} if compactFormAllowed { templateStr := ` ({ {{ range $idx, $entry := .Entries }} {{ emit $entry.Key }}: {{ emit $entry.Value }}, {{ end }} }) ` return esbuilder.Template("compactobjectliteral", templateStr, data).AsExpression() } else { templateStr := ` ((function() { var obj = {}; {{ range $idx, $entry := .Entries }} obj[{{ emit $entry.Key }}] = {{ emit $entry.Value }}; {{ end }} return obj; })()) ` return esbuilder.Template("expandedobjectliteral", templateStr, data).AsExpression() } }
// generateConditionalJump generates the code for a conditional jump. func (sg *stateGenerator) generateConditionalJump(jump *codedom.ConditionalJumpNode) { // Add the expression to the state machine. The type will be a nominally-wrapped Boolean, so we need to unwrap it // here. expression := sg.addTopLevelExpression( codedom.NominalUnwrapping(jump.BranchExpression, sg.scopegraph.TypeGraph().BoolTypeReference(), jump.BasisNode())) currentState := sg.currentState // Based on the expression value, jump to one state or another. data := struct { This codedom.Statement JumpToTrue string JumpToFalse string Expression esbuilder.SourceBuilder }{jump, sg.jumpToStatement(jump.True), sg.jumpToStatement(jump.False), expression} templateStr := ` if ({{ emit .Item.Expression }}) { {{ .Item.JumpToTrue }} continue; } else { {{ .Item.JumpToFalse }} continue; } ` template := esbuilder.Template("conditionaljump", templateStr, generatingItem{data, sg}) currentState.pushBuilt(sg.addMapping(template, jump)) }
// GenerateES5 produces ES5 code from the given scope graph. func GenerateES5(sg *scopegraph.ScopeGraph, generatedFilePath string, sourceRoot string) (string, *sourcemap.SourceMap, error) { generated := generateModules(sg) // Order the modules by their paths. pather := shared.NewPather(sg.SourceGraph().Graph) modulePathMap := map[string]esbuilder.SourceBuilder{} var modulePathList = make([]string, 0) for module, _ := range generated { path := pather.GetModulePath(module) modulePathList = append(modulePathList, path) modulePathMap[path] = generated[module] } sort.Strings(modulePathList) // Collect the generated modules into their final source. ordered := ordered_map.NewOrderedMap() for _, modulePath := range modulePathList { ordered.Set(modulePath, modulePathMap[modulePath]) } // Generate the unformatted code and source map. template := esbuilder.Template("es5", runtimeTemplate, ordered) sm := sourcemap.NewSourceMap(generatedFilePath, sourceRoot) unformatted := esbuilder.BuildSourceAndMap(template, sm) // Format the code. return escommon.FormatMappedECMASource(unformatted.String(), sm) }
// Source returns the full source for this state. func (s *state) SourceTemplate() esbuilder.SourceBuilder { templateStr := `{{ range $piece := .Pieces }} {{ emit $piece }} {{ end }}` return esbuilder.Template("state", templateStr, s) }
// generateRejection generates the code for promise rejection. func (sg *stateGenerator) generateRejection(rejection *codedom.RejectionNode) { value := sg.addTopLevelExpression(rejection.Value) templateStr := ` $reject({{ emit .Item }}); return; ` template := esbuilder.Template("rejection", templateStr, generatingItem{value, sg}) sg.currentState.pushBuilt(sg.addMapping(template, rejection)) }
// 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() } }
// generateImplementedMember generates the given member into ES5. func (gen *es5generator) generateImplementedMember(member typegraph.TGMember) esbuilder.SourceBuilder { srgMember, _ := gen.getSRGMember(member) generating := generatingMember{member, srgMember, gen} switch srgMember.MemberKind() { case srg.ConstructorMember: fallthrough case srg.FunctionMember: fallthrough case srg.OperatorMember: return esbuilder.Template("function", functionTemplateStr, generating) case srg.PropertyMember: return esbuilder.Template("property", propertyTemplateStr, generating) default: panic(fmt.Sprintf("Unknown kind of member %s", srgMember.MemberKind())) } }
// Set sets the given key to the given value. Note that the value *must* be a generatedSourceResult, // but this function takes in an interface{} to match the interface of the normal OrderedMap. func (gim *generatedInitMap) Set(key interface{}, value interface{}) { result := value.(generatedSourceResult) if result.IsPromise { gim.promising = true wrappedSource := esbuilder.Template("wrappedinit", ` (init.push({{ emit . }})) `, result.Source) gim.orderedMap.Set(key, wrappedSource) } else { gim.orderedMap.Set(key, result.Source) } }
// generateType generates the given type into ES5. func (gen *es5generator) generateType(typedef typegraph.TGTypeDecl) esbuilder.SourceBuilder { generating := generatingType{typedef, gen} switch typedef.TypeKind() { case typegraph.ClassType: return esbuilder.Template("class", classTemplateStr, generating) case typegraph.ImplicitInterfaceType: return esbuilder.Template("interface", interfaceTemplateStr, generating) case typegraph.NominalType: return esbuilder.Template("nominal", nominalTemplateStr, generating) case typegraph.StructType: return esbuilder.Template("struct", structTemplateStr, generating) case typegraph.ExternalInternalType: return esbuilder.Snippet("") default: panic("Unknown typedef kind") } }
// generateUnconditionalJump generates the code for an unconditional jump. func (sg *stateGenerator) generateUnconditionalJump(jump *codedom.UnconditionalJumpNode) { currentState := sg.currentState data := struct { JumpToTarget string }{sg.jumpToStatement(jump.Target)} templateStr := ` {{ .Item.JumpToTarget }} continue; ` template := esbuilder.Template("unconditionaljump", templateStr, generatingItem{data, sg}) currentState.pushBuilt(sg.addMapping(template, jump)) }
// generateArrowPromise generates the code for an arrow promise. func (sg *stateGenerator) generateArrowPromise(arrowPromise *codedom.ArrowPromiseNode) { currentState := sg.currentState sg.generateStates(arrowPromise.Target, generateNewState) childExpression := sg.addTopLevelExpression(arrowPromise.ChildExpression) var resolutionAssignment esbuilder.SourceBuilder = nil var rejectionAssignment esbuilder.SourceBuilder = nil if arrowPromise.ResolutionAssignment != nil { resolutionAssignment = sg.addTopLevelExpression(arrowPromise.ResolutionAssignment) } if arrowPromise.RejectionAssignment != nil { rejectionAssignment = sg.addTopLevelExpression(arrowPromise.RejectionAssignment) } data := struct { JumpToTarget string ChildExpression esbuilder.SourceBuilder ResolutionAssignment esbuilder.SourceBuilder RejectionAssignment esbuilder.SourceBuilder }{sg.jumpToStatement(arrowPromise.Target), childExpression, resolutionAssignment, rejectionAssignment} templateStr := ` ({{ emit .Item.ChildExpression }}).then(function(resolved) { {{ if .Item.ResolutionAssignment }} {{ emit .Item.ResolutionAssignment }} {{ end }} {{ .Item.JumpToTarget }} {{ .Snippets.Continue .IsGenerator }} }).catch(function(rejected) { {{ if .Item.RejectionAssignment }} {{ emit .Item.RejectionAssignment }} {{ .Item.JumpToTarget }} {{ .Snippets.Continue .IsGenerator }} {{ else }} {{ .Snippets.Reject "rejected" }} {{ end }} }); return; ` template := esbuilder.Template("arrowpromise", templateStr, generatingItem{data, sg}) currentState.pushBuilt(sg.addMapping(template, arrowPromise)) }
// generateVarDefinition generates the code for a variable definition. func (sg *stateGenerator) generateVarDefinition(vardef *codedom.VarDefinitionNode) { sg.addVariable(vardef.Name) if vardef.Initializer != nil { data := struct { Name string Initializer esbuilder.SourceBuilder }{vardef.Name, sg.addTopLevelExpression(vardef.Initializer)} templateStr := ` {{ .Item.Name }} = {{ emit .Item.Initializer }}; ` template := esbuilder.Template("vardef", templateStr, generatingItem{data, sg}) sg.currentState.pushBuilt(sg.addMapping(template, vardef)) } }
// generateFunctionDefinition generates the code for a function. func (eg *expressionGenerator) generateFunctionDefinition(function *codedom.FunctionDefinitionNode, context generationContext) esbuilder.ExpressionBuilder { templateStr := ` {{ if .Item.WorkerExecute }} $t.workerwrap('{{ .Item.UniqueId }}', {{ end }} ({{ if .Item.Generics }} function({{ range $index, $generic := .Item.Generics }}{{ if $index }}, {{ end }}{{ $generic }}{{ end }}) { {{ if .Item.RequiresThis }}var $this = this;{{ end }} var $f = {{ end }} function({{ range $index, $parameter := .Item.Parameters }}{{ if $index }}, {{ end }}{{ $parameter }}{{ end }}) { {{ if not .Item.Generics }}{{ if .Item.RequiresThis }}var $this = this;{{ end }}{{ end }} {{ $body := .GeneratedBody }} {{ if .Item.IsGenerator }} {{ if $body }} {{ emit $body }} return $generator.new($continue); {{ else }} return $generator.empty(); {{ end }} {{ else }} {{ if $body }} {{ emit $body }} return $promise.new($continue); {{ else }} return $promise.empty(); {{ end }} {{ end }} } {{ if .Item.Generics }} return $f; } {{ end }}) {{ if .Item.WorkerExecute }} ) {{ end }} ` data := struct { Item *codedom.FunctionDefinitionNode GeneratedBody esbuilder.SourceBuilder }{function, eg.machineBuilder(function.Body, function.IsGenerator())} return esbuilder.Template("functiondef", templateStr, data).AsExpression() }
// generateResolution generates the code for promise resolution. func (sg *stateGenerator) generateResolution(resolution *codedom.ResolutionNode) { var value esbuilder.SourceBuilder = nil if resolution.Value != nil { value = sg.addTopLevelExpression(resolution.Value) } templateStr := ` {{ if .Item }} $resolve({{ emit .Item }}); return; {{ else }} {{ .Snippets.Resolve "" }} {{ end }} ` template := esbuilder.Template("resolution", templateStr, generatingItem{value, sg}) sg.currentState.pushBuilt(sg.addMapping(template, resolution)) }
// generateVariable generates the given variable into ES5. func (gen *es5generator) generateVariable(member typegraph.TGMember) generatedSourceResult { srgMember, _ := gen.getSRGMember(member) initializer, _ := srgMember.Initializer() initResult := statemachine.GenerateExpressionResult(initializer, gen.scopegraph, gen.positionMapper) prefix := "instance" if member.IsStatic() { prefix = "$static" } data := struct { Name string Prefix string Initializer expressiongenerator.ExpressionResult }{member.Name(), prefix, initResult} source := esbuilder.Template("variable", variableTemplateStr, data) return generatedSourceResult{source, initResult.IsPromise()} }
// GenerateComposition generates the source for all the composed types structurually inherited by the type. func (gt generatingType) GenerateComposition() *generatedInitMap { initMap := newGeneratedInitMap() parentTypes := gt.Type.ParentTypes() for _, parentTypeRef := range parentTypes { data := struct { ComposedTypeLocation string InnerInstanceName string RequiredFields []typegraph.TGMember }{ gt.Generator.pather.TypeReferenceCall(parentTypeRef), gt.Generator.pather.InnerInstanceName(parentTypeRef), parentTypeRef.ReferredType().RequiredFields(), } source := esbuilder.Template("composition", compositionTemplateStr, data) initMap.Set(parentTypeRef, generatedSourceResult{source, true}) } return initMap }
// generateModule generates the given module into ES5. func (gen *es5generator) generateModule(module typegraph.TGModule) esbuilder.SourceBuilder { generating := generatingModule{module, gen} return esbuilder.Template("module", moduleTemplateStr, generating) }
// generateImplementedAliasedMember generates the given member into an alias in ES5. func (gen *es5generator) generateImplementedAliasedMember(member typegraph.TGMember) esbuilder.SourceBuilder { srgMember, _ := gen.getSRGMember(member) generating := generatingMember{member, srgMember, gen} return esbuilder.Template("aliasedmember", aliasedMemberTemplateStr, generating) }
// generateResolveExpression generates the code for an expression resolution. func (sg *stateGenerator) generateResolveExpression(resolveExpression *codedom.ResolveExpressionNode) { // Generate the resolved expression, requiring that it is asynchronous to ensure it becomes // a Promise. result := expressiongenerator.GenerateExpression(resolveExpression.ChildExpression, expressiongenerator.EnsureAsync, sg.scopegraph, sg.positionMapper, sg.generateMachine) var resolutionName = "" var rejectionName = "" if resolveExpression.ResolutionName != "" { resolutionName = sg.addVariable(resolveExpression.ResolutionName) } if resolveExpression.RejectionName != "" { rejectionName = sg.addVariable(resolveExpression.RejectionName) } // Save the current state and create a new state to jump to once the expression's // promise has resolved or rejected. currentState := sg.currentState sg.generateStates(resolveExpression.Target, generateNewState) // Build the expression with an assignment of the resolved expression value assigned to // the resolution variable (if any) and then a jump to the post-resolution state. jumpToTarget := sg.jumpToStatement(resolveExpression.Target) resolveData := struct { ResolutionName string RejectionName string JumpToTarget string Snippets snippets IsGenerator bool }{resolutionName, rejectionName, jumpToTarget, sg.snippets(), sg.isGeneratorFunction} wrappingTemplateStr := ` {{ if .Data.ResolutionName }} {{ .Data.ResolutionName }} = {{ emit .ResultExpr }}; {{ end }} {{ if .Data.RejectionName }} {{ .Data.RejectionName }} = null; {{ end }} {{ .Data.JumpToTarget }} {{ .Data.Snippets.Continue .Data.IsGenerator }} ` promise := result.BuildWrapped(wrappingTemplateStr, resolveData) // Similarly, add a .catch onto the Promise with an assignment of the rejected error (if any) // to the rejection variable (if any) and then a jump to the post-rejection state. rejectData := struct { Promise esbuilder.SourceBuilder ResolutionName string RejectionName string JumpToTarget string Snippets snippets IsGenerator bool }{promise, resolutionName, rejectionName, jumpToTarget, sg.snippets(), sg.isGeneratorFunction} catchTemplateStr := ` ({{ emit .Promise }}).catch(function($rejected) { {{ if .RejectionName }} {{ .RejectionName }} = $rejected; {{ end }} {{ if .ResolutionName }} {{ .ResolutionName }} = null; {{ end }} {{ .JumpToTarget }} {{ .Snippets.Continue .IsGenerator }} }); return; ` currentState.pushBuilt(esbuilder.Template("resolvecatch", catchTemplateStr, rejectData)) }