Пример #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)
}
Пример #2
0
// 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))
}
Пример #3
0
// 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
}
Пример #4
0
// 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()
	}
}
Пример #5
0
// 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))
}
Пример #6
0
// 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)
}
Пример #7
0
// 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)
}
Пример #8
0
// 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))
}
Пример #9
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()
	}
}
Пример #10
0
// 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()))
	}
}
Пример #11
0
// 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)
	}
}
Пример #12
0
// 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")
	}
}
Пример #13
0
// 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))
}
Пример #14
0
// 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))
}
Пример #15
0
// 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))
	}
}
Пример #16
0
// 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()
}
Пример #17
0
// 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))
}
Пример #18
0
// 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()}
}
Пример #19
0
// 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
}
Пример #20
0
// 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)
}
Пример #21
0
// 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)
}
Пример #22
0
// 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))
}