// 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) }
// 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) }
// AreEqual returns a call to the comparison operator between the two expressions. func AreEqual(leftExpr Expression, rightExpr Expression, comparisonType typegraph.TypeReference, tdg *typegraph.TypeGraph, basis compilergraph.GraphNode) Expression { operator, found := comparisonType.ResolveMember("equals", typegraph.MemberResolutionOperator) if !found { panic(fmt.Sprintf("Unknown equals operator under type %v", comparisonType)) } return MemberCall( StaticMemberReference(operator, comparisonType, basis), operator, []Expression{leftExpr, rightExpr}, basis) }
// 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) }
// ResolveStaticMember attempts to resolve a member (child) with the given name under this named scope, which // must be Static. func (nsi *namedScopeInfo) ResolveStaticMember(name string, module compilercommon.InputSource, staticType typegraph.TypeReference) (namedScopeInfo, error) { if !nsi.IsStatic() { return namedScopeInfo{}, fmt.Errorf("Could not find static name '%v' under non-static scope", name) } if nsi.typeInfo != nil { typeMember, rerr := staticType.ResolveAccessibleMember(name, module, typegraph.MemberResolutionStatic) if rerr != nil { return namedScopeInfo{}, rerr } return namedScopeInfo{srg.SRGNamedScope{}, typeMember, nsi.sb}, nil } else { namedScopeOrImport, found := nsi.srgInfo.ResolveNameUnderScope(name) if !found { return namedScopeInfo{}, fmt.Errorf("Could not find static name '%v' under %v %v", name, nsi.srgInfo.Title(), nsi.srgInfo.Name()) } return nsi.sb.processSRGNameOrInfo(namedScopeOrImport) } }
// 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) }
// InnerInstanceName returns the name of an inner instance of the given type, when accessed under a // type instance which structurally composes it. func (p Pather) InnerInstanceName(innerType typegraph.TypeReference) string { var name = unidecode.Unidecode(innerType.ReferredType().Name()) if !innerType.HasGenerics() { return name } for _, generic := range innerType.Generics() { name = name + "$" name = name + p.InnerInstanceName(generic) } return name }
// scopeReturnStatement scopes a return statement in the SRG. func (sb *scopeBuilder) scopeReturnStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var actualReturnType typegraph.TypeReference = sb.sg.tdg.VoidTypeReference() exprNode, found := node.TryGetNode(parser.NodeReturnStatementValue) if found { exprScope := sb.getScope(exprNode, context) if !exprScope.GetIsValid() { return newScope(). Invalid(). GetScope() } actualReturnType = exprScope.ResolvedTypeRef(sb.sg.tdg) } // Ensure the return types match. expectedReturnType, _ := sb.sg.tdg.LookupReturnType(context.parentImplemented) if expectedReturnType.IsVoid() { if !actualReturnType.IsVoid() { sb.decorateWithError(node, "No return value expected here, found value of type '%v'", actualReturnType) return newScope(). Invalid(). Returning(actualReturnType, true). GetScope() } } else if actualReturnType.IsVoid() { sb.decorateWithError(node, "Expected non-void resolved value") return newScope(). Invalid(). Returning(actualReturnType, true). GetScope() } else { if serr := actualReturnType.CheckSubTypeOf(expectedReturnType); serr != nil { sb.decorateWithError(node, "Expected return value of type '%v': %v", expectedReturnType, serr) return newScope(). Invalid(). Returning(actualReturnType, true). GetScope() } } return newScope(). IsTerminatingStatement(). Valid(). Returning(actualReturnType, true). GetScope() }
// scopeSmlNormalAttribute scopes an SML expression attribute under a declaration. func (sb *scopeBuilder) scopeSmlAttribute(node compilergraph.GraphNode, propsType typegraph.TypeReference, context scopeContext) (string, bool) { attributeName := node.Get(parser.NodeSmlAttributeName) // If the props type is a struct or class, ensure that the attribute name exists. var allowedValueType = sb.sg.tdg.AnyTypeReference() if propsType.IsRefToStruct() || propsType.IsRefToClass() { module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) resolvedMember, rerr := propsType.ResolveAccessibleMember(attributeName, module, typegraph.MemberResolutionInstance) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return attributeName, false } allowedValueType = resolvedMember.AssignableType() } else { // The props type must be a mapping, so the value must match it value type. allowedValueType = propsType.Generics()[0] } // Scope the attribute value (if any). If none, then we default to a boolean value. var attributeValueType = sb.sg.tdg.BoolTypeReference() valueNode, hasValueNode := node.TryGetNode(parser.NodeSmlAttributeValue) if hasValueNode { attributeValueScope := sb.getScope(valueNode, context) if !attributeValueScope.GetIsValid() { return attributeName, false } attributeValueType = attributeValueScope.ResolvedTypeRef(sb.sg.tdg) } // Ensure it matches the assignable value type. if serr := attributeValueType.CheckSubTypeOf(allowedValueType); serr != nil { sb.decorateWithError(node, "Cannot assign value of type %v for attribute %v: %v", attributeValueType, attributeName, serr) return attributeName, false } return attributeName, true }
// scopeSmlDecoratorAttribute scopes a decorator SML expression attribute under a declaration. func (sb *scopeBuilder) scopeSmlDecorator(node compilergraph.GraphNode, declaredType typegraph.TypeReference, context scopeContext) (typegraph.TypeReference, bool) { // Resolve the scope of the decorator. decoratorScope := sb.getScope(node.GetNode(parser.NodeSmlDecoratorPath), context) if !decoratorScope.GetIsValid() { return declaredType, false } namedScope, _ := sb.getNamedScopeForScope(decoratorScope) decoratorName := namedScope.Name() // Register that we make use of the decorator function. context.staticDependencyCollector.registerNamedDependency(namedScope) // Ensure the decorator refers to a function. decoratorType := decoratorScope.ResolvedTypeRef(sb.sg.tdg) if !decoratorType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) { sb.decorateWithError(node, "SML declaration decorator '%v' must refer to a function. Found: %v", decoratorName, decoratorType) return declaredType, false } // Ensure that the decorator doesn't return void. returnType := decoratorType.Generics()[0] if returnType.IsVoid() { sb.decorateWithError(node, "SML declaration decorator '%v' cannot return void", decoratorName) return declaredType, false } // Scope the attribute value (if any). var attributeValueType = sb.sg.tdg.BoolTypeReference() valueNode, hasValueNode := node.TryGetNode(parser.NodeSmlDecoratorValue) if hasValueNode { attributeValueScope := sb.getScope(valueNode, context) if !attributeValueScope.GetIsValid() { return returnType, false } attributeValueType = attributeValueScope.ResolvedTypeRef(sb.sg.tdg) } // Ensure the decorator takes the decorated type and a value as parameters. if decoratorType.ParameterCount() != 2 { sb.decorateWithError(node, "SML declaration decorator '%v' must refer to a function with two parameters. Found: %v", decoratorName, decoratorType) return returnType, false } // Ensure the first parameter is the declared type. allowedDecoratedType := decoratorType.Parameters()[0] allowedValueType := decoratorType.Parameters()[1] if serr := declaredType.CheckSubTypeOf(allowedDecoratedType); serr != nil { sb.decorateWithError(node, "SML declaration decorator '%v' expects to decorate an instance of type %v: %v", decoratorName, allowedDecoratedType, serr) return declaredType, false } // Ensure that the second parameter is the value type. if serr := attributeValueType.CheckSubTypeOf(allowedValueType); serr != nil { sb.decorateWithError(node, "Cannot assign value of type %v for decorator '%v': %v", attributeValueType, decoratorName, serr) return returnType, false } // The returned type is that of the decorator. return returnType, true }
// TypeReferenceCall returns source for retrieving an object reference to the type defined by the given // type reference. func (p Pather) TypeReferenceCall(typeRef typegraph.TypeReference) string { if typeRef.IsAny() { return "$t.any" } if typeRef.IsStruct() { return "$t.struct" } if typeRef.IsNull() { return "$t.null" } if typeRef.IsVoid() { return "$t.void" } referredType := typeRef.ReferredType() if referredType.TypeKind() == typegraph.GenericType { return referredType.Name() } // Add the type name. typePath := p.GetTypePath(referredType) // If there are no generics, then simply return the type path. if !typeRef.HasGenerics() { return typePath } // Invoke the type with generics (if any). var genericsString = "(" for index, generic := range typeRef.Generics() { if index > 0 { genericsString = genericsString + ", " } genericsString = genericsString + p.TypeReferenceCall(generic) } genericsString = genericsString + ")" return typePath + genericsString }
// WithGenericType marks the scope as having the given generic type. func (sib *scopeInfoBuilder) WithGenericType(generic typegraph.TypeReference) *scopeInfoBuilder { genericValue := generic.Value() sib.info.GenericType = &genericValue return sib }
// WithStaticType marks the scope as having the given static type. func (sib *scopeInfoBuilder) WithStaticType(static typegraph.TypeReference) *scopeInfoBuilder { staticValue := static.Value() sib.info.StaticType = &staticValue return sib }
// Resolving marks the scope as resolving a value of the given type. func (sib *scopeInfoBuilder) Resolving(resolved typegraph.TypeReference) *scopeInfoBuilder { resolvedValue := resolved.Value() sib.info.ResolvedType = &resolvedValue return sib }
// Assignable marks the scope as being assignable with a value of the given type. func (sib *scopeInfoBuilder) Assignable(assignable typegraph.TypeReference) *scopeInfoBuilder { assignableValue := assignable.Value() sib.info.AssignableType = &assignableValue return sib }
// Returning marks the scope as returning a value of the given type. func (sib *scopeInfoBuilder) Returning(returning typegraph.TypeReference, settlesScope bool) *scopeInfoBuilder { returnedValue := returning.Value() sib.info.ReturnedType = &returnedValue sib.info.IsSettlingScope = &settlesScope return sib }
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 }
// appendSigReference appends the given type reference to the given signature code // buffer, returning true if any portion is dynamic. func appendSigReference(typeRef typegraph.TypeReference, buf *bytes.Buffer) bool { if typeRef.IsAny() { buf.WriteString("any") return false } if typeRef.IsStruct() { buf.WriteString("struct") return false } if typeRef.IsNull() { buf.WriteString("null") return false } if typeRef.IsVoid() { buf.WriteString("void") return false } referredType := typeRef.ReferredType() if referredType.TypeKind() == typegraph.GenericType { buf.WriteString("\" + $t.typeid(") buf.WriteString(referredType.Name()) buf.WriteString(") + \"") return true } // Add the type's unique ID. buf.WriteString(referredType.GlobalUniqueId()) // If there are no generics, then we're done. if !typeRef.HasGenerics() { return false } // Otherwise, append the generics. buf.WriteRune('<') var dynamic = false for index, generic := range typeRef.Generics() { if index > 0 { buf.WriteRune(',') } genericDynamic := appendSigReference(generic, buf) dynamic = dynamic || genericDynamic } buf.WriteRune('>') return dynamic }