// buildMappingLiteralExpression builds the CodeDOM for a mapping literal expression. func (db *domBuilder) buildMappingLiteralExpression(node compilergraph.GraphNode) codedom.Expression { mappingScope, _ := db.scopegraph.GetScope(node) mappingType := mappingScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) eit := node.StartQuery(). Out(parser.NodeMappingLiteralExpressionEntryRef). BuildNodeIterator() var entries = make([]codedom.ObjectLiteralEntryNode, 0) for eit.Next() { entryNode := eit.Node() // The key expression must be a string when produced. We either reference it directly (if a string) // or call .String() (if a Stringable). keyNode := entryNode.GetNode(parser.NodeMappingLiteralExpressionEntryKey) keyScope, _ := db.scopegraph.GetScope(keyNode) keyType := keyScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) var keyExpr = db.buildExpression(keyNode) if !keyType.HasReferredType(db.scopegraph.TypeGraph().StringType()) { stringMethod, _ := keyType.ResolveMember("String", typegraph.MemberResolutionInstance) keyExpr = codedom.MemberCall( codedom.MemberReference(db.buildExpression(keyNode), stringMethod, node), stringMethod, []codedom.Expression{}, keyNode) } // Get the expression for the value. valueExpr := db.getExpression(entryNode, parser.NodeMappingLiteralExpressionEntryValue) // Build an object literal expression with the (native version of the) key string and the // created value. entryExpr := codedom.ObjectLiteralEntryNode{ codedom.NominalUnwrapping(keyExpr, db.scopegraph.TypeGraph().StringTypeReference(), keyNode), valueExpr, entryNode, } entries = append(entries, entryExpr) } if len(entries) == 0 { // Empty mapping. Call the Empty() constructor directly. constructor, _ := mappingType.ResolveMember("Empty", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mappingType, node), constructor, node), constructor, []codedom.Expression{}, node) } constructor, _ := mappingType.ResolveMember("overObject", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mappingType, node), constructor, node), constructor, []codedom.Expression{codedom.ObjectLiteral(entries, node)}, node) }
// buildMapExpression builds the CodeDOM for a map expression. func (db *domBuilder) buildMapExpression(node compilergraph.GraphNode) codedom.Expression { mapScope, _ := db.scopegraph.GetScope(node) mapType := mapScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) eit := node.StartQuery(). Out(parser.NodeMapExpressionChildEntry). BuildNodeIterator() var keyExprs = make([]codedom.Expression, 0) var valueExprs = make([]codedom.Expression, 0) for eit.Next() { entryNode := eit.Node() keyExprs = append(keyExprs, db.getExpression(entryNode, parser.NodeMapExpressionEntryKey)) valueExprs = append(valueExprs, db.getExpression(entryNode, parser.NodeMapExpressionEntryValue)) } if len(valueExprs) == 0 { // Empty map. Call the new() constructor directly. constructor, _ := mapType.ResolveMember("new", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mapType, node), constructor, node), constructor, []codedom.Expression{}, node) } constructor, _ := mapType.ResolveMember("forArrays", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mapType, node), constructor, node), constructor, []codedom.Expression{codedom.ArrayLiteral(keyExprs, node), codedom.ArrayLiteral(valueExprs, node)}, node) }
// buildMappingInitializerExpression builds the CodeDOM for initializing a mapping literal expression. func (db *domBuilder) buildMappingInitializerExpression(mappingType typegraph.TypeReference, initializers map[string]codedom.Expression, node compilergraph.GraphNode) codedom.Expression { var entries = make([]codedom.ObjectLiteralEntryNode, 0) for name, expr := range initializers { entries = append(entries, codedom.ObjectLiteralEntryNode{ codedom.LiteralValue(strconv.Quote(name), expr.BasisNode()), expr, expr.BasisNode(), }) } if len(entries) == 0 { // Empty mapping. Call the Empty() constructor directly. constructor, _ := mappingType.ResolveMember("Empty", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mappingType, node), constructor, node), constructor, []codedom.Expression{}, node) } constructor, _ := mappingType.ResolveMember("overObject", typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(mappingType, node), constructor, node), constructor, []codedom.Expression{codedom.ObjectLiteral(entries, node)}, node) }
// buildTemplateStringCall builds the CodeDOM representing the call to a template string function. func (db *domBuilder) buildTemplateStringCall(node compilergraph.GraphNode, funcExpr codedom.Expression, isTagged bool) codedom.Expression { pit := node.StartQuery(). Out(parser.NodeTemplateStringPiece). BuildNodeIterator() var pieceExprs = make([]codedom.Expression, 0) var valueExprs = make([]codedom.Expression, 0) var isPiece = true for pit.Next() { if isPiece { pieceExprs = append(pieceExprs, db.buildExpression(pit.Node())) } else { valueExprs = append(valueExprs, db.buildExpression(pit.Node())) } isPiece = !isPiece } // Handle common case: No literal string piece at all. if len(pieceExprs) == 0 { return codedom.NominalWrapping(codedom.LiteralValue("''", node), db.scopegraph.TypeGraph().StringType(), node) } // Handle common case: A single literal string piece with no values. if len(pieceExprs) == 1 && len(valueExprs) == 0 { return pieceExprs[0] } pieceSliceType := db.scopegraph.TypeGraph().SliceTypeReference(db.scopegraph.TypeGraph().StringTypeReference()) valueSliceType := db.scopegraph.TypeGraph().SliceTypeReference(db.scopegraph.TypeGraph().StringableTypeReference()) constructor, _ := pieceSliceType.ResolveMember("overArray", typegraph.MemberResolutionStatic) pieceSliceExpr := codedom.MemberCall( codedom.MemberReference( codedom.TypeLiteral(pieceSliceType, node), constructor, node), constructor, []codedom.Expression{codedom.ArrayLiteral(pieceExprs, node)}, node) valueSliceExpr := codedom.MemberCall( codedom.MemberReference( codedom.TypeLiteral(valueSliceType, node), constructor, node), constructor, []codedom.Expression{codedom.ArrayLiteral(valueExprs, node)}, node) return codedom.AwaitPromise(codedom.FunctionCall(funcExpr, []codedom.Expression{pieceSliceExpr, valueSliceExpr}, node), node) }
// buildInitializationCompoundExpression builds the compound expression for calling the specified initializers on a struct. func (db *domBuilder) buildInitializationCompoundExpression(structType typegraph.TypeReference, initializers map[string]codedom.Expression, structValue codedom.Expression, node compilergraph.GraphNode) codedom.Expression { // Create a variable to hold the struct instance. structInstanceVarName := db.generateScopeVarName(node) // Build the assignment expressions. assignmentExpressions := make([]codedom.Expression, len(initializers)) var index = 0 for fieldName, initializer := range initializers { member, found := structType.ResolveMember(fieldName, typegraph.MemberResolutionInstance) if !found { panic("Member not found in struct initializer construction") } assignmentExpressions[index] = codedom.MemberAssignment(member, codedom.MemberReference( codedom.LocalReference(structInstanceVarName, node), member, node), initializer, node) index = index + 1 } // Return a compound expression that takes in the struct value and the assignment expressions, // executes them, and returns the struct value. return codedom.CompoundExpression(structInstanceVarName, structValue, assignmentExpressions, codedom.LocalReference(structInstanceVarName, node), node) }
// buildStructInitializerExpression builds an initializer expression for a struct type. func (db *domBuilder) buildStructInitializerExpression(structType typegraph.TypeReference, initializers map[string]codedom.Expression, node compilergraph.GraphNode) codedom.Expression { staticType := structType.ReferredType() var arguments = make([]codedom.Expression, 0) for _, field := range staticType.RequiredFields() { arguments = append(arguments, initializers[field.Name()]) delete(initializers, field.Name()) } constructor, found := structType.ResolveMember("new", typegraph.MemberResolutionStatic) if !found { panic(fmt.Sprintf("Missing new constructor on type %v", structType)) } newCall := codedom.MemberCall( codedom.MemberReference( codedom.TypeLiteral(structType, node), constructor, node), constructor, arguments, node) // If there are no initializers, then just return the new value directly. if len(initializers) == 0 { return newCall } return db.buildInitializationCompoundExpression(structType, initializers, newCall, node) }
// buildInCollectionExpression builds the CodeDOM for an in collection operator. func (db *domBuilder) buildInCollectionExpression(node compilergraph.GraphNode) codedom.Expression { valueExpr := db.getExpression(node, parser.NodeBinaryExpressionLeftExpr) childExpr := db.getExpression(node, parser.NodeBinaryExpressionRightExpr) scope, _ := db.scopegraph.GetScope(node) operator, _ := scope.CalledOperator(db.scopegraph.TypeGraph()) return codedom.MemberCall(codedom.MemberReference(childExpr, operator, node), operator, []codedom.Expression{valueExpr}, node) }
// buildIndexerExpression builds the CodeDOM for an indexer call. func (db *domBuilder) buildIndexerExpression(node compilergraph.GraphNode) codedom.Expression { indexExpr := db.getExpression(node, parser.NodeSliceExpressionIndex) childExpr := db.getExpression(node, parser.NodeSliceExpressionChildExpr) scope, _ := db.scopegraph.GetScope(node) operator, _ := scope.CalledOperator(db.scopegraph.TypeGraph()) opExpr := codedom.MemberReference(childExpr, operator, node) return codedom.MemberCall(opExpr, operator, []codedom.Expression{indexExpr}, node) }
// buildCollectionInitializerExpression builds a literal collection expression. func (db *domBuilder) buildCollectionInitializerExpression(collectionType typegraph.TypeReference, valueExprs []codedom.Expression, emptyConstructorName string, arrayConstructorName string, node compilergraph.GraphNode) codedom.Expression { if len(valueExprs) == 0 { // Empty collection. Call the empty constructor directly. constructor, _ := collectionType.ResolveMember(emptyConstructorName, typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(collectionType, node), constructor, node), constructor, []codedom.Expression{}, node) } arrayExpr := codedom.ArrayLiteral(valueExprs, node) constructor, _ := collectionType.ResolveMember(arrayConstructorName, typegraph.MemberResolutionStatic) return codedom.MemberCall( codedom.MemberReference(codedom.TypeLiteral(collectionType, node), constructor, node), constructor, []codedom.Expression{arrayExpr}, node) }
// buildSlicerExpression builds the CodeDOM for a slice call. func (db *domBuilder) buildSlicerExpression(node compilergraph.GraphNode) codedom.Expression { childExpr := db.getExpression(node, parser.NodeSliceExpressionChildExpr) leftExpr := db.getExpressionOrDefault(node, parser.NodeSliceExpressionLeftIndex, codedom.LiteralValue("null", node)) rightExpr := db.getExpressionOrDefault(node, parser.NodeSliceExpressionRightIndex, codedom.LiteralValue("null", node)) scope, _ := db.scopegraph.GetScope(node) operator, _ := scope.CalledOperator(db.scopegraph.TypeGraph()) opExpr := codedom.MemberReference(childExpr, operator, node) return codedom.MemberCall(opExpr, operator, []codedom.Expression{leftExpr, rightExpr}, node) }
// buildNamedAccess builds the CodeDOM for a member access expression. func (db *domBuilder) buildNamedAccess(node compilergraph.GraphNode, name string, childExprNode *compilergraph.GraphNode) codedom.Expression { scope, _ := db.scopegraph.GetScope(node) namedReference, hasNamedReference := db.scopegraph.GetReferencedName(scope) // Reference to an unknown name or a nullable member access must be a dynamic access. if !hasNamedReference || node.Kind() == parser.NodeNullableMemberAccessExpression { return codedom.DynamicAccess(db.buildExpression(*childExprNode), name, node) } // Reference to a local name is a var or parameter. if namedReference.IsLocal() { return codedom.LocalReference(namedReference.Name(), node) } // Check for a reference to a type. if typeRef, isType := namedReference.Type(); isType { return codedom.StaticTypeReference(typeRef, node) } // Check for a reference to a member. if memberRef, isMember := namedReference.Member(); isMember { // If the member is under a child expression, build as an access expression. if childExprNode != nil { childExpr := db.buildExpression(*childExprNode) _, underFuncCall := node.TryGetIncomingNode(parser.NodeFunctionCallExpressionChildExpr) isAliasedFunctionReference := scope.ResolvedTypeRef(db.scopegraph.TypeGraph()).HasReferredType(db.scopegraph.TypeGraph().FunctionType()) if isAliasedFunctionReference && !underFuncCall { return codedom.DynamicAccess(childExpr, memberRef.Name(), node) } else { return codedom.MemberReference(childExpr, memberRef, node) } } else { // This is a direct access of a static member. Generate an access under the module. return codedom.StaticMemberReference(memberRef, db.scopegraph.TypeGraph().AnyTypeReference(), node) } } panic("Unknown kind of named access") return nil }
// buildStructCloneExpression builds a clone expression for a struct type. func (db *domBuilder) buildStructCloneExpression(structType typegraph.TypeReference, initializers map[string]codedom.Expression, node compilergraph.GraphNode) codedom.Expression { cloneMethod, found := structType.ResolveMember("Clone", typegraph.MemberResolutionInstance) if !found { panic(fmt.Sprintf("Missing Clone() method on type %v", structType)) } cloneCall := codedom.MemberCall( codedom.MemberReference( db.getExpression(node, parser.NodeStructuralNewTypeExpression), cloneMethod, node), cloneMethod, []codedom.Expression{}, node) // If there are no initializers, then just return the cloned value directly. if len(initializers) == 0 { return cloneCall } return db.buildInitializationCompoundExpression(structType, initializers, cloneCall, node) }
// buildLoopStatement builds the CodeDOM for a loop statement. func (db *domBuilder) buildLoopStatement(node compilergraph.GraphNode) (codedom.Statement, codedom.Statement) { startStatement := codedom.EmptyStatement(node) startStatement.MarkReferenceable() finalStatement := codedom.EmptyStatement(node) // Save initial continue and break statements for the loop. db.continueStatementMap[node.NodeId] = startStatement db.breakStatementMap[node.NodeId] = finalStatement // A loop statement is buildd as a start statement which conditionally jumps to either the loop body // (on true) or, on false, jumps to a final state after the loop. if loopExpr, hasLoopExpr := db.tryGetExpression(node, parser.NodeLoopStatementExpression); hasLoopExpr { // Check for a named value under the loop. If found, this is a loop over a stream or streamable. namedValue, hasNamedValue := node.TryGetNode(parser.NodeStatementNamedValue) if hasNamedValue { namedValueName := namedValue.Get(parser.NodeNamedValueName) resultVarName := db.generateScopeVarName(node) namedValueScope, _ := db.scopegraph.GetScope(namedValue) // Create the stream variable. streamVarName := db.generateScopeVarName(node) var streamVariable = codedom.VarDefinitionWithInit(streamVarName, loopExpr, node) // If the expression is Streamable, first call .Stream() on the expression to get the stream // for the variable. if namedValueScope.HasLabel(proto.ScopeLabel_STREAMABLE_LOOP) { // Call .Stream() on the expression. streamableMember, _ := db.scopegraph.TypeGraph().StreamableType().GetMember("Stream") streamExpr := codedom.MemberCall( codedom.MemberReference(loopExpr, streamableMember, namedValue), streamableMember, []codedom.Expression{}, namedValue) streamVariable = codedom.VarDefinitionWithInit(streamVarName, streamExpr, node) } // Create variables to hold the named value (as requested in the SRG) and the loop result. namedVariable := codedom.VarDefinition(namedValueName, node) resultVariable := codedom.VarDefinition(resultVarName, node) // Create an expression statement to set the result variable to a call to Next(). streamMember, _ := db.scopegraph.TypeGraph().StreamType().GetMember("Next") nextCallExpr := codedom.MemberCall( codedom.MemberReference(codedom.LocalReference(streamVarName, node), streamMember, node), streamMember, []codedom.Expression{}, namedValue) resultExpressionStatement := codedom.ExpressionStatement(codedom.LocalAssignment(resultVarName, nextCallExpr, namedValue), namedValue) resultExpressionStatement.MarkReferenceable() // Set the continue statement to call Next() again. db.continueStatementMap[node.NodeId] = resultExpressionStatement // Create an expression statement to set the named variable to the first part of the tuple. namedExpressionStatement := codedom.ExpressionStatement( codedom.LocalAssignment(namedValueName, codedom.NativeAccess( codedom.LocalReference(resultVarName, namedValue), "First", namedValue), namedValue), namedValue) // Jump to the body state if the second part of the tuple in the result variable is true. bodyStart, bodyEnd := db.getStatements(node, parser.NodeLoopStatementBlock) checkJump := codedom.ConditionalJump( codedom.NativeAccess(codedom.LocalReference(resultVarName, node), "Second", node), bodyStart, finalStatement, node) // Steps: // 1) Empty statement // 2) Create the stream's variable (with no value) // 3) Create the named value variable (with no value) // 4) Create the result variable (with no value) // 5) (loop starts here) Pull the Next() result out of the stream // 6) Pull the true/false bool out of the result // 7) Pull the named value out of the result // 8) Jump based on the true/false boolean value codedom.AssignNextStatement(startStatement, streamVariable) codedom.AssignNextStatement(streamVariable, namedVariable) codedom.AssignNextStatement(namedVariable, resultVariable) codedom.AssignNextStatement(resultVariable, resultExpressionStatement) codedom.AssignNextStatement(resultExpressionStatement, namedExpressionStatement) codedom.AssignNextStatement(namedExpressionStatement, checkJump) // Jump to the result checking expression once the loop body completes. directJump := codedom.UnconditionalJump(resultExpressionStatement, node) codedom.AssignNextStatement(bodyEnd, directJump) return startStatement, finalStatement } else { bodyStart, bodyEnd := db.getStatements(node, parser.NodeLoopStatementBlock) // Loop over a direct boolean expression which is evaluated on each iteration. initialJump := codedom.ConditionalJump(loopExpr, bodyStart, finalStatement, node) directJump := codedom.UnconditionalJump(initialJump, node) codedom.AssignNextStatement(bodyEnd, directJump) codedom.AssignNextStatement(startStatement, initialJump) return startStatement, finalStatement } } else { bodyStart, bodyEnd := db.getStatements(node, parser.NodeLoopStatementBlock) // A loop without an expression just loops infinitely over the body. directJump := codedom.UnconditionalJump(bodyStart, node) codedom.AssignNextStatement(bodyEnd, directJump) codedom.AssignNextStatement(startStatement, bodyStart) return startStatement, directJump } }
// 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 }