//
// 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
{
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+$", RegexOptions.Compiled);
public ESCLParser()
{
}
public string Prefix {get; set;}
public string Version {get; set;}
public IEnumerable Parse(string filename)
{
return Parse(File.ReadAllLines(filename));
}
public IEnumerable 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) =>
{
Func get_tokens = (_) =>
line.Trim("/*. ".ToCharArray()).Split(" _-+".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).Select(t =>
{
switch (t.ToLower())
{
case ("bitfield"): 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 =>
{
// Patch some names that are known to be problematic
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)));
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);
// Check whether this is an include guard (e.g. #define __OPENCL_CL_H)
if (tokens[1].StartsWith("__"))
return acc;
acc[acc.Count - 1].Add(new XElement("token",
new XAttribute("name", tokens[1].Substring(Prefix.Length + 1)), // removes 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 ext = extensions.Match(name).Value;
return String.IsNullOrEmpty(ext) ? "Core" : ext;
};
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 fun =
new
{
Name = funcname,
Return = rettype,
Version = Version,
Extension = GetExtension(funcname),
Profile = String.Empty,
Parameters =
from item in line.Split("()".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[1].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
let tokens = item.Trim().Split(' ')
let param_name = (tokens.Last().Trim() != "*/" ? tokens.Last() : tokens[tokens.Length - 2]).Trim()
let param_type = (tokens.First().Trim() != "const" ? tokens.First().Trim() : tokens[tokens.Length - 2]).Trim()
let has_array_size = array_size.IsMatch(param_name)
let indirection_level = (from c in param_name where c == '*' select c).Count() + (from c in param_type where c == '*' select c).Count() + (has_array_size ? 1 : 0)
let pointers = new string[] { "*", "*", "*", "*" } // for adding indirection levels (pointers) to param_type
where tokens.Length > 1
select new
{
Name = (has_array_size ? array_size.Replace(param_name, "") : param_name).Replace("*", ""),
Type = param_type.Replace("*", "") + String.Join("", pointers, 0, indirection_level),
Count = has_array_size ? Int32.Parse(array_size.Match(param_name).Value.Trim('[', ']')) : 0
}
};
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));
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;
}
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 "GL_API":
inRettype = true;
break;
case "GL_APIENTRY":
inRettype = false;
funcname = words [i+1].Substring(Prefix.Length);
quit = true;
break;
default:
if (inRettype)
rettype += words [i];
break;
}
}
}
}
}