// 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) }
// buildBooleanUnaryExpression builds the CodeDOM for a native unary operator. func (db *domBuilder) buildBooleanUnaryExpression(node compilergraph.GraphNode, op string) codedom.Expression { boolType := db.scopegraph.TypeGraph().BoolTypeReference() childExpr := codedom.NominalUnwrapping(db.getExpression(node, parser.NodeUnaryExpressionChildExpr), boolType, node) return codedom.NominalWrapping( codedom.UnaryOperation(op, childExpr, node), db.scopegraph.TypeGraph().BoolType(), node) }
// buildStringLiteral builds the CodeDOM for a string literal. func (db *domBuilder) buildStringLiteral(node compilergraph.GraphNode) codedom.Expression { stringValueStr := node.Get(parser.NodeStringLiteralExpressionValue) if stringValueStr[0] == '`' { unquoted := stringValueStr[1 : len(stringValueStr)-1] stringValueStr = strconv.Quote(unquoted) } return codedom.NominalWrapping(codedom.LiteralValue(stringValueStr, node), db.scopegraph.TypeGraph().StringType(), 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) }
// buildIsComparisonExpression builds the CodeDOM for an is comparison operator. func (db *domBuilder) buildIsComparisonExpression(node compilergraph.GraphNode) codedom.Expression { generatedLeftExpr := db.getExpression(node, parser.NodeBinaryExpressionLeftExpr) // Check for a `not` subexpression. If found, we invert the check. op := "==" rightExpr := node.GetNode(parser.NodeBinaryExpressionRightExpr) if rightExpr.Kind() == parser.NodeKeywordNotExpression { op = "!=" rightExpr = rightExpr.GetNode(parser.NodeUnaryExpressionChildExpr) } generatedRightExpr := db.buildExpression(rightExpr) return codedom.NominalWrapping( codedom.BinaryOperation(generatedLeftExpr, op, generatedRightExpr, node), db.scopegraph.TypeGraph().BoolType(), node) }
// buildNumericLiteral builds the CodeDOM for a numeric literal. func (db *domBuilder) buildNumericLiteral(node compilergraph.GraphNode) codedom.Expression { numericValueStr := node.Get(parser.NodeNumericLiteralExpressionValue) if strings.HasSuffix(numericValueStr, "f") { numericValueStr = numericValueStr[0 : len(numericValueStr)-1] } // Handle binary. if strings.HasPrefix(numericValueStr, "0b") || strings.HasPrefix(numericValueStr, "0B") { parsed, _ := strconv.ParseInt(numericValueStr[2:], 2, 64) numericValueStr = strconv.Itoa(int(parsed)) } // Note: Handles Hex. intValue, isNotInt := strconv.ParseInt(numericValueStr, 0, 64) if isNotInt == nil { numericValueStr = strconv.Itoa(int(intValue)) } exprScope, _ := db.scopegraph.GetScope(node) numericType := exprScope.ResolvedTypeRef(db.scopegraph.TypeGraph()).ReferredType() return codedom.NominalWrapping(codedom.LiteralValue(numericValueStr, node), numericType, 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 } }
// buildBooleanLiteral builds the CodeDOM for a boolean literal. func (db *domBuilder) buildBooleanLiteral(node compilergraph.GraphNode) codedom.Expression { booleanValueStr := node.Get(parser.NodeBooleanLiteralExpressionValue) return codedom.NominalWrapping(codedom.LiteralValue(booleanValueStr, node), db.scopegraph.TypeGraph().BoolType(), node) }
// buildJumpingCaseStatement builds the CodeDOM for a statement which jumps (branches) based on // various cases. func (db *domBuilder) buildJumpingCaseStatement(node compilergraph.GraphNode, casePredicate compilergraph.Predicate, caseStatementPredicate compilergraph.Predicate, caseExpressionPredicate compilergraph.Predicate, startStatement codedom.Statement, getCheckExpression checkExpressionGenerator) (codedom.Statement, codedom.Statement) { // A branching statement is an extended version of a conditional statement. For each branch, we check if the // branch's value matches the conditional (or "true" if there is no conditional expr). On true, the block // under the case is executed, followed by a jump to the final statement. Otherwise, the next block // in the chain is executed. finalStatement := codedom.EmptyStatement(node) // Save a break statement reference for the generated statement. db.breakStatementMap[node.NodeId] = finalStatement // Generate the statements and check expressions for each of the cases. var branchJumps = make([]*codedom.ConditionalJumpNode, 0) cit := node.StartQuery(). Out(casePredicate). BuildNodeIterator() for cit.Next() { caseNode := cit.Node() caseStart, caseEnd := db.getStatements(caseNode, caseStatementPredicate) caseExpressionNode, hasCaseExpression := caseNode.TryGetNode(caseExpressionPredicate) // Generate the expression against which we should check. If there is no expression on // the case, then this is the default and we compare against "true". var branchExpression codedom.Expression = nil if hasCaseExpression { branchExpression = getCheckExpression(caseExpressionNode) } else { branchExpression = codedom.NominalWrapping( codedom.LiteralValue("true", caseNode), db.scopegraph.TypeGraph().BoolType(), caseNode) } branchJump := codedom.BranchOn(branchExpression, caseNode) branchJump.True = caseStart branchJumps = append(branchJumps, branchJump) // Jump the end statement, once complete, to the final statement. codedom.AssignNextStatement(caseEnd, codedom.UnconditionalJump(finalStatement, caseNode)) } // Generate a "trampoline" that checks each case. for index, branchJump := range branchJumps { // Jump the current branch (on false) to the next conditional check. if index < len(branchJumps)-1 { branchJump.False = branchJumps[index+1] } else { branchJump.False = finalStatement } } // Have the start state jump to the first branch (if any). if len(branchJumps) > 0 { codedom.AssignNextStatement(startStatement, branchJumps[0]) } return startStatement, finalStatement }
// 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 }
// buildSmlText builds the CodeDOM for a SML text literal. func (db *domBuilder) buildSmlText(node compilergraph.GraphNode) codedom.Expression { stringValueStr := strconv.Quote(node.Get(parser.NodeSmlTextValue)) return codedom.NominalWrapping(codedom.LiteralValue(stringValueStr, node), db.scopegraph.TypeGraph().StringType(), node) }