// buildBooleanBinaryExpression builds the CodeDOM for a boolean unary operator. func (db *domBuilder) buildBooleanBinaryExpression(node compilergraph.GraphNode, op string) codedom.Expression { boolType := db.scopegraph.TypeGraph().BoolTypeReference() leftExpr := codedom.NominalUnwrapping(db.getExpression(node, parser.NodeBinaryExpressionLeftExpr), boolType, node) rightExpr := codedom.NominalUnwrapping(db.getExpression(node, parser.NodeBinaryExpressionRightExpr), boolType, node) return codedom.NominalWrapping( codedom.BinaryOperation(leftExpr, op, rightExpr, node), db.scopegraph.TypeGraph().BoolType(), node) }
// 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) }
// 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)) }
// buildRootTypeExpression builds the CodeDOM for a root type expression. func (db *domBuilder) buildRootTypeExpression(node compilergraph.GraphNode) codedom.Expression { childExprNode := node.GetNode(parser.NodeUnaryExpressionChildExpr) childScope, _ := db.scopegraph.GetScope(childExprNode) childType := childScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) childExpr := db.buildExpression(childExprNode) return codedom.NominalUnwrapping(childExpr, childType, node) }
// buildExpressions builds expressions for each of the nodes found in the iterator. func (db *domBuilder) buildExpressions(iterator compilergraph.NodeIterator, option buildExprOption) []codedom.Expression { var expressions = make([]codedom.Expression, 0) for iterator.Next() { expr := db.buildExpression(iterator.Node()) // If requested, check to see if the expression was used under a special exception where // a nominal type is used in place of its root data type. If so, we need to unwrap the // expression value. if option == buildExprCheckNominalShortcutting && db.scopegraph.HasSecondaryLabel(iterator.Node(), proto.ScopeLabel_NOMINALLY_SHORTCUT_EXPR) { expr = codedom.NominalUnwrapping(expr, db.scopegraph.TypeGraph().AnyTypeReference(), iterator.Node()) } expressions = append(expressions, expr) } return expressions }
// 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) }
// buildFunctionCall builds the CodeDOM for a function call. func (db *domBuilder) buildFunctionCall(node compilergraph.GraphNode) codedom.Expression { childExprNode := node.GetNode(parser.NodeFunctionCallExpressionChildExpr) childScope, _ := db.scopegraph.GetScope(childExprNode) // Check if the child expression has a static scope. If so, this is a type conversion between // a nominal type and a base type. if childScope.GetKind() == proto.ScopeKind_STATIC { wrappedExprNode := node.GetNode(parser.NodeFunctionCallArgument) wrappedExprScope, _ := db.scopegraph.GetScope(wrappedExprNode) wrappedExprType := wrappedExprScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) wrappedExpr := db.buildExpression(wrappedExprNode) targetTypeRef := childScope.StaticTypeRef(db.scopegraph.TypeGraph()) // If the targetTypeRef is not nominal or structural, then we know we are unwrapping. if !targetTypeRef.IsNominalOrStruct() { return codedom.NominalUnwrapping(wrappedExpr, wrappedExprType, node) } else { return codedom.NominalRefWrapping(wrappedExpr, wrappedExprType, targetTypeRef, node) } } // Collect the expressions for the arguments. ait := node.StartQuery(). Out(parser.NodeFunctionCallArgument). BuildNodeIterator() arguments := db.buildExpressions(ait, buildExprCheckNominalShortcutting) childExpr := db.buildExpression(childExprNode) // If the function call is to a member, then we return a MemberCall. namedRef, isNamed := db.scopegraph.GetReferencedName(childScope) if isNamed && !namedRef.IsLocal() { member, _ := namedRef.Member() if childExprNode.Kind() == parser.NodeNullableMemberAccessExpression { return codedom.NullableMemberCall(childExpr, member, arguments, node) } return codedom.MemberCall(childExpr, member, arguments, node) } // Otherwise, this is a normal function call with an await. return codedom.AwaitPromise(codedom.FunctionCall(childExpr, arguments, node), node) }
// buildExpression builds the CodeDOM for the given SRG node and returns it as an expression. Will // panic if the returned DOM type is not an expression. func (db *domBuilder) buildExpression(node compilergraph.GraphNode) codedom.Expression { switch node.Kind() { // Access Expressions. case parser.NodeMemberAccessExpression: fallthrough case parser.NodeNullableMemberAccessExpression: fallthrough case parser.NodeDynamicMemberAccessExpression: return db.buildMemberAccessExpression(node) case parser.NodeTypeIdentifierExpression: return db.buildIdentifierExpression(node) case parser.NodeGenericSpecifierExpression: return db.buildGenericSpecifierExpression(node) case parser.NodeCastExpression: return db.buildCastExpression(node) case parser.NodeStreamMemberAccessExpression: return db.buildStreamMemberAccessExpression(node) // Await Expression. case parser.NodeTypeAwaitExpression: return db.buildAwaitExpression(node) // Lambda Expressions. case parser.NodeTypeLambdaExpression: return db.buildLambdaExpression(node) // SML Expressions. case parser.NodeTypeSmlExpression: return db.buildSmlExpression(node) case parser.NodeTypeSmlText: return db.buildSmlText(node) // Flow Expressions. case parser.NodeTypeConditionalExpression: return db.buildConditionalExpression(node) case parser.NodeTypeLoopExpression: return db.buildLoopExpression(node) // Op Expressions. case parser.NodeRootTypeExpression: return db.buildRootTypeExpression(node) case parser.NodeFunctionCallExpression: return db.buildFunctionCall(node) case parser.NodeSliceExpression: return db.buildSliceExpression(node) case parser.NodeNullComparisonExpression: return db.buildNullComparisonExpression(node) case parser.NodeAssertNotNullExpression: return db.buildAssertNotNullExpression(node) case parser.NodeIsComparisonExpression: return db.buildIsComparisonExpression(node) case parser.NodeInCollectionExpression: return db.buildInCollectionExpression(node) case parser.NodeBitwiseNotExpression: return db.buildUnaryOperatorExpression(node, nil) case parser.NodeKeywordNotExpression: return db.buildUnaryOperatorExpression(node, func(expr codedom.Expression) codedom.Expression { boolType := db.scopegraph.TypeGraph().BoolTypeReference() childExpr := codedom.UnaryOperation("!", codedom.NominalUnwrapping(expr, boolType, node), node) return codedom.NominalWrapping( childExpr, db.scopegraph.TypeGraph().BoolType(), node) }) case parser.NodeDefineRangeExpression: fallthrough case parser.NodeBitwiseXorExpression: fallthrough case parser.NodeBitwiseOrExpression: fallthrough case parser.NodeBitwiseAndExpression: fallthrough case parser.NodeBitwiseShiftLeftExpression: fallthrough case parser.NodeBitwiseShiftRightExpression: fallthrough case parser.NodeBinaryAddExpression: fallthrough case parser.NodeBinarySubtractExpression: fallthrough case parser.NodeBinaryMultiplyExpression: fallthrough case parser.NodeBinaryDivideExpression: fallthrough case parser.NodeBinaryModuloExpression: return db.buildBinaryOperatorExpression(node, nil) case parser.NodeComparisonEqualsExpression: return db.buildBinaryOperatorExpression(node, nil) case parser.NodeComparisonNotEqualsExpression: return db.buildBinaryOperatorExpression(node, func(expr codedom.Expression) codedom.Expression { boolType := db.scopegraph.TypeGraph().BoolTypeReference() childExpr := codedom.UnaryOperation("!", codedom.NominalUnwrapping(expr, boolType, node), node) return codedom.NominalRefWrapping( childExpr, boolType.NominalDataType(), boolType, node) }) case parser.NodeComparisonLTEExpression: return db.buildBinaryOperatorExpression(node, func(expr codedom.Expression) codedom.Expression { intType := db.scopegraph.TypeGraph().IntTypeReference() boolType := db.scopegraph.TypeGraph().BoolTypeReference() childExpr := codedom.BinaryOperation(codedom.NominalUnwrapping(expr, intType, node), "<=", codedom.LiteralValue("0", node), node) return codedom.NominalRefWrapping( childExpr, boolType.NominalDataType(), boolType, node) }) case parser.NodeComparisonLTExpression: return db.buildBinaryOperatorExpression(node, func(expr codedom.Expression) codedom.Expression { intType := db.scopegraph.TypeGraph().IntTypeReference() boolType := db.scopegraph.TypeGraph().BoolTypeReference() childExpr := codedom.BinaryOperation(codedom.NominalUnwrapping(expr, intType, node), "<", codedom.LiteralValue("0", node), node) return codedom.NominalRefWrapping( childExpr, boolType.NominalDataType(), boolType, node) }) case parser.NodeComparisonGTEExpression: return db.buildBinaryOperatorExpression(node, func(expr codedom.Expression) codedom.Expression { intType := db.scopegraph.TypeGraph().IntTypeReference() boolType := db.scopegraph.TypeGraph().BoolTypeReference() childExpr := codedom.BinaryOperation(codedom.NominalUnwrapping(expr, intType, node), ">=", codedom.LiteralValue("0", node), node) return codedom.NominalRefWrapping( childExpr, boolType.NominalDataType(), boolType, node) }) case parser.NodeComparisonGTExpression: return db.buildBinaryOperatorExpression(node, func(expr codedom.Expression) codedom.Expression { intType := db.scopegraph.TypeGraph().IntTypeReference() boolType := db.scopegraph.TypeGraph().BoolTypeReference() childExpr := codedom.BinaryOperation(codedom.NominalUnwrapping(expr, intType, node), ">", codedom.LiteralValue("0", node), node) return codedom.NominalRefWrapping( childExpr, boolType.NominalDataType(), boolType, node) }) // Boolean operators. case parser.NodeBooleanAndExpression: return db.buildBooleanBinaryExpression(node, "&&") case parser.NodeBooleanOrExpression: return db.buildBooleanBinaryExpression(node, "||") case parser.NodeBooleanNotExpression: return db.buildBooleanUnaryExpression(node, "!") // Literals. case parser.NodeStructuralNewExpression: return db.buildStructuralNewExpression(node) case parser.NodeNumericLiteralExpression: return db.buildNumericLiteral(node) case parser.NodeBooleanLiteralExpression: return db.buildBooleanLiteral(node) case parser.NodeStringLiteralExpression: return db.buildStringLiteral(node) case parser.NodeNullLiteralExpression: return db.buildNullLiteral(node) case parser.NodeThisLiteralExpression: return db.buildThisLiteral(node) case parser.NodeValLiteralExpression: return db.buildValLiteral(node) case parser.NodeListExpression: return db.buildListExpression(node) case parser.NodeSliceLiteralExpression: return db.buildSliceLiteralExpression(node) case parser.NodeMappingLiteralExpression: return db.buildMappingLiteralExpression(node) case parser.NodeMapExpression: return db.buildMapExpression(node) case parser.NodeTypeTemplateString: return db.buildTemplateStringExpression(node) case parser.NodeTaggedTemplateLiteralString: return db.buildTaggedTemplateString(node) default: panic(fmt.Sprintf("Unknown SRG expression node: %s", node.Kind())) return nil } }
func (db *domBuilder) buildOptimizedBinaryOperatorExpression(node compilergraph.GraphNode, parentType typegraph.TypeReference, leftExpr codedom.Expression, rightExpr codedom.Expression) (codedom.Expression, bool) { // Verify this is a supported native operator. opString, hasOp := operatorMap[node.Kind()] if !hasOp { return nil, false } // Verify we have a native binary operator we can optimize. if !parentType.IsNominal() { return nil, false } isNumeric := false switch { case parentType.IsDirectReferenceTo(db.scopegraph.TypeGraph().IntType()): isNumeric = true case parentType.IsDirectReferenceTo(db.scopegraph.TypeGraph().BoolType()): fallthrough case parentType.IsDirectReferenceTo(db.scopegraph.TypeGraph().StringType()): fallthrough default: return nil, false } // Handle the various kinds of operators. switch node.Kind() { case parser.NodeComparisonEqualsExpression: fallthrough case parser.NodeComparisonNotEqualsExpression: // Always allowed. break case parser.NodeComparisonLTEExpression: fallthrough case parser.NodeComparisonLTExpression: fallthrough case parser.NodeComparisonGTEExpression: fallthrough case parser.NodeComparisonGTExpression: // Only allowed for number. if !isNumeric { return nil, false } } boolType := db.scopegraph.TypeGraph().BoolTypeReference() unwrappedLeftExpr := codedom.NominalUnwrapping(leftExpr, parentType, node) unwrappedRightExpr := codedom.NominalUnwrapping(rightExpr, parentType, node) compareExpr := codedom.BinaryOperation(unwrappedLeftExpr, opString, unwrappedRightExpr, node) return codedom.NominalRefWrapping(compareExpr, boolType.NominalDataType(), boolType, node), true }