2009-09-03 21:01:11 +02:00
//
// 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] -> <param type="int" name="foo[4]" />.
// This should become <param type="int*" name="foo" count="4" />.
// 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+\s?<?<?\s?-?\w*\)?$" , RegexOptions . Compiled ) ;
public ESCLParser ( )
{
}
public string Prefix { get ; set ; }
public string Version { get ; set ; }
public IEnumerable < XElement > Parse ( string filename )
{
return Parse ( File . ReadAllLines ( filename ) ) ;
}
public IEnumerable < XElement > Parse ( string [ ] lines )
{
char [ ] splitters = new char [ ] { ' ' , '\t' , ',' , '(' , ')' , ';' , '\n' , '\r' } ;
2009-10-09 07:08:44 +02:00
bool is_long_bitfield = false ;
2009-09-03 21:01:11 +02:00
// Line splitter
Func < string , string [ ] > split = line = >
line . Split ( splitters , StringSplitOptions . RemoveEmptyEntries ) ;
// Adds new enum to the accumulator (acc)
Func < string , List < XElement > , List < XElement > > enum_name = ( line , acc ) = >
{
Func < string , string [ ] > get_tokens = ( _ ) = >
line . Trim ( "/*. " . ToCharArray ( ) ) . Split ( " _-+" . ToCharArray ( ) , StringSplitOptions . RemoveEmptyEntries ) . Select ( t = >
{
switch ( t . ToLower ( ) )
{
2009-10-09 07:08:44 +02:00
case ( "bitfield" ) : is_long_bitfield = true ; return "Flags" ;
2009-09-03 21:01:11 +02:00
default :
if ( t . ToLower ( ) = = Prefix )
return "" ;
else
return t ;
}
/* gmcs bug 336258 */ return "" ;
} ) . ToArray ( ) ;
Func < string [ ] , string > 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 < string , string > 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 < string , List < XElement > > add_enum = @enum = >
{
switch ( @enum )
{
case null :
case "" :
return acc ;
default :
2009-10-09 07:08:44 +02:00
acc . Add ( new XElement ( "enum" ,
new XAttribute ( "name" , @enum ) ,
new XAttribute ( "type" , is_long_bitfield ? "long" : "int" ) ) ) ;
2009-09-03 21:01:11 +02:00
return acc ;
}
} ;
return add_enum ( translate_name ( get_name ( get_tokens ( line ) ) ) ) ;
} ;
// Adds new token to last enum in accumulator
Func < string , List < XElement > , List < XElement > > 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 < string , List < XElement > , List < XElement > > function = ( line , acc ) = >
{
if ( ! line . EndsWith ( ";" ) )
{
function_string + = line + " " ;
return acc ;
}
line = function_string + line ;
function_string = "" ;
Func < string , string > 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 paramaters_string = Regex . Match ( line , @"\(.*\)" ) . Captures [ 0 ] . Value . TrimStart ( '(' ) . TrimEnd ( ')' ) ;
// This regex matches function parameters.
// The first part matches functions 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 (before the '|') matches parameters of the following formats:
// '[return type] [parameter name]', '[return type] * [parameter name]', 'const [return type]* [parameter name]'
// where [parameter name] can either be inside comments (/* ... */) or not.
var get_param = new Regex ( @"(\w+\s\(\*\w+\)\s*\(.*\)\s*(/\*.*?\*/|\w+)? | (const\s)?\w+\s*\**\s*(/\*.*?\*/|\w+(\[.*?\])?)),?" , RegexOptions . IgnorePatternWhitespace ) ;
var fun =
new
{
Name = funcname ,
Return = rettype ,
Version = Version ,
Extension = GetExtension ( funcname ) ,
Profile = String . Empty ,
Parameters =
from item in get_param . Matches ( paramaters_string ) . OfType < Match > ( ) . Select ( m = > m . Captures [ 0 ] . Value . TrimEnd ( ',' ) )
//paramaters_string.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
let tokens = item . Trim ( ) . Split ( ' ' )
let is_function_pointer = item . Contains ( "(*" ) // This only occurs in function pointers, e.g. void (*pfn_notify)() or void (*user_func)()
let param_name =
is_function_pointer ? tokens [ 1 ] . TrimStart ( '(' , '*' ) . Split ( ')' ) [ 0 ] :
( tokens . Last ( ) . Trim ( ) ! = "*/" ? tokens . Last ( ) : tokens [ tokens . Length - 2 ] ) . Trim ( )
let param_type =
is_function_pointer ? "IntPtr" :
( from t in tokens where t . Trim ( ) ! = "const" & & t . Trim ( ) ! = "unsigned" select t ) . First ( ) . Trim ( )
let has_array_size = array_size . IsMatch ( param_name )
let 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 )
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 ( "*" , "" ) , // Pointers are placed into the parameter Type, not Name
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)
Count = has_array_size ? Int32 . Parse ( array_size . Match ( param_name ) . Value . Trim ( '[' , ']' ) ) : 0 ,
Flow = param_name . EndsWith ( "ret" ) ? "out" : "in"
}
} ;
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 < string , bool > is_comment = line = > line . StartsWith ( "/*" ) | | line . StartsWith ( "//" ) ;
Func < string , bool > 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 < string , bool > 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 < XElement > ( ) ,
( List < XElement > 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 "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 ;
}
}
}
}
2009-07-31 17:24:08 +02:00
}