From 2040d8271e1f50af823e5b967c22c4fc326a54a0 Mon Sep 17 00:00:00 2001 From: triton Date: Wed, 29 Aug 2012 21:42:03 +0100 Subject: [PATCH] Renamed current generator to GCC generator (part 2). --- src/GCCGenerator/Access.cs | 9 + src/GCCGenerator/Class.cs | 82 + src/GCCGenerator/Enumeration.cs | 21 + src/GCCGenerator/Field.cs | 52 + src/GCCGenerator/Filter.cs | 36 + src/GCCGenerator/Generator.cs | 622 +++++++ src/GCCGenerator/Lib.cs | 33 + src/GCCGenerator/Makefile.am | 106 ++ src/GCCGenerator/Method.cs | 105 ++ src/GCCGenerator/Namespace.cs | 49 + src/GCCGenerator/Node.cs | 95 + src/GCCGenerator/Options.cs | 1597 +++++++++++++++++ src/GCCGenerator/Parameter.cs | 42 + src/GCCGenerator/Property.cs | 55 + src/GCCGenerator/Templates/Base.cs | 181 ++ src/GCCGenerator/Templates/Base.tt | 2 + src/GCCGenerator/Templates/BaseMembers.cs | 36 + .../Templates/CSharp/CSharpClass.cs | 1265 +++++++++++++ .../Templates/CSharp/CSharpClass.tt | 404 +++++ .../Templates/CSharp/CSharpEnum.cs | 184 ++ .../Templates/CSharp/CSharpEnum.tt | 28 + .../Templates/CSharp/CSharpLanguage.cs | 73 + .../Templates/CSharp/CSharpLibs.cs | 116 ++ .../Templates/CSharp/CSharpLibs.tt | 19 + src/GCCGenerator/Templates/Context.cs | 19 + src/GCCGenerator/generator.csproj | 151 ++ src/GCCGenerator/generator.in | 3 + src/GCCGenerator/generator.sln | 20 + 28 files changed, 5405 insertions(+) create mode 100644 src/GCCGenerator/Access.cs create mode 100644 src/GCCGenerator/Class.cs create mode 100644 src/GCCGenerator/Enumeration.cs create mode 100644 src/GCCGenerator/Field.cs create mode 100644 src/GCCGenerator/Filter.cs create mode 100644 src/GCCGenerator/Generator.cs create mode 100644 src/GCCGenerator/Lib.cs create mode 100644 src/GCCGenerator/Makefile.am create mode 100644 src/GCCGenerator/Method.cs create mode 100644 src/GCCGenerator/Namespace.cs create mode 100644 src/GCCGenerator/Node.cs create mode 100644 src/GCCGenerator/Options.cs create mode 100644 src/GCCGenerator/Parameter.cs create mode 100644 src/GCCGenerator/Property.cs create mode 100644 src/GCCGenerator/Templates/Base.cs create mode 100644 src/GCCGenerator/Templates/Base.tt create mode 100644 src/GCCGenerator/Templates/BaseMembers.cs create mode 100644 src/GCCGenerator/Templates/CSharp/CSharpClass.cs create mode 100644 src/GCCGenerator/Templates/CSharp/CSharpClass.tt create mode 100644 src/GCCGenerator/Templates/CSharp/CSharpEnum.cs create mode 100644 src/GCCGenerator/Templates/CSharp/CSharpEnum.tt create mode 100644 src/GCCGenerator/Templates/CSharp/CSharpLanguage.cs create mode 100644 src/GCCGenerator/Templates/CSharp/CSharpLibs.cs create mode 100644 src/GCCGenerator/Templates/CSharp/CSharpLibs.tt create mode 100644 src/GCCGenerator/Templates/Context.cs create mode 100644 src/GCCGenerator/generator.csproj create mode 100644 src/GCCGenerator/generator.in create mode 100644 src/GCCGenerator/generator.sln diff --git a/src/GCCGenerator/Access.cs b/src/GCCGenerator/Access.cs new file mode 100644 index 00000000..96098ea2 --- /dev/null +++ b/src/GCCGenerator/Access.cs @@ -0,0 +1,9 @@ +using System; + +public enum Access { + @private, + @protected, + @public +} + + diff --git a/src/GCCGenerator/Class.cs b/src/GCCGenerator/Class.cs new file mode 100644 index 00000000..04b9ca57 --- /dev/null +++ b/src/GCCGenerator/Class.cs @@ -0,0 +1,82 @@ +// +// Class.cs: Represents a C++ class +// +// Author: +// Alexander Corrado (alexander.corrado@gmail.com) +// Andreia Gaita (shana@spoiledcat.net) +// Zoltan Varga +// +// Copyright (C) 2011 Novell Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; + +using Mono.Cxxi; + +public class Class : Namespace { + + public Class (Node n) + : base (n) + { + BaseClasses = new List (); + Fields = new List (); + Properties = new List (); + Methods = new List (); + NestedClasses = new List (); + NestedEnums = new List (); + } + + public List BaseClasses { + get; set; + } + + public List NestedClasses { + get; set; + } + + public List NestedEnums { + get; set; + } + + public List Fields { + get; set; + } + + public List Properties { + get; set; + } + + public List Methods { + get; + set; + } + + public CppType MangleType { + get; + set; + } + + public bool Disable { + get; set; + } + +} diff --git a/src/GCCGenerator/Enumeration.cs b/src/GCCGenerator/Enumeration.cs new file mode 100644 index 00000000..df51d26b --- /dev/null +++ b/src/GCCGenerator/Enumeration.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +public class Enumeration : Namespace { + + public struct Item { + public string Name; + public int Value; + } + + public Enumeration (Node n) + : base (n) + { + this.Items = new List (); + } + + public List Items { + get; set; + } +} + diff --git a/src/GCCGenerator/Field.cs b/src/GCCGenerator/Field.cs new file mode 100644 index 00000000..46f6ac3c --- /dev/null +++ b/src/GCCGenerator/Field.cs @@ -0,0 +1,52 @@ +// +// Field.cs: Represents a field of a C++ class +// +// Author: +// Alexander Corrado (alexander.corrado@gmail.com) +// Andreia Gaita (shana@spoiledcat.net) +// Zoltan Varga +// +// Copyright (C) 2011 Novell Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using Mono.Cxxi; + +public class Field { + + public Field (string name, CppType type, Access access) { + Name = name; + Type = type; + Access = access; + } + + public string Name { + get; set; + } + + public CppType Type { + get; set; + } + + public Access Access { + get; set; + } +} diff --git a/src/GCCGenerator/Filter.cs b/src/GCCGenerator/Filter.cs new file mode 100644 index 00000000..89d04f0e --- /dev/null +++ b/src/GCCGenerator/Filter.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Xml.Linq; +using System.Collections.Generic; + +public enum FilterMode { + Include, + Exclude, + External +} + +public enum ImplementationType { + @class, + @struct +} + +public struct Filter { + + public string TypeName { get; set; } + public FilterMode Mode { get; set; } + public ImplementationType ImplType { get; set; } + + public static Dictionary Load (XDocument doc, out FilterMode @default) + { + string value; + @default = (value = (string)doc.Root.Attribute ("default")) != null ? (FilterMode)Enum.Parse (typeof (FilterMode), value) : FilterMode.Include; + + var rules = from rule in doc.Root.Elements () + let mode = (FilterMode)Enum.Parse (typeof (FilterMode), rule.Name.LocalName) + let impl = (value = (string)rule.Attribute ("implementation")) != null ? (ImplementationType)Enum.Parse (typeof (ImplementationType), value) : ImplementationType.@class + select new Filter { TypeName = rule.Value, Mode = mode, ImplType = impl }; + + + return rules.ToDictionary (r => r.TypeName); + } +} \ No newline at end of file diff --git a/src/GCCGenerator/Generator.cs b/src/GCCGenerator/Generator.cs new file mode 100644 index 00000000..32800a06 --- /dev/null +++ b/src/GCCGenerator/Generator.cs @@ -0,0 +1,622 @@ +// +// Generator.cs: C++ Interop Code Generator +// +// + +using System; +using System.IO; +using System.Diagnostics; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Linq; +using System.Linq; +using System.Reflection; + +using Templates; +using NDesk.Options; +using Mono.Cxxi; + +public class Generator { + + // Command line arguments + public string InputFileName { get; set; } + public string OutputDir { get; set; } + public string FilterFile { get; set; } + + // In the future we might support more than one of these at once... + public Lib Lib { get; set; } + + // Classes to generate code for + public Dictionary NodeToNamespace { get; set; } + + private FilterMode default_filter_mode; + public FilterMode DefaultFilterMode { get { return default_filter_mode; } set { default_filter_mode = value; } } + public Dictionary Filters { get; set; } + + // Code templates + public LibsBase LibsTemplate { get; set; } + public ClassBase ClassTemplate { get; set; } + public EnumBase EnumTemplate { get; set; } + + public static int Main (String[] args) { + var generator = new Generator (); + generator.Run (args); + return 0; + } + + void Run (String[] args) { + Lib = new Lib (); + NodeToNamespace = new Dictionary (); + + if (ParseArguments (args) != 0) { + Environment.Exit (1); + } + + Node root = LoadXml (InputFileName); + + if (FilterFile != null) + Filters = Filter.Load (XDocument.Load (FilterFile), out default_filter_mode); + + CreateTypes (root); + + CreateMembers (); + + GenerateCode (); + } + + int ParseArguments (String[] args) { + bool help = false; + + var p = new OptionSet { + { "h|?|help", "Show this help message", v => help = v != null }, + { "o=|out=", "Set the output directory", v => OutputDir = v }, + { "ns=|namespace=", "Set the namespace of the generated code", v => Lib.BaseNamespace = v }, + { "lib=", "The base name of the C++ library, i.e. 'qt' for libqt.so", v =>Lib.BaseName = v }, + { "filters=", "A file containing filter directives for filtering classes", v => FilterFile = v }, + { "inline=", "Inline methods in lib are: notpresent (default), present, surrogatelib (present in %lib%-inline)", v => Lib.InlinePolicy = (InlineMethods)Enum.Parse (typeof (InlineMethods), v, true) } + }; + + try { + args = p.Parse (args).ToArray (); + } catch (OptionException) { + Console.WriteLine ("Try `generator --help' for more information."); + return 1; + } + + if (help) { + p.WriteOptionDescriptions (Console.Error); + return 1; + } + + if (args.Length != 1) { + Console.WriteLine ("Usage: generator "); + return 1; + } + + // Code templates + LibsTemplate = new CSharpLibs (); + ClassTemplate = new CSharpClass (); + EnumTemplate = new CSharpEnum (); + + InputFileName = args [0]; + + if (Lib.BaseName == null) { + Console.WriteLine ("The --lib= option is required."); + return 1; + } + + if (Lib.BaseNamespace == null) { + Lib.BaseNamespace = Path.GetFileNameWithoutExtension (Lib.BaseName); + } + + if (OutputDir == null) + OutputDir = "output"; + + return 0; + } + + Node LoadXml (string file) { + XmlReader reader = XmlReader.Create (file, new XmlReaderSettings ()); + + Node[] parents = new Node [1024]; + + Node root = null; + + while (reader.Read()) { + if (reader.IsStartElement ()) { + string type = reader.Name; + + var attributes = new Dictionary (); + while (reader.MoveToNextAttribute ()) { + attributes [reader.Name] = reader.Value; + } + + Node n = new Node { + Id = "", + Type = type, + Attributes = attributes, + Children = new List () + }; + + if (attributes.ContainsKey ("id")) { + n.Id = attributes ["id"]; + Node.IdToNode [n.Id] = n; + } + + if (attributes.ContainsKey ("name")) + n.Name = attributes ["name"]; + + if (parents [reader.Depth - 1] != null) { + //Console.WriteLine (parents [reader.Depth - 1].type + " -> " + e.type); + parents [reader.Depth - 1].Children.Add (n); + } + parents [reader.Depth] = n; + + if (n.Type == "GCC_XML" && root == null) + root = n; + } + } + + return root; + } + + void CreateTypes (Node root) { + + foreach (Node n in root.Children) { + if (n.IsTrue ("incomplete") || !n.HasValue ("name") || n.Attributes ["name"] == "::") + continue; + + Namespace ns; + switch (n.Type) { + + case "Class": + case "Struct": + ns = new Class (n); + break; + + case "Enumeration": + ns = new Enumeration (n); + break; + + case "Namespace": + ns = new Namespace (n); + break; + + default: + continue; + + } + + NodeToNamespace [n] = ns; + Lib.Namespaces.Add (ns); + } + + for (var i = 0; i < Lib.Namespaces.Count; i++) { + Namespace ns = Lib.Namespaces [i]; + SetParentNamespace (ns); + + var filter = GetFilterOrDefault (ns); + if (filter.Mode == FilterMode.Exclude) + NodeToNamespace.Remove (ns.Node); + + if (filter.Mode != FilterMode.Include) { + Lib.Namespaces.RemoveAt (i); + i--; + continue; + } + + var klass = ns as Class; + if (klass == null) + continue; + + // Compute bases + foreach (Node bn in klass.Node.Children.Where (o => o.Type == "Base")) { + Class baseClass = NodeToNamespace [bn.NodeForAttr ("type")] as Class; + Debug.Assert (baseClass != null); + klass.BaseClasses.Add (baseClass); + } + } + } + + void SetParentNamespace (Namespace ns) + { + Namespace parent = null; + if (ns.Node.HasValue ("context") && NodeToNamespace.TryGetValue (Node.IdToNode [ns.Node.Attributes ["context"]], out parent)) + { + SetParentNamespace (parent); + ns.ParentNamespace = parent; + } + } + + void CreateMembers () { + + foreach (var ns in Lib.Namespaces) { + + var parentClass = ns.ParentNamespace as Class; + + var @enum = ns as Enumeration; + if (@enum != null) { + if (parentClass != null) + parentClass.NestedEnums.Add (@enum); + + foreach (var enumValue in @enum.Node.Children.Where (o => o.Type == "EnumValue")) { + int val; + var item = new Enumeration.Item { Name = enumValue.Attributes ["name"] }; + + if (enumValue.HasValue ("init") && int.TryParse (enumValue.Attributes ["init"], out val)) + item.Value = val; + + @enum.Items.Add (item); + } + + continue; + } + + var klass = ns as Class; + if (klass == null || !klass.Node.HasValue ("members")) + continue; + + klass.MangleType = GetType (klass.Node); + + if (parentClass != null) + parentClass.NestedClasses.Add (klass); + + int fieldCount = 0; + foreach (Node n in klass.Node ["members"].Split (new[] {' '}, StringSplitOptions.RemoveEmptyEntries).Select (id => Node.IdToNode [id])) { + bool ctor = false; + bool dtor = false; + bool skip = false; + + switch (n.Type) { + case "Field": + var fieldType = GetType (GetTypeNode (n)); + if (fieldType.ElementType == CppTypes.Unknown && fieldType.ElementTypeName == null) + fieldType = new CppType (CppTypes.Void, CppModifiers.Pointer); + + string fieldName; + if (n.Name != "") + fieldName = n.Name; + else + fieldName = "field" + fieldCount++; + + klass.Fields.Add (new Field (fieldName, fieldType, (Access)Enum.Parse (typeof (Access), n ["access"]))); + break; + + case "Constructor": + ctor = true; + break; + case "Destructor": + dtor = true; + break; + case "Method": + break; + default: + continue; + } + + if ((!dtor && n.HasValue ("overrides") && CheckPrimaryBases (klass, b => b.Node.CheckValueList ("members", n.Attributes ["overrides"]))) || // excl. virtual methods from primary base (except dtor) + (!n.IsTrue ("extern") && !n.IsTrue ("inline"))) + continue; + + if (n.IsTrue ("inline") && Lib.InlinePolicy == InlineMethods.NotPresent) + skip = true; + + string name = dtor ? "Destruct" : n.Name; + + var method = new Method (n) { + Name = name, + Access = (Access)Enum.Parse (typeof (Access), n.Attributes ["access"]), + IsVirtual = n.IsTrue ("virtual"), + IsStatic = n.IsTrue ("static"), + IsConst = n.IsTrue ("const"), + IsInline = n.IsTrue ("inline"), + IsArtificial = n.IsTrue ("artificial"), + IsConstructor = ctor, + IsDestructor = dtor + }; + + if (method.Access == Access.@private) + skip = true; + + if (dtor || method.IsArtificial) + method.GenWrapperMethod = false; + + CppType retType; + if (n.HasValue ("returns")) + retType = GetType (n.NodeForAttr ("returns")); + else + retType = CppTypes.Void; + if (retType.ElementType == CppTypes.Unknown) { + retType = CppTypes.Void; + skip = true; + } + if (CppTypeToManaged (retType) == null) { + //Console.WriteLine ("\t\tS: " + retType); + retType = CppTypes.Void; + skip = true; + } + + method.ReturnType = retType; + + int c = 0; + var argTypes = new List (); + foreach (Node arg in n.Children.Where (o => o.Type == "Argument")) { + string argname; + if (arg.Name == null || arg.Name == "") + argname = "arg" + c; + else + argname = arg.Name; + + var argtype = GetType (GetTypeNode (arg)); + if (argtype.ElementType == CppTypes.Unknown) { + //Console.WriteLine ("Skipping method " + klass.Name + "::" + member.Name + " () because it has an argument with unknown type '" + TypeNodeToString (arg) + "'."); + argtype = new CppType (CppTypes.Void, CppModifiers.Pointer); + skip = true; + } + + if (CppTypeToManaged (argtype) == null) { + //Console.WriteLine ("\t\tS: " + argtype); + argtype = new CppType (CppTypes.Void, CppModifiers.Pointer); + skip = true; + } + + method.Parameters.Add (new Parameter (argname, argtype)); + argTypes.Add (argtype); + + c++; + } + if (skip && !method.IsVirtual) + continue; + else if (skip && method.IsVirtual) + method.GenWrapperMethod = false; + + // FIXME: More complete type name check + if (ctor && argTypes.Count == 1 && argTypes [0].ElementType == CppTypes.Class && argTypes [0].ElementTypeName == klass.Name && argTypes [0].Modifiers.Count == 2 && argTypes [0].Modifiers.Contains (CppModifiers.Const) && argTypes [0].Modifiers.Contains (CppModifiers.Reference)) + method.IsCopyCtor = true; + + Console.WriteLine ("\t" + klass.Name + "." + method.Name); + + klass.Methods.Add (method); + } + + foreach (var method in klass.Methods) { + if (AddAsProperty (klass, method)) + method.GenWrapperMethod = false; + } + + Field f2 = klass.Fields.FirstOrDefault (f => f.Type.ElementType == CppTypes.Unknown); + if (f2 != null) { + Console.WriteLine ("Skipping " + klass.Name + " because field " + f2.Name + " has unknown type."); + klass.Disable = true; + } + } + } + + // + // Property support + // + bool AddAsProperty (Class klass, Method method) { + // if it's const, returns a value, has no parameters, and there is no other method with the same name + // in this class assume it's a property getter (for now?) + if (method.IsConst && !method.ReturnType.Equals (CppTypes.Void) && !method.Parameters.Any () && + klass.Methods.Count (o => o.Name == method.Name) == 1) { + Property property; + + property = klass.Properties.Where (o => o.Name == method.FormattedName).FirstOrDefault (); + if (property != null) { + property.GetMethod = method; + } else { + property = new Property (method.FormattedName, method.ReturnType) { GetMethod = method }; + klass.Properties.Add (property); + } + + return true; + } + + // if it's name starts with "set", does not return a value, and has one arg (besides this ptr) + // and there is no other method with the same name... + if (method.Name.ToLower ().StartsWith ("set") && method.ReturnType.Equals (CppTypes.Void) && + method.Parameters.Count == 1 && klass.Methods.Count (o => o.Name == method.Name) == 1) { + string getterName = method.Name.Substring (3).TrimStart ('_').ToLower (); + + string pname = method.FormattedName.Substring (3); + Property property = null; + + // ...AND there is a corresponding getter method that returns the right type, then assume it's a property setter + bool doIt = false; + property = klass.Properties.Where (o => o.Name == pname).FirstOrDefault (); + if (property != null) { + doIt = property.GetMethod != null && property.GetMethod.ReturnType.Equals (method.Parameters[0].Type); + } else { + Method getter = klass.Methods.Where (o => o.Name == getterName).FirstOrDefault (); + doIt = getter != null && getter.ReturnType.Equals (method.Parameters[0].Type); + } + if (doIt) { + if (property != null) { + property.SetMethod = method; + } else { + property = new Property (pname, method.Parameters [0].Type) { SetMethod = method }; + klass.Properties.Add (property); + } + + // set the method's arg name to "value" so that the prop setter works right + var valueParam = method.Parameters[0]; + valueParam.Name = "value"; + + return true; + } + } + + return false; + } + + // Checks klass's primary base, primary base's primary base, and so on up the hierarchy + public bool CheckPrimaryBases (Class klass, Func predicate) + { + if (klass.BaseClasses.Count == 0) + return false; + var primaryBase = klass.BaseClasses [0]; + return predicate (primaryBase) || CheckPrimaryBases (primaryBase, predicate); + } + + // Return a CppType for the type node N, return CppTypes.Unknown for unknown types + CppType GetType (Node n) { + return GetType (n, new CppType ()); + } + + CppType GetType (Node n, CppType modifiers) { + var fundamental = CppTypes.Unknown; + + switch (n.Type) { + case "Typedef": + return GetType (GetTypeNode (n), modifiers); + case "ArrayType": + CppModifiers mod = null; + if (n.Attributes ["max"] != null && n.Attributes ["min"] != null) + mod = new CppModifiers.ArrayModifier (int.Parse (n.Attributes ["max"].TrimEnd ('u')) - int.Parse (n.Attributes ["min"].TrimEnd ('u')) + 1); + else + mod = CppModifiers.Array; + return GetType (GetTypeNode (n), modifiers.Modify (mod)); + case "PointerType": + return GetType (GetTypeNode (n), modifiers.Modify (CppModifiers.Pointer)); + case "ReferenceType": + return GetType (GetTypeNode (n), modifiers.Modify (CppModifiers.Reference)); + case "FundamentalType": + return modifiers.CopyTypeFrom (new CppType (n.Name)); + case "CvQualifiedType": + if (n.IsTrue ("const")) + return GetType (GetTypeNode (n), modifiers.Modify (CppModifiers.Const)); + else + throw new NotImplementedException (); + case "Class": + fundamental = CppTypes.Class; + break; + case "Struct": + fundamental = CppTypes.Struct; + break; + case "Enumeration": + fundamental = CppTypes.Enum; + break; + default: + return CppTypes.Unknown; + } + + if (!NodeToNamespace.ContainsKey (n)) { + // FIXME: Do something better + return CppTypes.Unknown; + } + + return modifiers.CopyTypeFrom (new CppType (fundamental, string.Join ("::", NodeToNamespace [n].FullyQualifiedName))); + } + + Node GetTypeNode (Node n) { + return Node.IdToNode [n.Attributes ["type"]]; + } + + // Return the System.Type name corresponding to T, or null + // Returned as a string, because other wrappers do not have System.Types yet + public string CppTypeToManaged (CppType t) { + + Type mtype = t.ToManagedType (); + if (mtype != null && mtype != typeof (ICppObject)) { + return mtype.FullName; + } + + switch (t.ElementType) { + + case CppTypes.Class: + case CppTypes.Struct: + case CppTypes.Enum: + + var filter = GetFilterOrDefault (t); + var qname = filter.TypeName.Replace ("::", "."); + + if (filter.ImplType == ImplementationType.@struct && !IsByVal (t)) + return qname + "&"; + else + return qname; + + } + + return null; + } + + void GenerateCode () { + Directory.CreateDirectory (OutputDir); + + // Generate Libs file + using (TextWriter w = File.CreateText (Path.Combine (OutputDir, "Libs.cs"))) { + LibsTemplate.Generator = this; + LibsTemplate.Libs = new[] { Lib }; + w.Write (LibsTemplate.TransformText ()); + } + + + // Generate user types + foreach (Namespace ns in Lib.Namespaces) { + if (ns.ParentNamespace is Class) + continue; + + var klass = ns as Class; + if (klass != null) { + if (klass.Disable) + continue; + + using (TextWriter w = File.CreateText (Path.Combine (OutputDir, klass.Name + ".cs"))) { + ClassTemplate.Generator = this; + ClassTemplate.Class = klass; + ClassTemplate.Nested = false; + w.Write (ClassTemplate.TransformText ()); + } + + continue; + } + + var @enum = ns as Enumeration; + if (@enum != null) { + + using (TextWriter w = File.CreateText (Path.Combine (OutputDir, @enum.Name + ".cs"))) { + EnumTemplate.Generator = this; + EnumTemplate.Enum = @enum; + EnumTemplate.Nested = false; + w.Write (EnumTemplate.TransformText ()); + } + + continue; + } + + } + } + + static public bool IsByVal (CppType t) + { + return ((t.ElementType == CppTypes.Class || t.ElementType == CppTypes.Struct) && + !t.Modifiers.Contains (CppModifiers.Pointer) && + !t.Modifiers.Contains (CppModifiers.Reference) && + !t.Modifiers.Contains (CppModifiers.Array)); + } + + Filter GetFilterOrDefault (Namespace ns) + { + return GetFilterOrDefault (string.Join ("::", ns.FullyQualifiedName)); + } + + public Filter GetFilterOrDefault (CppType cpptype) + { + var fqn = cpptype.ElementTypeName; + if (cpptype.Namespaces != null) + fqn = string.Join ("::", cpptype.Namespaces) + "::" + fqn; + + var newtype = new CppType (fqn, cpptype.Modifiers.Where (m => m == CppModifiers.Template)); + return GetFilterOrDefault (newtype.ToString ().Replace (" ", "")); + } + + Filter GetFilterOrDefault (string fqn) + { + Filter result; + if (Filters != null && Filters.TryGetValue (fqn, out result)) + return result; + + return new Filter { TypeName = fqn, Mode = default_filter_mode }; + } +} diff --git a/src/GCCGenerator/Lib.cs b/src/GCCGenerator/Lib.cs new file mode 100644 index 00000000..579074d1 --- /dev/null +++ b/src/GCCGenerator/Lib.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +using Mono.Cxxi; + +public class Lib { + + public Lib () + { + Namespaces = new List (); + } + + public string BaseName { + get; + set; + } + + public InlineMethods InlinePolicy { + get; + set; + } + + public string BaseNamespace { + get; + set; + } + + public IList Namespaces { + get; + set; + } +} + diff --git a/src/GCCGenerator/Makefile.am b/src/GCCGenerator/Makefile.am new file mode 100644 index 00000000..c446f5d7 --- /dev/null +++ b/src/GCCGenerator/Makefile.am @@ -0,0 +1,106 @@ + +EXTRA_DIST = m4/expansions.m4 + +if ENABLE_DEBUG +ASSEMBLY_COMPILER_COMMAND = mcs +ASSEMBLY_COMPILER_FLAGS = -noconfig -codepage:utf8 -warn:4 -optimize- -debug "-define:DEBUG" +BUILD_DIR = $(top_srcdir)/bin/Debug + +ASSEMBLY = $(BUILD_DIR)/generator.exe +ASSEMBLY_MDB = $(ASSEMBLY).mdb + +MONO_CXXI_DLL_SOURCE=$(BUILD_DIR)/Mono.Cxxi.dll +MONO_CXXI_DLL=$(BUILD_DIR)/Mono.Cxxi.dll +GENERATOR_EXE_MDB_SOURCE=$(BUILD_DIR)/generator.exe.mdb +GENERATOR_EXE_MDB=$(BUILD_DIR)/generator.exe.mdb + +endif + +if ENABLE_RELEASE +ASSEMBLY_COMPILER_COMMAND = mcs +ASSEMBLY_COMPILER_FLAGS = -noconfig -codepage:utf8 -warn:4 -optimize+ +BUILD_DIR = $(top_srcdir)/bin/Release + +ASSEMBLY = $(BUILD_DIR)/generator.exe +ASSEMBLY_MDB = + +MONO_CXXI_DLL_SOURCE=$(BUILD_DIR)/Mono.Cxxi.dll +MONO_CXXI_DLL=$(BUILD_DIR)/Mono.Cxxi.dll +GENERATOR_EXE_MDB= + +endif + +COMPILE_TARGET = exe + +AL=al2 +SATELLITE_ASSEMBLY_NAME=$(notdir $(basename $(ASSEMBLY))).resources.dll + +PROGRAMFILES = \ + $(MONO_CXXI_DLL) \ + $(GENERATOR_EXE_MDB) + +BINARIES = \ + $(GENERATOR) + + +RESGEN=resgen2 + +all: $(ASSEMBLY) $(PROGRAMFILES) $(BINARIES) + +FILES = \ + Access.cs \ + Class.cs \ + Enumeration.cs \ + Field.cs \ + Filter.cs \ + Generator.cs \ + Lib.cs \ + Method.cs \ + Namespace.cs \ + Node.cs \ + Options.cs \ + Parameter.cs \ + Property.cs \ + Templates/Base.cs \ + Templates/BaseMembers.cs \ + Templates/Context.cs \ + Templates/CSharp/CSharpClass.cs \ + Templates/CSharp/CSharpEnum.cs \ + Templates/CSharp/CSharpLanguage.cs \ + Templates/CSharp/CSharpLibs.cs + +DATA_FILES = + +RESOURCES = + +EXTRAS = \ + generator.in + +REFERENCES = \ + ../../bin/Debug/Mono.Cxxi.dll \ + System \ + System.Core \ + System.Xml \ + System.Xml.Linq + +DLL_REFERENCES = + +CLEANFILES = $(PROGRAMFILES) $(BINARIES) + +include $(top_srcdir)/Makefile.include + +GENERATOR = $(BUILD_DIR)/generator + +$(eval $(call emit-deploy-target,MONO_CXXI_DLL)) +$(eval $(call emit-deploy-wrapper,GENERATOR,generator,x)) + + +$(eval $(call emit_resgen_targets)) +$(build_xamlg_list): %.xaml.g.cs: %.xaml + xamlg '$<' + +$(ASSEMBLY_MDB): $(ASSEMBLY) + +$(ASSEMBLY): $(build_sources) $(build_resources) $(build_datafiles) $(DLL_REFERENCES) $(build_xamlg_list) $(build_satellite_assembly_list) + mkdir -p $(shell dirname $(ASSEMBLY)) + $(ASSEMBLY_COMPILER_COMMAND) $(ASSEMBLY_COMPILER_FLAGS) -out:$(ASSEMBLY) -target:$(COMPILE_TARGET) $(build_sources_embed) $(build_resources_embed) $(build_references_ref) diff --git a/src/GCCGenerator/Method.cs b/src/GCCGenerator/Method.cs new file mode 100644 index 00000000..d183c775 --- /dev/null +++ b/src/GCCGenerator/Method.cs @@ -0,0 +1,105 @@ +// +// Method.cs: Represents a C++ method +// +// Author: +// Alexander Corrado (alexander.corrado@gmail.com) +// Andreia Gaita (shana@spoiledcat.net) +// Zoltan Varga +// +// Copyright (C) 2011 Novell Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; + +using Mono.Cxxi; + +public class Method { + + public Method (Node node) { + Node = node; + Parameters = new List (); + GenWrapperMethod = true; + } + + public Node Node { + get; set; + } + + public Access Access { + get; set; + } + + public string Name { + get; set; + } + + public bool IsVirtual { + get; set; + } + + public bool IsStatic { + get; set; + } + + public bool IsConst { + get; set; + } + + public bool IsInline { + get; set; + } + + public bool IsArtificial { + get; set; + } + + public bool IsConstructor { + get; set; + } + + public bool IsDestructor { + get; set; + } + + public bool IsCopyCtor { + get; set; + } + + public bool GenWrapperMethod { + get; set; + } + + public CppType ReturnType { + get; set; + } + + public List Parameters { + get; set; + } + + // The C# method name + public string FormattedName { + get { + return "" + Char.ToUpper (Name [0]) + Name.Substring (1); + } + } +} diff --git a/src/GCCGenerator/Namespace.cs b/src/GCCGenerator/Namespace.cs new file mode 100644 index 00000000..2b59f9ed --- /dev/null +++ b/src/GCCGenerator/Namespace.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; + +public class Namespace { + + public Namespace (string name) + { + this.Name = name; + } + + public Namespace (Node node) + : this (node.Name) + { + this.Node = node; + } + + public Node Node { + get; set; + } + + // Back ref to enclosing namespace (may be null) + public Namespace ParentNamespace { + get; set; + } + + public string Name { + get; set; + } + + private string [] fullyQualifiedName; + public string [] FullyQualifiedName { + get { + if (fullyQualifiedName == null) { + + if (ParentNamespace == null) { + fullyQualifiedName = new string[] { Name }; + } else { + var parentFqn = ParentNamespace.FullyQualifiedName; + fullyQualifiedName = new string[parentFqn.Length + 1]; + Array.Copy (parentFqn, fullyQualifiedName, parentFqn.Length); + fullyQualifiedName [parentFqn.Length] = Name; + } + } + return fullyQualifiedName; + } + } + +} + diff --git a/src/GCCGenerator/Node.cs b/src/GCCGenerator/Node.cs new file mode 100644 index 00000000..27eab7f1 --- /dev/null +++ b/src/GCCGenerator/Node.cs @@ -0,0 +1,95 @@ +// +// Node.cs: +// +// +// Author: +// Alexander Corrado (alexander.corrado@gmail.com) +// Andreia Gaita (shana@spoiledcat.net) +// Zoltan Varga +// +// Copyright (C) 2011 Novell Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Collections.Generic; + + +// +// This class represents an XML node read from the output of gccxml +// +public class Node { + + // The XML element type + public string Type { + get; set; + } + + // The value of the 'id' attribute + public string Id { + get; set; + } + + // The value of the 'name' attribute or null + public string Name { + get; set; + } + + // Attributes + public Dictionary Attributes { + get; set; + } + + // The children nodes of this node + public List Children { + get; set; + } + + public string this [string key] { + get { + return Attributes [key]; + } + } + + // Maps ids to nodes + public static Dictionary IdToNode = new Dictionary (); + + // For an attribute which contains an id, return the corresponding Node + public Node NodeForAttr (string attr) { + return IdToNode [Attributes [attr]]; + } + + public bool IsTrue (string key) { + return Attributes.ContainsKey (key) && Attributes[key] == "1"; + } + + public bool HasValue (string key) { + return Attributes.ContainsKey (key) && Attributes[key] != ""; + } + + public bool CheckValue (string key, string name) { + return Attributes.ContainsKey (key) && Attributes[key].Trim () == name.Trim (); + } + + public bool CheckValueList (string key, string name) { + return Attributes.ContainsKey (key) && Attributes[key].Split (' ').Contains (name.Trim ()); + } +} diff --git a/src/GCCGenerator/Options.cs b/src/GCCGenerator/Options.cs new file mode 100644 index 00000000..1fcdd863 --- /dev/null +++ b/src/GCCGenerator/Options.cs @@ -0,0 +1,1597 @@ +// +// Options.cs +// +// Authors: +// Jonathan Pryor +// +// Copyright (C) 2008 Novell (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +// Compile With: +// gmcs -debug+ -d:TEST -r:System.Core Options.cs +// gmcs -debug+ -d:LINQ -d:TEST -r:System.Core Options.cs + +// +// A Getopt::Long-inspired option parsing library for C#. +// +// NDesk.Options.OptionSet is built upon a key/value table, where the +// key is a option format string and the value is an Action +// delegate that is invoked when the format string is matched. +// +// Option format strings: +// BNF Grammar: ( name [=:]? ) ( '|' name [=:]? )+ +// +// Each '|'-delimited name is an alias for the associated action. If the +// format string ends in a '=', it has a required value. If the format +// string ends in a ':', it has an optional value. If neither '=' or ':' +// is present, no value is supported. +// +// Options are extracted either from the current option by looking for +// the option name followed by an '=' or ':', or is taken from the +// following option IFF: +// - The current option does not contain a '=' or a ':' +// - The following option is not a registered named option +// +// The `name' used in the option format string does NOT include any leading +// option indicator, such as '-', '--', or '/'. All three of these are +// permitted/required on any named option. +// +// Option bundling is permitted so long as: +// - '-' is used to start the option group +// - all of the bundled options do not require values +// - all of the bundled options are a single character +// +// This allows specifying '-a -b -c' as '-abc'. +// +// Option processing is disabled by specifying "--". All options after "--" +// are returned by OptionSet.Parse() unchanged and unprocessed. +// +// Unprocessed options are returned from OptionSet.Parse(). +// +// Examples: +// int verbose = 0; +// OptionSet p = new OptionSet () +// .Add ("v", v => ++verbose) +// .Add ("name=|value=", v => Console.WriteLine (v)); +// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); +// +// The above would parse the argument string array, and would invoke the +// lambda expression three times, setting `verbose' to 3 when complete. +// It would also print out "A" and "B" to standard output. +// The returned array would contain the string "extra". +// +// C# 3.0 collection initializers are supported: +// var p = new OptionSet () { +// { "h|?|help", v => ShowHelp () }, +// }; +// +// System.ComponentModel.TypeConverter is also supported, allowing the use of +// custom data types in the callback type; TypeConverter.ConvertFromString() +// is used to convert the value option to an instance of the specified +// type: +// +// var p = new OptionSet () { +// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, +// }; +// +// Random other tidbits: +// - Boolean options (those w/o '=' or ':' in the option format string) +// are explicitly enabled if they are followed with '+', and explicitly +// disabled if they are followed with '-': +// string a = null; +// var p = new OptionSet () { +// { "a", s => a = s }, +// }; +// p.Parse (new string[]{"-a"}); // sets v != null +// p.Parse (new string[]{"-a+"}); // sets v != null +// p.Parse (new string[]{"-a-"}); // sets v == null +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Runtime.Serialization; +using System.Text.RegularExpressions; + +#if LINQ +using System.Linq; +#endif + +#if TEST +using NDesk.Options; +#endif + +namespace NDesk.Options { + + public class OptionValueCollection : IList, IList { + + List values = new List (); + OptionContext c; + + internal OptionValueCollection (OptionContext c) + { + this.c = c; + } + + #region ICollection + void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} + bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} + object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} + #endregion + + #region ICollection + public void Add (string item) {values.Add (item);} + public void Clear () {values.Clear ();} + public bool Contains (string item) {return values.Contains (item);} + public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} + public bool Remove (string item) {return values.Remove (item);} + public int Count {get {return values.Count;}} + public bool IsReadOnly {get {return false;}} + #endregion + + #region IEnumerable + IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IEnumerable + public IEnumerator GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IList + int IList.Add (object value) {return (values as IList).Add (value);} + bool IList.Contains (object value) {return (values as IList).Contains (value);} + int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} + void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} + void IList.Remove (object value) {(values as IList).Remove (value);} + void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} + bool IList.IsFixedSize {get {return false;}} + object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} + #endregion + + #region IList + public int IndexOf (string item) {return values.IndexOf (item);} + public void Insert (int index, string item) {values.Insert (index, item);} + public void RemoveAt (int index) {values.RemoveAt (index);} + + private void AssertValid (int index) + { + if (c.Option == null) + throw new InvalidOperationException ("OptionContext.Option is null."); + if (index >= c.Option.MaxValueCount) + throw new ArgumentOutOfRangeException ("index"); + if (c.Option.OptionValueType == OptionValueType.Required && + index >= values.Count) + throw new OptionException (string.Format ( + c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), + c.OptionName); + } + + public string this [int index] { + get { + AssertValid (index); + return index >= values.Count ? null : values [index]; + } + set { + values [index] = value; + } + } + #endregion + + public List ToList () + { + return new List (values); + } + + public string[] ToArray () + { + return values.ToArray (); + } + + public override string ToString () + { + return string.Join (", ", values.ToArray ()); + } + } + + public class OptionContext { + public OptionContext (OptionSet set) + { + OptionValues = new OptionValueCollection (this); + OptionSet = set; + } + + public Option Option { get; set; } + public string OptionName { get; set; } + public int OptionIndex { get; set; } + public OptionSet OptionSet { get; private set; } + public OptionValueCollection OptionValues { get; private set; } + } + + public enum OptionValueType { + None, + Optional, + Required, + } + + public abstract class Option { + string prototype, description; + string[] names; + OptionValueType type; + int count; + string[] separators; + + protected Option (string prototype, string description) + : this (prototype, description, 1) + { + } + + protected Option (string prototype, string description, int maxValueCount) + { + if (prototype == null) + throw new ArgumentNullException ("prototype"); + if (prototype.Length == 0) + throw new ArgumentException ("Cannot be the empty string.", "prototype"); + if (maxValueCount < 0) + throw new ArgumentOutOfRangeException ("maxValueCount"); + + this.prototype = prototype; + this.names = prototype.Split ('|'); + this.description = description; + this.count = maxValueCount; + this.type = ParsePrototype (); + + if (this.count == 0 && type != OptionValueType.None) + throw new ArgumentException ( + "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + + "OptionValueType.Optional.", + "maxValueCount"); + if (this.type == OptionValueType.None && maxValueCount > 1) + throw new ArgumentException ( + string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), + "maxValueCount"); + } + + public string Prototype {get {return prototype;}} + public string Description {get {return description;}} + public OptionValueType OptionValueType {get {return type;}} + public int MaxValueCount {get {return count;}} + + public string[] GetNames () + { + return (string[]) names.Clone (); + } + + public string[] GetValueSeparators () + { + if (separators == null) + return new string [0]; + return (string[]) separators.Clone (); + } + + protected static T Parse (string value, OptionContext c) + { + TypeConverter conv = TypeDescriptor.GetConverter (typeof (T)); + T t = default (T); + try { + if (value != null) + t = (T) conv.ConvertFromString (value); + } + catch (Exception e) { + throw new OptionException ( + string.Format ( + c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), + value, typeof (T).Name, c.OptionName), + c.OptionName, e); + } + return t; + } + + internal string[] Names {get {return names;}} + internal string[] ValueSeparators {get {return separators;}} + + static readonly char[] NameTerminator = new char[]{'=', ':'}; + + private OptionValueType ParsePrototype () + { + char type = '\0'; + List seps = new List (); + for (int i = 0; i < names.Length; ++i) { + string name = names [i]; + if (name.Length == 0) + throw new ArgumentException ("Empty option names are not supported.", "prototype"); + + int end = name.IndexOfAny (NameTerminator); + if (end == -1) + continue; + names [i] = name.Substring (0, end); + if (type == '\0' || type == name [end]) + type = name [end]; + else + throw new ArgumentException ( + string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), + "prototype"); + AddSeparators (name, end, seps); + } + + if (type == '\0') + return OptionValueType.None; + + if (count <= 1 && seps.Count != 0) + throw new ArgumentException ( + string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), + "prototype"); + if (count > 1) { + if (seps.Count == 0) + this.separators = new string[]{":", "="}; + else if (seps.Count == 1 && seps [0].Length == 0) + this.separators = null; + else + this.separators = seps.ToArray (); + } + + return type == '=' ? OptionValueType.Required : OptionValueType.Optional; + } + + private void AddSeparators (string name, int end, List seps) + { + int start = -1; + for (int i = end+1; i < name.Length; ++i) { + switch (name [i]) { + case '{': + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + start = i+1; + break; + case '}': + if (start == -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + seps.Add (name.Substring (start, i-start)); + start = -1; + break; + default: + if (start == -1) + seps.Add (name [i].ToString ()); + break; + } + } + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + } + + public void Invoke (OptionContext c) + { + OnParseComplete (c); + c.OptionName = null; + c.Option = null; + c.OptionValues.Clear (); + } + + protected abstract void OnParseComplete (OptionContext c); + + public override string ToString () + { + return Prototype; + } + } + + [Serializable] + public class OptionException : Exception { + private string option; + + public OptionException (string message, string optionName) + : base (message) + { + this.option = optionName; + } + + public OptionException (string message, string optionName, Exception innerException) + : base (message, innerException) + { + this.option = optionName; + } + + protected OptionException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + this.option = info.GetString ("OptionName"); + } + + public string OptionName { + get {return this.option;} + } + + public override void GetObjectData (SerializationInfo info, StreamingContext context) + { + base.GetObjectData (info, context); + info.AddValue ("OptionName", option); + } + } + + public delegate void OptionAction (TKey key, TValue value); + + public class OptionSet : Collection