// buildArrowStatement builds the CodeDOM for an arrow statement. func (db *domBuilder) buildArrowStatement(node compilergraph.GraphNode) (codedom.Statement, codedom.Statement) { sourceExpr := codedom.RuntimeFunctionCall( codedom.TranslatePromiseFunction, []codedom.Expression{db.getExpression(node, parser.NodeArrowStatementSource)}, node) destinationNode := node.GetNode(parser.NodeArrowStatementDestination) destinationScope, _ := db.scopegraph.GetScope(destinationNode) var destinationTarget codedom.Expression = nil var rejectionTarget codedom.Expression = nil // Retrieve the expression of the destination variable. if !destinationScope.GetIsAnonymousReference() { destinationTarget = db.buildAssignmentExpression(destinationNode, codedom.LocalReference("resolved", node), node) } // Retrieve the expression of the rejection variable. rejectionNode, hasRejection := node.TryGetNode(parser.NodeArrowStatementRejection) if hasRejection { rejectionScope, _ := db.scopegraph.GetScope(rejectionNode) if !rejectionScope.GetIsAnonymousReference() { rejectionTarget = db.buildAssignmentExpression(rejectionNode, codedom.LocalReference("rejected", node), node) } } empty := codedom.EmptyStatement(node) promise := codedom.ArrowPromise(sourceExpr, destinationTarget, rejectionTarget, empty, node) return promise, empty }
// buildCastExpression builds the CodeDOM for a cast expression. func (db *domBuilder) buildCastExpression(node compilergraph.GraphNode) codedom.Expression { childExpr := db.getExpression(node, parser.NodeCastExpressionChildExpr) // Determine the resulting type. scope, _ := db.scopegraph.GetScope(node) resultingType := scope.ResolvedTypeRef(db.scopegraph.TypeGraph()) // If the resulting type is a structural subtype of the child expression's type, then // we are accessing the automatically composited inner instance. childScope, _ := db.scopegraph.GetScope(node.GetNode(parser.NodeCastExpressionChildExpr)) childType := childScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) if childType.CheckStructuralSubtypeOf(resultingType) { return codedom.NestedTypeAccess(childExpr, resultingType, node) } // Otherwise, add a cast call with the cast type. typeLiteral := codedom.TypeLiteral(resultingType, node) allowNull := codedom.LiteralValue("false", node) if resultingType.NullValueAllowed() { allowNull = codedom.LiteralValue("true", node) } return codedom.RuntimeFunctionCall(codedom.CastFunction, []codedom.Expression{childExpr, typeLiteral, allowNull}, node) }
// buildMatchStatement builds the CodeDOM for a match statement. func (db *domBuilder) buildMatchStatement(node compilergraph.GraphNode) (codedom.Statement, codedom.Statement) { // Retrieve (or generate) the name of a variable to hold the value being matched against. var matchExprVarName = "" if namedValue, hasNamedValue := node.TryGetNode(parser.NodeStatementNamedValue); hasNamedValue { matchExprVarName = namedValue.Get(parser.NodeNamedValueName) } else { matchExprVarName = db.generateScopeVarName(node) } // Set the match expression's value into the variable. startStatement := codedom.VarDefinitionWithInit(matchExprVarName, db.getExpression(node, parser.NodeMatchStatementExpression), node) getCheckExpression := func(caseTypeRefNode compilergraph.GraphNode) codedom.Expression { caseTypeLiteral, _ := db.scopegraph.ResolveSRGTypeRef( db.scopegraph.SourceGraph().GetTypeRef(caseTypeRefNode)) return codedom.NominalWrapping( codedom.RuntimeFunctionCall(codedom.IsTypeFunction, []codedom.Expression{ codedom.LocalReference(matchExprVarName, caseTypeRefNode), codedom.TypeLiteral(caseTypeLiteral, caseTypeRefNode), }, caseTypeRefNode), db.scopegraph.TypeGraph().BoolType(), caseTypeRefNode) } return db.buildJumpingCaseStatement(node, parser.NodeMatchStatementCase, parser.NodeMatchStatementCaseStatement, parser.NodeMatchStatementCaseTypeReference, startStatement, getCheckExpression) }
// generateNominalWrapping generates the expression source for the nominal wrapping of an instance of a base type. func (eg *expressionGenerator) generateNominalWrapping(nominalWrapping *codedom.NominalWrappingNode, context generationContext) esbuilder.ExpressionBuilder { // If this is a wrap is of an unwrap, then cancel both operations if we are simply rewrapping // the unwrapped type. if unwrapping, ok := nominalWrapping.ChildExpression.(*codedom.NominalUnwrappingNode); ok { if unwrapping.ChildExpressionType.NominalDataType() == nominalWrapping.NominalTypeRef { // Skip entirely. return eg.generateExpression(unwrapping.ChildExpression, context) } } // If the child expression being wrapped is non-nullable and not a nominal or struct, // then we can use a fast-path box call without the extra unboxing. boxFunction := codedom.BoxFunction if !nominalWrapping.ChildExpressionType.NullValueAllowed() && (!nominalWrapping.ChildExpressionType.IsNominalOrStruct() || nominalWrapping.IsLiteralWrap) { boxFunction = codedom.FastBoxFunction } call := codedom.RuntimeFunctionCall( boxFunction, []codedom.Expression{ nominalWrapping.ChildExpression, codedom.TypeLiteral(nominalWrapping.NominalTypeRef, nominalWrapping.BasisNode())}, nominalWrapping.BasisNode()) return eg.generateExpression(call, context) }
// generateNominalUnwrapping generates the expression source for the unwrapping of a nominal instance of a base type. func (eg *expressionGenerator) generateNominalUnwrapping(nominalUnwrapping *codedom.NominalUnwrappingNode, context generationContext) esbuilder.ExpressionBuilder { // If this is an unwrap is of a wrap, then try to either cancel both operations (if we can), // or simplify the unwrapping operation. childExpression := nominalUnwrapping.ChildExpression if wrapping, ok := nominalUnwrapping.ChildExpression.(*codedom.NominalWrappingNode); ok { // If the value that was being wrapped is, itself, nominal or structural, then we still // need to unwrap it, but we can fast-path by ignoring the wrap and directly unwrapping // the nominal/struct. Otherwise, we can simply just collapse the unwrap+wrap into a NOOP. if !wrapping.IsLiteralWrap && wrapping.ChildExpressionType.IsNominalOrStruct() { childExpression = wrapping.ChildExpression } else { // Otherwise, we can just collapse both operations into nothing. return eg.generateExpression(wrapping.ChildExpression, context) } } // If the child expression being unwrapped is non-nullable and we know it is boxed, then // we can use a fast-path unbox call without the extra checks. if !nominalUnwrapping.ChildExpressionType.NullValueAllowed() && nominalUnwrapping.ChildExpressionType.IsNominalOrStruct() { access := codedom.NativeAccess( childExpression, codedom.BoxedDataProperty, nominalUnwrapping.BasisNode()) return eg.generateExpression(access, context) } call := codedom.RuntimeFunctionCall( codedom.UnboxFunction, []codedom.Expression{ childExpression, }, nominalUnwrapping.BasisNode()) return eg.generateExpression(call, context) }
// buildAwaitExpression builds the CodeDOM for an await expression. func (db *domBuilder) buildAwaitExpression(node compilergraph.GraphNode) codedom.Expression { sourceExpr := codedom.RuntimeFunctionCall( codedom.TranslatePromiseFunction, []codedom.Expression{db.getExpression(node, parser.NodeAwaitExpressionSource)}, node) return codedom.AwaitPromise(sourceExpr, node) }
// buildStreamMemberAccessExpression builds the CodeDOM for a stream member access expression (*.) func (db *domBuilder) buildStreamMemberAccessExpression(node compilergraph.GraphNode) codedom.Expression { childExpr := db.getExpression(node, parser.NodeMemberAccessChildExpr) memberName := codedom.LiteralValue("'"+node.Get(parser.NodeMemberAccessIdentifier)+"'", node) return codedom.RuntimeFunctionCall(codedom.StreamMemberAccessFunction, []codedom.Expression{childExpr, memberName}, node, ) }
// generateWithShortCircuiting generates an expression with automatic short circuiting based on the value found // in the resultName variable and compared to the given compare value. func (eg *expressionGenerator) generateWithShortCircuiting(expr codedom.Expression, resultName string, compareValue codedom.Expression, context generationContext) esbuilder.ExpressionBuilder { shortCircuiter := eg.generateExpression( codedom.RuntimeFunctionCall(codedom.ShortCircuitPromiseFunction, []codedom.Expression{codedom.LocalReference(resultName, expr.BasisNode()), compareValue}, expr.BasisNode()), context) return eg.generateExpression(expr, generationContext{shortCircuiter}) }
// generateDynamicAccess generates the expression source for dynamic access. func (eg *expressionGenerator) generateDynamicAccess(dynamicAccess *codedom.DynamicAccessNode, context generationContext) esbuilder.ExpressionBuilder { basisNode := dynamicAccess.BasisNode() funcCall := codedom.RuntimeFunctionCall( codedom.DynamicAccessFunction, []codedom.Expression{ dynamicAccess.ChildExpression, codedom.LiteralValue("'"+dynamicAccess.Name+"'", basisNode), }, basisNode, ) // All dynamic accesses return a promise, to ensure it works for properties. return eg.generateExpression(codedom.AwaitPromise(funcCall, basisNode), context) }
// popResource removes a resource from the resource stack. func (sg *stateGenerator) popResource(name string, basis compilergraph.GraphNode) { sg.managesResources = true sg.resources.Pop() // popr('varName').then(...) popCall := codedom.RuntimeFunctionCall( codedom.StatePopResourceFunction, []codedom.Expression{ codedom.LiteralValue("'"+name+"'", basis), }, basis, ) sg.generateStates(codedom.ExpressionStatement(codedom.AwaitPromise(popCall, basis), basis), generateImplicitState) }
func (eg *expressionGenerator) generateShortCircuiter(compareExpr codedom.Expression, compareValue codedom.Expression, childExpr codedom.Expression, context generationContext, handler shortCircuitHandler) esbuilder.ExpressionBuilder { // Generate a specialized wrapper which resolves the left side value and places it into the result. resultName := eg.generateUniqueName("$result") resolveCompareValue := codedom.RuntimeFunctionCall(codedom.ResolvePromiseFunction, []codedom.Expression{compareExpr}, compareExpr.BasisNode()) eg.addAsyncWrapper(eg.generateExpression(resolveCompareValue, context), resultName) shortedChildExpr := eg.generateWithShortCircuiting(childExpr, resultName, compareValue, context) return handler(resultName, shortedChildExpr) }
// 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) }
// pushResource adds a resource to the resource stack with the given name. func (sg *stateGenerator) pushResource(name string, basis compilergraph.GraphNode) { sg.managesResources = true // pushr(varName, 'varName'); pushCall := codedom.RuntimeFunctionCall( codedom.StatePushResourceFunction, []codedom.Expression{ codedom.LocalReference(name, basis), codedom.LiteralValue("'"+name+"'", basis), }, basis, ) sg.generateStates(codedom.ExpressionStatement(pushCall, basis), generateImplicitState) sg.resources.Push(resource{ name: name, basis: basis, }) }
// generateMemberCall generates the expression source for a call to a module or type member. func (eg *expressionGenerator) generateMemberCall(memberCall *codedom.MemberCallNode, context generationContext) esbuilder.ExpressionBuilder { if memberCall.Member.IsOperator() && memberCall.Member.IsNative() { // This is a call to a native operator. if memberCall.Member.Name() != "index" { panic("Native call to non-index operator") } refExpr := memberCall.ChildExpression.(*codedom.MemberReferenceNode).ChildExpression return eg.generateExpression(codedom.NativeIndexing(refExpr, memberCall.Arguments[0], memberCall.BasisNode()), context) } callPath := memberCall.ChildExpression arguments := memberCall.Arguments var functionCall = codedom.FunctionCall(callPath, arguments, memberCall.BasisNode()) if memberCall.Nullable { // Invoke the function with a specialized nullable-invoke. refExpr := callPath.(*codedom.DynamicAccessNode).ChildExpression var isPromising = "false" if memberCall.Member.IsPromising() { isPromising = "true" } localArguments := []codedom.Expression{ refExpr, codedom.LiteralValue("'"+memberCall.Member.Name()+"'", refExpr.BasisNode()), codedom.LiteralValue(isPromising, memberCall.BasisNode()), codedom.ArrayLiteral(arguments, memberCall.BasisNode()), } functionCall = codedom.RuntimeFunctionCall(codedom.NullableInvokeFunction, localArguments, memberCall.BasisNode()) } return eg.generateExpression(codedom.WrapIfPromising(functionCall, memberCall.Member, memberCall.BasisNode()), context) }
// buildAssertNotNullExpression builds the CodeDOM for an assert not null (expr!) operator. func (db *domBuilder) buildAssertNotNullExpression(node compilergraph.GraphNode) codedom.Expression { childExpr := db.getExpression(node, parser.NodeUnaryExpressionChildExpr) return codedom.RuntimeFunctionCall(codedom.AssertNotNullFunction, []codedom.Expression{childExpr}, node) }
// buildSmlExpression builds the CodeDOM for a SML expression. func (db *domBuilder) buildSmlExpression(node compilergraph.GraphNode) codedom.Expression { smlScope, _ := db.scopegraph.GetScope(node) funcOrTypeRefNode := node.GetNode(parser.NodeSmlExpressionTypeOrFunction) funcOrTypeRefScope, _ := db.scopegraph.GetScope(funcOrTypeRefNode) // Build the expression for the function or constructor to be called to construct the expression. var declarationFunction = db.buildExpression(funcOrTypeRefNode) var declarationFunctionType = db.scopegraph.TypeGraph().AnyTypeReference() if smlScope.HasLabel(proto.ScopeLabel_SML_CONSTRUCTOR) { constructor, _ := funcOrTypeRefScope.StaticTypeRef(db.scopegraph.TypeGraph()).ResolveMember("Declare", typegraph.MemberResolutionStatic) declarationFunctionType = constructor.MemberType() declarationFunction = codedom.MemberReference(declarationFunction, constructor, node) } else { declarationFunctionType = funcOrTypeRefScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) } var declarationArguments = make([]codedom.Expression, 0) declFunctionParams := declarationFunctionType.Parameters() // If the SML expression expects any attributes, then construct the props struct, class or mapping. if len(declFunctionParams) > 0 { attributeExpressions := map[string]codedom.Expression{} ait := node.StartQuery(). Out(parser.NodeSmlExpressionAttribute). BuildNodeIterator() for ait.Next() { attributeNode := ait.Node() attributeName := attributeNode.Get(parser.NodeSmlAttributeName) attributeExpressions[attributeName] = db.getExpressionOrDefault( attributeNode, parser.NodeSmlAttributeValue, codedom.NominalWrapping( codedom.LiteralValue("true", attributeNode), db.scopegraph.TypeGraph().BoolType(), attributeNode)) } // Construct the props object expression, either as a struct, class or as a mapping. propsType := declFunctionParams[0] if smlScope.HasLabel(proto.ScopeLabel_SML_PROPS_MAPPING) { declarationArguments = append(declarationArguments, db.buildMappingInitializerExpression(propsType, attributeExpressions, node)) } else { declarationArguments = append(declarationArguments, db.buildStructInitializerExpression(propsType, attributeExpressions, node)) } } // If the SML expression expects any children, then construct the children stream or value (if any). if len(declFunctionParams) > 1 { cit := node.StartQuery(). Out(parser.NodeSmlExpressionChild). BuildNodeIterator() children := db.buildExpressions(cit, buildExprNormally) switch { case smlScope.HasLabel(proto.ScopeLabel_SML_SINGLE_CHILD): // If there is a child, it is a value. If missing, the child is nullable, so we don't put // anything there. if len(children) > 0 { declarationArguments = append(declarationArguments, children[0]) } case smlScope.HasLabel(proto.ScopeLabel_SML_STREAM_CHILD): // Single child which is a stream. Passed directly to the declaring function as an argument. declarationArguments = append(declarationArguments, children[0]) case smlScope.HasLabel(proto.ScopeLabel_SML_CHILDREN): // The argument is a stream of the child expressions. Build it as a generator. if len(children) == 0 { // Add an empty generator. declarationArguments = append(declarationArguments, codedom.RuntimeFunctionCall(codedom.EmptyGeneratorDirect, []codedom.Expression{}, node)) } else { yielders := make([]codedom.Statement, len(children)) for index, child := range children { yielders[index] = codedom.YieldValue(child, child.BasisNode()) if index > 0 { yielders[index-1].(codedom.HasNextStatement).SetNext(yielders[index]) } } generatorFunc := codedom.FunctionDefinition([]string{}, []string{}, yielders[0], false, codedom.GeneratorFunction, node) generatorExpr := codedom.FunctionCall(generatorFunc, []codedom.Expression{}, node) declarationArguments = append(declarationArguments, codedom.AwaitPromise(generatorExpr, node)) } default: panic("Unknown SML children scope label") } } // The value of declaration function invoked with the arguments is the initial definition. var definition = codedom.AwaitPromise( codedom.FunctionCall(declarationFunction, declarationArguments, node), node) // Add any decorators as wrapping around the definition call. dit := node.StartQuery(). Out(parser.NodeSmlExpressionDecorator). BuildNodeIterator() for dit.Next() { decoratorNode := dit.Node() decoratorFunc := db.getExpression(decoratorNode, parser.NodeSmlDecoratorPath) // The value for the decorator is either the value expression given or the literal // value "true" if none specified. decoratorValue := db.getExpressionOrDefault( decoratorNode, parser.NodeSmlDecoratorValue, codedom.NominalWrapping( codedom.LiteralValue("true", decoratorNode), db.scopegraph.TypeGraph().BoolType(), decoratorNode)) // The decorator is invoked over the definition. decoratorArgs := []codedom.Expression{definition, decoratorValue} definition = codedom.AwaitPromise( codedom.FunctionCall(decoratorFunc, decoratorArgs, decoratorNode), decoratorNode) } return definition }