// Copyright © Microsoft Corporation. // This source file is subject to the Microsoft Permissive License. // See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx. // All other rights reserved. using System; using System.Collections.Generic; using System.Xml.XPath; namespace Microsoft.Ddue.Tools { public class FSharpDeclarationSyntaxGenerator : SyntaxGeneratorTemplate { public FSharpDeclarationSyntaxGenerator(XPathNavigator configuration) : base(configuration) { if (String.IsNullOrEmpty(Language)) Language = "FSharp"; } // namespace: done public override void WriteNamespaceSyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = reflection.Evaluate(apiNameExpression).ToString(); writer.WriteKeyword("namespace"); writer.WriteString(" "); writer.WriteIdentifier(name); } public override void WriteClassSyntax(XPathNavigator reflection, SyntaxWriter writer) { WriteDotNetObject(reflection, writer, "class"); } // TODO: Use apiContainingTypeSubgroupExpression instead of passing in class, struct, interface public override void WriteStructureSyntax(XPathNavigator reflection, SyntaxWriter writer) { WriteDotNetObject(reflection, writer, "struct"); } public override void WriteInterfaceSyntax(XPathNavigator reflection, SyntaxWriter writer) { WriteDotNetObject(reflection, writer, "interface"); } public override void WriteDelegateSyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = (string)reflection.Evaluate(apiNameExpression); bool isSerializable = (bool)reflection.Evaluate(apiIsSerializableTypeExpression); if (isSerializable) WriteAttribute("T:System.SerializableAttribute", writer); WriteAttributes(reflection, writer); writer.WriteKeyword("type"); writer.WriteString(" "); writer.WriteIdentifier(name); writer.WriteString(" = "); writer.WriteLine(); writer.WriteString(" "); writer.WriteKeyword("delegate"); writer.WriteString(" "); writer.WriteKeyword("of"); writer.WriteString(" "); WriteParameters(reflection, writer); writer.WriteKeyword("->"); writer.WriteString(" "); WriteReturnValue(reflection, writer); } public override void WriteEnumerationSyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = (string)reflection.Evaluate(apiNameExpression); bool isSerializable = (bool)reflection.Evaluate(apiIsSerializableTypeExpression); if (isSerializable) WriteAttribute("T:System.SerializableAttribute", writer); WriteAttributes(reflection, writer); writer.WriteKeyword("type"); writer.WriteString(" "); WriteVisibility(reflection, writer); writer.WriteIdentifier(name); } public override void WriteConstructorSyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = (string)reflection.Evaluate(apiContainingTypeNameExpression); bool isStatic = (bool)reflection.Evaluate(apiIsStaticExpression); WriteAttributes(reflection, writer); writer.WriteKeyword("new"); writer.WriteString(" : "); WriteParameters(reflection, writer); writer.WriteKeyword("->"); writer.WriteString(" "); writer.WriteIdentifier(name); } public override void WriteNormalMethodSyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = (string)reflection.Evaluate(apiNameExpression); bool isOverride = (bool)reflection.Evaluate(apiIsOverrideExpression); bool isStatic = (bool)reflection.Evaluate(apiIsStaticExpression); bool isVirtual = (bool)reflection.Evaluate(apiIsVirtualExpression) && !(bool)reflection.Evaluate(apiIsAbstractProcedureExpression); int iterations = isVirtual ? 2 : 1; for (int i = 0; i < iterations; i++) { WriteAttributes(reflection, writer); WriteVisibility(reflection, writer); if (isStatic) { writer.WriteKeyword("static"); writer.WriteString(" "); } if (isVirtual) if (i == 0) { writer.WriteKeyword("abstract"); writer.WriteString(" "); } else { writer.WriteKeyword("override"); writer.WriteString(" "); } else { WriteMemberKeyword(reflection, writer); } writer.WriteIdentifier(name); writer.WriteString(" : "); WriteParameters(reflection, writer); writer.WriteKeyword("->"); writer.WriteString(" "); WriteReturnValue(reflection, writer); writer.WriteString(" "); WriteGenericTemplateConstraints(reflection, writer); if (i == 0) writer.WriteLine(); } } public override void WriteOperatorSyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = (string)reflection.Evaluate(apiNameExpression); string identifier; bool isStatic = (bool)reflection.Evaluate(apiIsStaticExpression); switch (name) { // unary math operators case "UnaryPlus": identifier = "+"; break; case "UnaryNegation": identifier = "-"; break; case "Increment": identifier = "++"; break; case "Decrement": identifier = "--"; break; // unary logical operators case "LogicalNot": identifier = "not"; break; case "True": identifier = "true"; break; case "False": identifier = "false"; break; // binary comparison operators case "Equality": identifier = "="; break; case "Inequality": identifier = "<>"; break; case "LessThan": identifier = "<"; break; case "GreaterThan": identifier = ">"; break; case "LessThanOrEqual": identifier = "<="; break; case "GreaterThanOrEqual": identifier = ">="; break; // binary math operators case "Addition": identifier = "+"; break; case "Subtraction": identifier = "-"; break; case "Multiply": identifier = "*"; break; case "Division": identifier = "/"; break; case "Modulus": identifier = "%"; break; // binary logical operators case "BitwiseAnd": identifier = "&&&"; break; case "BitwiseOr": identifier = "|||"; break; case "ExclusiveOr": identifier = "^^^"; break; // bit-array operators case "OnesComplement": identifier = null; // No F# equiv. break; case "LeftShift": identifier = "<<<"; break; case "RightShift": identifier = ">>>"; break; // unrecognized operator default: identifier = null; break; } if (identifier == null) { writer.WriteMessage("UnsupportedOperator_" + Language); } else { if (isStatic) { writer.WriteKeyword("static"); writer.WriteString(" "); } writer.WriteKeyword("let"); writer.WriteString(" "); writer.WriteKeyword("inline"); writer.WriteKeyword(" "); writer.WriteString("("); writer.WriteIdentifier(identifier); writer.WriteString(")"); WriteParameters(reflection, writer); writer.WriteString(" : "); WriteReturnValue(reflection, writer); } } public override void WriteCastSyntax(XPathNavigator reflection, SyntaxWriter writer) { writer.WriteMessage("UnsupportedCast_" + Language); } // DONE public override void WritePropertySyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = (string)reflection.Evaluate(apiNameExpression); bool isGettable = (bool)reflection.Evaluate(apiIsReadPropertyExpression); bool isSettable = (bool)reflection.Evaluate(apiIsWritePropertyExpression); bool isStatic = (bool)reflection.Evaluate(apiIsStaticExpression); bool isVirtual = (bool)reflection.Evaluate(apiIsVirtualExpression) && !(bool)reflection.Evaluate(apiIsAbstractProcedureExpression); int iterations = isVirtual ? 2 : 1; for (int i = 0; i < iterations; i++) { WriteAttributes(reflection, writer); WriteVisibility(reflection, writer); if (isStatic) { writer.WriteKeyword("static"); writer.WriteString(" "); } if (isVirtual) if (i == 0) { writer.WriteKeyword("abstract"); writer.WriteString(" "); } else { writer.WriteKeyword("override"); writer.WriteString(" "); } else { WriteMemberKeyword(reflection, writer); } writer.WriteIdentifier(name); writer.WriteString(" : "); WriteReturnValue(reflection, writer); if (isSettable) { writer.WriteString(" "); writer.WriteKeyword("with"); writer.WriteString(" "); string getVisibility = (string)reflection.Evaluate(apiGetVisibilityExpression); if (!String.IsNullOrEmpty(getVisibility)) { WriteVisibility(getVisibility, writer); } writer.WriteKeyword("get"); writer.WriteString(", "); string setVisibility = (string)reflection.Evaluate(apiSetVisibilityExpression); if (!String.IsNullOrEmpty(setVisibility)) { WriteVisibility(setVisibility, writer); } writer.WriteKeyword("set"); } if (i == 0) writer.WriteLine(); } } public override void WriteEventSyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = (string)reflection.Evaluate(apiNameExpression); XPathNavigator handler = reflection.SelectSingleNode(apiHandlerOfEventExpression); XPathNavigator args = reflection.SelectSingleNode(apiEventArgsExpression); bool isVirtual = (bool)reflection.Evaluate(apiIsVirtualExpression) && !(bool)reflection.Evaluate(apiIsAbstractProcedureExpression); int iterations = isVirtual ? 2 : 1; for (int i = 0; i < iterations; i++) { WriteAttributes(reflection, writer); WriteVisibility(reflection, writer); if (isVirtual) if (i == 0) { writer.WriteKeyword("abstract"); writer.WriteString(" "); } else { writer.WriteKeyword("override"); writer.WriteString(" "); } else { WriteMemberKeyword(reflection, writer); } writer.WriteIdentifier(name); writer.WriteString(" : "); writer.WriteReferenceLink("T:Microsoft.FSharp.Control.IEvent"); writer.WriteString("<"); WriteTypeReference(handler, writer); writer.WriteString(","); writer.WriteLine(); writer.WriteString(" "); if (args == null) { writer.WriteReferenceLink("T:System.EventArgs"); } else { WriteTypeReference(args, writer); } writer.WriteString(">"); if (i == 0) writer.WriteLine(); } } public override void WriteFieldSyntax(XPathNavigator reflection, SyntaxWriter writer) { string name = (string)reflection.Evaluate(apiNameExpression); bool isStatic = (bool)reflection.Evaluate(apiIsStaticExpression); bool isLiteral = (bool)reflection.Evaluate(apiIsLiteralFieldExpression); bool isInitOnly = (bool)reflection.Evaluate(apiIsInitOnlyFieldExpression); bool isSerialized = (bool)reflection.Evaluate(apiIsSerializedFieldExpression); if (!isSerialized) WriteAttribute("T:System.NonSerializedAttribute", writer); WriteAttributes(reflection, writer); if (isStatic) { writer.WriteKeyword("static"); writer.WriteString(" "); } writer.WriteKeyword("val"); writer.WriteString(" "); if (!isInitOnly) { writer.WriteKeyword("mutable"); writer.WriteString(" "); } WriteVisibility(reflection, writer); writer.WriteIdentifier(name); writer.WriteString(": "); WriteReturnValue(reflection, writer); } private void WriteDotNetObject(XPathNavigator reflection, SyntaxWriter writer, string kind) { string name = reflection.Evaluate(apiNameExpression).ToString(); bool isSerializable = (bool)reflection.Evaluate(apiIsSerializableTypeExpression); XPathNodeIterator implements = reflection.Select(apiImplementedInterfacesExpression); XPathNavigator baseClass = reflection.SelectSingleNode(apiBaseClassExpression); bool hasBaseClass = (baseClass != null) && !((bool)baseClass.Evaluate(typeIsObjectExpression)); // CLR considers interfaces abstract. bool isAbstract = (bool)reflection.Evaluate(apiIsAbstractTypeExpression) && kind != "interface"; bool isSealed = (bool)reflection.Evaluate(apiIsSealedTypeExpression); if (isAbstract) WriteAttribute("T:Microsoft.FSharp.Core.AbstractClassAttribute", writer); if (isSealed) WriteAttribute("T:Microsoft.FSharp.Core.SealedAttribute", writer); if (isSerializable) WriteAttribute("T:System.SerializableAttribute", writer); WriteAttributes(reflection, writer); writer.WriteKeyword("type"); writer.WriteString(" "); writer.WriteIdentifier(name); WriteGenericTemplates(reflection, writer); writer.WriteString(" = "); if (hasBaseClass || implements.Count != 0) { writer.WriteLine(); writer.WriteString(" "); } writer.WriteKeyword(kind); if (hasBaseClass || implements.Count != 0) { writer.WriteLine(); } if (hasBaseClass) { writer.WriteString(" "); writer.WriteKeyword("inherit"); writer.WriteString(" "); WriteTypeReference(baseClass, writer); writer.WriteLine(); } while (implements.MoveNext()) { XPathNavigator implement = implements.Current; writer.WriteString(" "); writer.WriteKeyword("interface"); writer.WriteString(" "); WriteTypeReference(implement, writer); writer.WriteLine(); } if (hasBaseClass || implements.Count != 0) { writer.WriteString(" "); } else { writer.WriteString(" "); } writer.WriteKeyword("end"); } // Visibility private void WriteVisibility(XPathNavigator reflection, SyntaxWriter writer) { string visibility = reflection.Evaluate(apiVisibilityExpression).ToString(); WriteVisibility(visibility, writer); } private Dictionary visibilityDictionary = new Dictionary() { { "public", null }, // Default in F#, so unnecessary. { "family", null }, // Not supported in F#, section 8.8 in F# spec. { "family or assembly", null }, // Not supported in F#, section 8.8 in F# spec. { "family and assembly", null }, // Not supported in F#, section 8.8 in F# spec. { "assembly", "internal" }, { "private", "private" }, }; // DONE private void WriteVisibility(string visibility, SyntaxWriter writer) { if(visibilityDictionary.ContainsKey(visibility) && visibilityDictionary[visibility] != null) { writer.WriteKeyword(visibilityDictionary[visibility]); writer.WriteString(" "); } } // Write member | abstract | override private void WriteMemberKeyword(XPathNavigator reflection, SyntaxWriter writer) { bool isOverride = (bool)reflection.Evaluate(apiIsOverrideExpression); bool isAbstract = (bool)reflection.Evaluate(apiIsAbstractProcedureExpression); if (isOverride) { writer.WriteKeyword("override"); } else if (isAbstract) { writer.WriteKeyword("abstract"); } else { writer.WriteKeyword("member"); } writer.WriteString(" "); return; } // Attributes private void WriteAttribute(string reference, SyntaxWriter writer) { writer.WriteString("[<"); writer.WriteReferenceLink(reference); writer.WriteString(">]"); writer.WriteLine(); } // Initial version private void WriteAttributes(XPathNavigator reflection, SyntaxWriter writer) { XPathNodeIterator attributes = (XPathNodeIterator)reflection.Evaluate(apiAttributesExpression); foreach (XPathNavigator attribute in attributes) { XPathNavigator type = attribute.SelectSingleNode(attributeTypeExpression); if (type.GetAttribute("api", String.Empty) == "T:System.Runtime.CompilerServices.ExtensionAttribute") continue; writer.WriteString("[<"); WriteTypeReference(type, writer); XPathNodeIterator arguments = (XPathNodeIterator)attribute.Select(attributeArgumentsExpression); XPathNodeIterator assignments = (XPathNodeIterator)attribute.Select(attributeAssignmentsExpression); if ((arguments.Count > 0) || (assignments.Count > 0)) { writer.WriteString("("); while (arguments.MoveNext()) { XPathNavigator argument = arguments.Current; if (arguments.CurrentPosition > 1) { writer.WriteString(", "); if (writer.Position > maxPosition) { writer.WriteLine(); writer.WriteString(" "); } } WriteValue(argument, writer); } if ((arguments.Count > 0) && (assignments.Count > 0)) writer.WriteString(", "); while (assignments.MoveNext()) { XPathNavigator assignment = assignments.Current; if (assignments.CurrentPosition > 1) { writer.WriteString(", "); if (writer.Position > maxPosition) { writer.WriteLine(); writer.WriteString(" "); } } writer.WriteString((string)assignment.Evaluate(assignmentNameExpression)); writer.WriteString(" = "); WriteValue(assignment, writer); } writer.WriteString(")"); } writer.WriteString(">]"); writer.WriteLine(); } } private void WriteValue(XPathNavigator parent, SyntaxWriter writer) { XPathNavigator type = parent.SelectSingleNode(attributeTypeExpression); XPathNavigator value = parent.SelectSingleNode(valueExpression); if (value == null) Console.WriteLine("null value"); switch (value.LocalName) { case "nullValue": writer.WriteKeyword("null"); break; case "typeValue": writer.WriteKeyword("typeof"); writer.WriteString("("); WriteTypeReference(value.SelectSingleNode(typeExpression), writer); writer.WriteString(")"); break; case "enumValue": XPathNodeIterator fields = value.SelectChildren(XPathNodeType.Element); while (fields.MoveNext()) { string name = fields.Current.GetAttribute("name", String.Empty); if (fields.CurrentPosition > 1) writer.WriteString("|"); WriteTypeReference(type, writer); writer.WriteString("."); writer.WriteString(name); } break; case "value": string text = value.Value; string typeId = type.GetAttribute("api", String.Empty); switch (typeId) { case "T:System.String": writer.WriteString("\""); writer.WriteString(text); writer.WriteString("\""); break; case "T:System.Boolean": bool bool_value = Convert.ToBoolean(text); if (bool_value) { writer.WriteKeyword("true"); } else { writer.WriteKeyword("false"); } break; case "T:System.Char": writer.WriteString("'"); writer.WriteString(text); writer.WriteString("'"); break; } break; } } // Generics private void WriteGenericTemplates(XPathNavigator reflection, SyntaxWriter writer) { XPathNodeIterator templates = (XPathNodeIterator)reflection.Evaluate(apiTemplatesExpression); if (templates.Count == 0) return; writer.WriteString("<"); while (templates.MoveNext()) { XPathNavigator template = templates.Current; string name = template.GetAttribute("name", String.Empty); writer.WriteString("'"); writer.WriteString(name); if (templates.CurrentPosition < templates.Count) writer.WriteString(", "); } WriteGenericTemplateConstraints(reflection, writer); writer.WriteString(">"); } private void WriteGenericTemplateConstraints(XPathNavigator reflection, SyntaxWriter writer) { XPathNodeIterator templates = reflection.Select(apiTemplatesExpression); if (templates.Count == 0) return; foreach (XPathNavigator template in templates) { bool constrained = (bool)template.Evaluate(templateIsConstrainedExpression); if (constrained) { string name = (string)template.Evaluate(templateNameExpression); writer.WriteString(" "); writer.WriteKeyword("when"); writer.WriteString(" '"); writer.WriteString(name); writer.WriteString(" : "); } else { continue; } bool value = (bool)template.Evaluate(templateIsValueTypeExpression); bool reference = (bool)template.Evaluate(templateIsReferenceTypeExpression); bool constructor = (bool)template.Evaluate(templateIsConstructableExpression); XPathNodeIterator constraints = template.Select(templateConstraintsExpression); // keep track of whether there is a previous constraint, so we know whether to put a comma bool previous = false; if (value) { if (previous) writer.WriteString(", "); writer.WriteKeyword("struct"); previous = true; } if (reference) { if (previous) writer.WriteString(", "); writer.WriteKeyword("not struct"); previous = true; } if (constructor) { if (previous) writer.WriteString(", "); writer.WriteKeyword("new"); writer.WriteString("()"); previous = true; } foreach (XPathNavigator constraint in constraints) { if (previous) writer.WriteString(" and "); WriteTypeReference(constraint, writer); previous = true; } } } // Parameters private void WriteParameters(XPathNavigator reflection, SyntaxWriter writer) { XPathNodeIterator parameters = reflection.Select(apiParametersExpression); if (parameters.Count > 0) { WriteParameters(parameters, reflection, writer); } else { writer.WriteKeyword("unit"); writer.WriteString(" "); } return; } private void WriteParameters(XPathNodeIterator parameters, XPathNavigator reflection, SyntaxWriter writer) { bool isExtension = (bool)reflection.Evaluate(apiIsExtensionMethod); writer.WriteLine(); while (parameters.MoveNext()) { XPathNavigator parameter = parameters.Current; string name = (string)parameter.Evaluate(parameterNameExpression); bool isOut = (bool)parameter.Evaluate(parameterIsOutExpression); bool isRef = (bool)parameter.Evaluate(parameterIsRefExpression); XPathNavigator type = parameter.SelectSingleNode(parameterTypeExpression); writer.WriteString(" "); writer.WriteParameter(name); writer.WriteString(":"); WriteTypeReference(type, writer); if (isOut || isRef) { writer.WriteString(" "); writer.WriteKeyword("byref"); } if (parameters.CurrentPosition != parameters.Count) { writer.WriteString(" * "); writer.WriteLine(); } else { writer.WriteString(" "); } } } // Return Value private void WriteReturnValue(XPathNavigator reflection, SyntaxWriter writer) { XPathNavigator type = reflection.SelectSingleNode(apiReturnTypeExpression); if (type == null) { writer.WriteKeyword("unit"); } else { WriteTypeReference(type, writer); } } // References private void WriteTypeReference(XPathNavigator reference, SyntaxWriter writer) { switch (reference.LocalName) { case "arrayOf": int rank = Convert.ToInt32(reference.GetAttribute("rank", String.Empty)); XPathNavigator element = reference.SelectSingleNode(typeExpression); WriteTypeReference(element, writer); writer.WriteString("["); for (int i = 1; i < rank; i++) { writer.WriteString(","); } writer.WriteString("]"); break; case "pointerTo": XPathNavigator pointee = reference.SelectSingleNode(typeExpression); writer.WriteKeyword("nativeptr"); writer.WriteString("<"); WriteTypeReference(pointee, writer); writer.WriteString(">"); break; case "referenceTo": XPathNavigator referee = reference.SelectSingleNode(typeExpression); WriteTypeReference(referee, writer); break; case "type": string id = reference.GetAttribute("api", String.Empty); WriteNormalTypeReference(id, writer); XPathNodeIterator typeModifiers = reference.Select(typeModifiersExpression); while (typeModifiers.MoveNext()) { WriteTypeReference(typeModifiers.Current, writer); } break; case "template": string name = reference.GetAttribute("name", String.Empty); writer.WriteString("'"); writer.WriteString(name); XPathNodeIterator modifiers = reference.Select(typeModifiersExpression); while (modifiers.MoveNext()) { WriteTypeReference(modifiers.Current, writer); } break; case "specialization": writer.WriteString("<"); XPathNodeIterator arguments = reference.Select(specializationArgumentsExpression); while (arguments.MoveNext()) { if (arguments.CurrentPosition > 1) writer.WriteString(", "); WriteTypeReference(arguments.Current, writer); } writer.WriteString(">"); break; } } // DONE private void WriteNormalTypeReference(string api, SyntaxWriter writer) { switch (api) { case "T:System.Void": writer.WriteReferenceLink(api, "unit"); break; case "T:System.String": writer.WriteReferenceLink(api, "string"); break; case "T:System.Boolean": writer.WriteReferenceLink(api, "bool"); break; case "T:System.Byte": writer.WriteReferenceLink(api, "byte"); break; case "T:System.SByte": writer.WriteReferenceLink(api, "sbyte"); break; case "T:System.Char": writer.WriteReferenceLink(api, "char"); break; case "T:System.Int16": writer.WriteReferenceLink(api, "int16"); break; case "T:System.Int32": writer.WriteReferenceLink(api, "int"); break; case "T:System.Int64": writer.WriteReferenceLink(api, "int64"); break; case "T:System.UInt16": writer.WriteReferenceLink(api, "uint16"); break; case "T:System.UInt32": writer.WriteReferenceLink(api, "uint32"); break; case "T:System.UInt64": writer.WriteReferenceLink(api, "uint64"); break; case "T:System.Single": writer.WriteReferenceLink(api, "float32"); break; case "T:System.Double": writer.WriteReferenceLink(api, "float"); break; case "T:System.Decimal": writer.WriteReferenceLink(api, "decimal"); break; default: writer.WriteReferenceLink(api); break; } } } }