// // Copyright (C) 2009 the Open Toolkit (http://www.opentk.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. // using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using System.Diagnostics; namespace CHeaderToXML { // Todo: Array parameters are copied as-is, e.g.: int foo[4] -> . // This should become . // Todo: Fails to parse ES extension headers, which mix enum and function definitions. // Parses ES and CL header files. sealed class ESCLParser : Parser { Regex extensions = new Regex("(ARB|EXT|AMD|NV|OES|QCOM)", RegexOptions.RightToLeft | RegexOptions.Compiled); Regex array_size = new Regex(@"\[.+\]", RegexOptions.RightToLeft | RegexOptions.Compiled); Regex EnumToken = new Regex(@"^#define \w+\s+\(?-?\w+\s? Parse(string[] lines) { char[] splitters = new char[] { ' ', '\t', ',', '(', ')', ';', '\n', '\r' }; // Line splitter Func split = line => line.Split(splitters, StringSplitOptions.RemoveEmptyEntries); // Adds new enum to the accumulator (acc) Func, List> enum_name = (line, acc) => { bool is_long_bitfield = false; Func get_tokens = (_) => line.Trim("/*. ".ToCharArray()).Split(" _-+".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).Select(t => { switch (t.ToLower()) { case ("bitfield"): is_long_bitfield = true; return "Flags"; default: if (t.ToLower() == Prefix) return ""; else return t; } /* gmcs bug 336258 */ return ""; }).ToArray(); Func get_name = tokens => { // Some comments do not indicate enums. Cull them! if (tokens[0].StartsWith("$")) return null; // Some names consist of more than one tokens. Concatenate them. return tokens.Aggregate( "", (string n, string token) => { n += String.IsNullOrEmpty(token) ? "" : Char.ToUpper(token[0]).ToString() + token.Substring(1); return n; }, n => n); }; Func translate_name = name => { if (String.IsNullOrEmpty(name)) return name; // Patch some names that are known to be problematic if (name.EndsWith("FlagsFlags")) name = name.Replace("FlagsFlags", "Flags"); switch (name) { case "OpenGLEScoreversions": case "EGLVersioning": case "OpenCLVersion": return "Version"; case "ShaderPrecision-SpecifiedTypes": return "ShaderPrecision"; case "Texturecombine+dot3": return "TextureCombine"; case "MacroNamesAndCorrespondingValuesDefinedByOpenCL": return null; default: return name; } }; Func> add_enum = @enum => { switch (@enum) { case null: case "": return acc; default: acc.Add(new XElement("enum", new XAttribute("name", @enum), new XAttribute("type", is_long_bitfield ? "long" : "int"))); return acc; } }; return add_enum(translate_name(get_name(get_tokens(line)))); }; // Adds new token to last enum in accumulator Func, List> enum_token = (line, acc) => { if (EnumToken.IsMatch(line)) { if (acc.Count == 0 || acc.Last().Name.LocalName != "enum") acc.Add(new XElement("enum", new XAttribute("name", "Unknown"))); var tokens = split(line); // Some constants are defined bitshifts, e.g. (1 << 2). If a constant contains parentheses // we assume it is a bitshift. Otherwise, we assume it is single value, separated by space // (e.g. 0xdeadbeef). if (line.Contains("(")) tokens[2] = "(" + line.Split('(')[1]; // Check whether this is an include guard (e.g. #define __OPENCL_CL_H) if (tokens[1].StartsWith("__")) return acc; // Check whether this is a known header define like WIN32_LEAN_AND_MEAN switch (tokens[1]) { case "WIN32_LEAN_AND_MEAN": case "APIENTRY": case "GLAPI": return acc; } acc[acc.Count - 1].Add(new XElement("token", new XAttribute("name", tokens[1].Substring(Prefix.Length + 1)), // remove prefix new XAttribute("value", tokens[2]))); } return acc; }; // Parses a function declaration var function_string = ""; // Used to concatenate functions that are split in different lines. (e.g. "void\nclFoo(int /* a */,\nint b);") Func, List> function = (line, acc) => { if (!line.EndsWith(";")) { function_string += line + " "; return acc; } line = function_string + line; function_string = ""; Func GetExtension = name => { var match = extensions.Match(name); return match != null && String.IsNullOrEmpty(match.Value) ? "Core" : match.Value; }; var words = line.Split(splitters, StringSplitOptions.RemoveEmptyEntries); //var words = line.Replace("/*", "").Replace("*/", "").Split(" ()".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); // ES does not start methods with 'extern', while CL does. // Remove the 'extern' keyword to create a single code-path. if (words[0] == "extern") words = words.Skip(1).ToArray(); string rettype = null; string funcname = null; GetFunctionNameAndType(words, out funcname, out rettype); var parameters_string = Regex.Match(line, @"\(.*\)").Captures[0].Value.TrimStart('(').TrimEnd(')'); var parameters = (from item in get_param.Matches(parameters_string).OfType() select item.Captures[0].Value.TrimEnd(',')).ToList(); var fun = new { Name = funcname, Return = rettype, Version = Version, Extension = GetExtension(funcname), Profile = String.Empty, Parameters = GetParameters(funcname, parameters_string) }; XElement func = new XElement("function", new XAttribute("name", fun.Name)); func.Add(new XAttribute("extension", fun.Extension)); func.Add(new XAttribute("profile", fun.Profile)); func.Add(new XAttribute("category", fun.Version)); func.Add(new XAttribute("version", fun.Version)); func.Add(new XElement("returns", new XAttribute("type", fun.Return))); foreach (var p in fun.Parameters) { var param = new XElement("param", new XAttribute("type", p.Type), new XAttribute("name", p.Name)); if (p.Count > 0) param.Add(new XAttribute("count", p.Count)); param.Add(new XAttribute("flow", p.Flow)); func.Add(param); } acc.Add(func); return acc; }; Func is_comment = line => line.StartsWith("/*") || line.StartsWith("//"); Func is_enum = line => { if (!is_comment(line)) return false; // Some enum tokens are commented out and should not be confused with new enum declarations. // Since tokens are always in ALL_CAPS, while enum names always contain at least one lower case // character, we'll try to use this information to distinguish between the two. // Warning: rather fragile. if (Regex.IsMatch(line, @"/\*\s+([A-Z]+_?[0-9]*_?)+\s+\*/")) return false; var toks = split(line); return toks.Length > 1;// && toks[1].StartsWith("GL"); }; Func is_function = line => (line.StartsWith("GL_APICALL") || line.StartsWith("GL_API") || line.StartsWith("GLAPI") || line.StartsWith("EGLAPI") || line.StartsWith("extern CL_API_ENTRY")); var signatures = lines.Aggregate( new List(), (List acc, string line) => { return is_function(line) || !String.IsNullOrEmpty(function_string) ? function(line, acc) : is_enum(line) ? enum_name(line, acc) : enum_token(line, acc); }, acc => from elem in acc where !elem.IsEmpty select elem); return signatures; } class Parameter { public string Name { get; set; } public string Type { get; set; } public int Count { get; set; } public string Flow { get; set; } } // This regex matches function parameters. // The first part matches function pointers in the following format: // '[return type] (*[function pointer name])([parameter list]) [parameter name] // where [parameter name] may or may not be in comments. // The second part (after the '|') matches parameters of the following formats: // '[parameter type] [parameter name]', '[parameter type] [pointer] [parameter name]', 'const [parameter type][pointer] [parameter name]' // where [parameter name] may be inside comments (/* ... */) and [pointer] is '', '*', '**', etc. static readonly Regex get_param = new Regex( @"(\w+\s\(\*\w+\)\s*\(.*\)\s*(/\*.*?\*/|\w+)? | (const\s*)? (\w+\s*)+ (\**\s*\**) (/\*.*?\*/|\w+(\[.*?\])?)) ,?", RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled); IEnumerable GetParameters(string funcname, string parameters_string) { var parameters = get_param.Matches(parameters_string).OfType().Select(m => m.Captures[0].Value.TrimEnd(',')); foreach (var item in parameters) { var tokens = item.Trim().Split(' '); // This only occurs in function pointers, e.g. void (*pfn_notify)() or void (*user_func)() var is_function_pointer = item.Contains("(*"); var param_name = is_function_pointer ? tokens[1].TrimStart('(', '*').Split(')')[0] : (tokens.Last().Trim() != "*/" ? tokens.Last() : tokens[tokens.Length - 2]).Trim(); var param_type = is_function_pointer ? "IntPtr" : (from t in tokens where t.Trim() != "const" && t.Trim() != "unsigned" select t).First().Trim(); var has_array_size = array_size.IsMatch(param_name); var indirection_level = is_function_pointer ? 0 : (from c in param_name where c == '*' select c).Count() + (from c in param_type where c == '*' select c).Count() + (from t in tokens where t == "***" select t).Count() * 3 + (from t in tokens where t == "**" select t).Count() * 2 + (from t in tokens where t == "*" select t).Count() + (has_array_size ? 1 : 0); // for adding indirection levels (pointers) to param_type var pointers = new string[] { "*", "*", "*", "*" }; if (tokens.Length > 1) { // Pointers are placed into the parameter Type, not Name var name = (has_array_size ? array_size.Replace(param_name, "") : param_name).Replace("*", ""); var type = is_function_pointer ? param_type : (tokens.Contains("unsigned") && !param_type.StartsWith("byte") ? "u" : "") + // Make sure we don't ignore the unsigned part of unsigned parameters (e.g. unsigned int -> uint) param_type.Replace("*", "") + String.Join("", pointers, 0, indirection_level); // Normalize pointer indirection level (place as many asterisks as in indirection_level variable) var count = has_array_size ? Int32.Parse(array_size.Match(param_name).Value.Trim('[', ']')) : 0; var flow = param_name.EndsWith("ret") || ((funcname.StartsWith("Get") || funcname.StartsWith("Gen")) && indirection_level > 0 && !(funcname.EndsWith("Info") || funcname.EndsWith("IDs") || funcname.EndsWith("ImageFormats"))) ? // OpenCL contains Get*[Info|IDs|ImageFormats] methods with 'in' pointer parameters "out" : "in"; yield return new Parameter { Name = name, Type = type, Count = count, Flow = flow }; } } } void GetFunctionNameAndType(string[] words, out string funcname, out string rettype) { funcname = null; rettype = null; bool inRettype = false; bool quit = false; for (int i = 0; !quit && i < words.Length; ++i) { switch (words [i]) { case "const": // ignore break; case "GLAPI": // ES 1.0 case "GL_API": // ES 1.1 case "GL_APICALL": // ES 2.0 case "CL_API_ENTRY": // CL 1.0 inRettype = true; break; case "APIENTRY": // ES 1.0 case "GL_APIENTRY": // ES 1.1 & 2.0 case "CL_API_CALL": // CL 1.0 inRettype = false; funcname = words [i+1].Substring(Prefix.Length); quit = true; break; default: if (inRettype) rettype += words [i]; break; } } } } }