@ -13,363 +13,310 @@ namespace CppSharp.Passes
@@ -13,363 +13,310 @@ namespace CppSharp.Passes
{
public class GetterSetterToPropertyPass : TranslationUnitPass
{
private class PropertyGenerator
static GetterSetterToPropertyPass ( )
{
private readonly List < Method > getters = new List < Method > ( ) ;
private readonly List < Method > setters = new List < Method > ( ) ;
private readonly List < Method > setMethods = new List < Method > ( ) ;
private readonly List < Method > nonSetters = new List < Method > ( ) ;
private bool useHeuristics = true ;
LoadVerbs ( ) ;
}
public PropertyGenerator ( Class @class , bool useHeuristics )
private static void LoadVerbs ( )
{
var assembly = Assembly . GetAssembly ( typeof ( GetterSetterToPropertyPass ) ) ;
using ( var resourceStream = GetResourceStream ( assembly ) )
{
this . useHeuristics = useHeuristics ;
foreach ( var method in @class . Methods . Where (
m = > ! m . IsConstructor & & ! m . IsDestructor & & ! m . IsOperator & & m . IsGenerated & &
! m . ExcludeFromPasses . Contains ( typeof ( GetterSetterToPropertyPass ) ) ) )
DistributeMethod ( method ) ;
using ( var streamReader = new StreamReader ( resourceStream ) )
while ( ! streamReader . EndOfStream )
verbs . Add ( streamReader . ReadLine ( ) ) ;
}
}
public void GenerateProperties ( )
{
GenerateProperties ( setters , false ) ;
GenerateProperties ( setMethods , true ) ;
private static Stream GetResourceStream ( Assembly assembly )
{
var resources = assembly . GetManifestResourceNames ( ) ;
foreach ( Method getter in
from getter in getters
where getter . IsGenerated & &
getter . SynthKind ! = FunctionSynthKind . ComplementOperator & &
( ( Class ) getter . Namespace ) . Methods . All (
m = > m = = getter | | ! m . IsGenerated | | m . Name ! = getter . Name | |
m . Parameters . Count ( p = > p . Kind = = ParameterKind . Regular ) = = 0 )
select getter )
{
// Make it a read-only property
GenerateProperty ( getter . Namespace , getter ) ;
}
}
if ( resources . Count ( ) = = 0 )
throw new Exception ( "Cannot find embedded verbs data resource." ) ;
private void GenerateProperties ( IEnumerable < Method > settersToUse , bool readOnly )
// We are relying on this fact that there is only one resource embedded.
// Before we loaded the resource by name but found out that naming was
// different between different platforms and/or build systems.
return assembly . GetManifestResourceStream ( resources [ 0 ] ) ;
}
public GetterSetterToPropertyPass ( )
{
VisitOptions . VisitClassBases = false ;
VisitOptions . VisitClassFields = false ;
VisitOptions . VisitClassProperties = false ;
VisitOptions . VisitClassMethods = false ;
VisitOptions . VisitNamespaceEnums = false ;
VisitOptions . VisitNamespaceTemplates = false ;
VisitOptions . VisitNamespaceTypedefs = false ;
VisitOptions . VisitNamespaceEvents = false ;
VisitOptions . VisitNamespaceVariables = false ;
VisitOptions . VisitFunctionParameters = false ;
VisitOptions . VisitTemplateArguments = false ;
}
public override bool VisitClassDecl ( Class @class )
{
if ( ! base . VisitClassDecl ( @class ) )
return false ;
ProcessProperties ( @class , GenerateProperties ( @class ) ) ;
return false ;
}
protected virtual HashSet < Property > GenerateProperties ( Class @class )
{
var newProperties = new HashSet < Property > ( ) ;
foreach ( var method in @class . Methods . Where (
m = > ! m . IsConstructor & & ! m . IsDestructor & & ! m . IsOperator & & m . IsGenerated & &
m . SynthKind ! = FunctionSynthKind . DefaultValueOverload & &
m . SynthKind ! = FunctionSynthKind . ComplementOperator & &
! m . ExcludeFromPasses . Contains ( typeof ( GetterSetterToPropertyPass ) ) ) )
{
foreach ( var setter in settersToUse )
if ( IsGetter ( method ) )
{
var type = ( Class ) setter . Namespace ;
var firstWord = GetFirstWord ( setter . Name ) ;
string property ;
if ( ( firstWord = = "set" | | firstWord = = "set_" ) & &
firstWord . Length < setter . Name . Length )
property = setter . Name . Substring ( firstWord . Length ) ;
else
property = setter . Name ;
var nameBuilder = new StringBuilder ( property ) ;
if ( char . IsLower ( setter . Name [ 0 ] ) )
nameBuilder [ 0 ] = char . ToLowerInvariant ( nameBuilder [ 0 ] ) ;
string afterSet = nameBuilder . ToString ( ) ;
var s = setter ;
foreach ( var getter in nonSetters . Where ( m = > m . Namespace = = type & &
m . ExplicitInterfaceImpl = = s . ExplicitInterfaceImpl ) )
{
var name = GetReadWritePropertyName ( getter , afterSet ) ;
if ( name = = afterSet & &
GetUnderlyingType ( getter . OriginalReturnType ) . Equals (
GetUnderlyingType ( setter . Parameters [ 0 ] . QualifiedType ) ) )
{
Method g = getter ;
foreach ( var method in type . Methods . Where ( m = > m ! = g & & m . Name = = name ) )
{
var oldName = method . Name ;
method . Name = string . Format ( "get{0}{1}" ,
char . ToUpperInvariant ( method . Name [ 0 ] ) , method . Name . Substring ( 1 ) ) ;
Diagnostics . Debug ( "Method {0}::{1} renamed to {2}" , method . Namespace . Name , oldName , method . Name ) ;
}
foreach ( var @event in type . Events . Where ( e = > e . Name = = name ) )
{
var oldName = @event . Name ;
@event . Name = string . Format ( "on{0}{1}" ,
char . ToUpperInvariant ( @event . Name [ 0 ] ) , @event . Name . Substring ( 1 ) ) ;
Diagnostics . Debug ( "Event {0}::{1} renamed to {2}" , @event . Namespace . Name , oldName , @event . Name ) ;
}
GenerateProperty ( name , getter . Namespace , getter , readOnly ? null : setter ) ;
goto next ;
}
}
Property baseProperty = type . GetBaseProperty ( new Property { Name = afterSet } , getTopmost : true ) ;
if ( ! type . IsInterface & & baseProperty ! = null & & baseProperty . IsVirtual & & setter . IsVirtual )
{
bool isReadOnly = baseProperty . SetMethod = = null ;
var name = GetReadWritePropertyName ( baseProperty . GetMethod , afterSet ) ;
GenerateProperty ( name , setter . Namespace , baseProperty . GetMethod ,
readOnly | | isReadOnly ? null : setter ) ;
}
next :
;
string name = GetPropertyName ( method . Name ) ;
QualifiedType type = method . OriginalReturnType ;
Property property = GetProperty ( method , name , type ) ;
property . GetMethod = method ;
property . QualifiedType = method . OriginalReturnType ;
newProperties . Add ( property ) ;
continue ;
}
foreach ( Method nonSetter in nonSetters )
if ( IsSetter ( method ) )
{
Class type = ( Class ) nonSetter . Namespace ;
string name = GetPropertyName ( nonSetter . Name ) ;
Property baseProperty = type . GetBaseProperty ( new Property { Name = name } , getTopmost : true ) ;
if ( ! type . IsInterface & & baseProperty ! = null & & baseProperty . IsVirtual )
{
bool isReadOnly = baseProperty . SetMethod = = null ;
if ( readOnly = = isReadOnly )
{
GenerateProperty ( nonSetter . Namespace , nonSetter ,
readOnly ? null : baseProperty . SetMethod ) ;
}
}
string name = GetPropertyNameFromSetter ( method . Name ) ;
QualifiedType type = method . Parameters . First ( p = > p . Kind = = ParameterKind . Regular ) . QualifiedType ;
Property property = GetProperty ( method , name , type ) ;
property . SetMethod = method ;
newProperties . Add ( property ) ;
}
}
private static string GetReadWritePropertyName ( INamedDecl getter , string afterSet )
{
string name = GetPropertyName ( getter . Name ) ;
if ( name ! = afterSet & & name . StartsWith ( "is" , StringComparison . Ordinal ) & &
name ! = "is" )
{
name = char . ToLowerInvariant ( name [ 2 ] ) + name . Substring ( 3 ) ;
}
return name ;
}
return newProperties ;
}
private static Type GetUnderlyingType ( QualifiedType type )
{
TagType tagType = type . Type as TagType ;
if ( tagType ! = null )
return type . Type ;
// TODO: we should normally check pointer types for const;
// however, there's some bug, probably in the parser, that returns IsConst = false for "const Type& arg"
// so skip the check for the time being
PointerType pointerType = type . Type as PointerType ;
return pointerType ! = null ? pointerType . Pointee : type . Type ;
}
private static Property GetProperty ( Method method , string name , QualifiedType type )
{
Type underlyingType = GetUnderlyingType ( type ) ;
Class @class = ( Class ) method . Namespace ;
Property property = @class . Properties . Find (
p = > p . Field = = null & &
( p . Name = = name | |
( p . GetMethod ! = null & & GetReadWritePropertyName ( p . GetMethod , name ) = = name ) ) & &
( ( p . GetMethod ! = null & &
GetUnderlyingType ( p . GetMethod . OriginalReturnType ) . Equals ( underlyingType ) ) | |
( p . SetMethod ! = null & &
GetUnderlyingType ( p . SetMethod . Parameters [ 0 ] . QualifiedType ) . Equals ( underlyingType ) ) ) ) ? ?
new Property { Name = name , QualifiedType = type } ;
private static void GenerateProperty ( DeclarationContext context , Method getter , Method setter = null )
if ( property . Namespace = = null )
{
GenerateProperty ( GetPropertyName ( getter . Name ) , context , getter , setter ) ;
property . Namespace = method . Namespace ;
property . Access = method . Access ;
@class . Properties . Add ( property ) ;
}
private static void GenerateProperty ( string name , DeclarationContext context , Method getter , Method setter )
else
{
var type = ( Class ) context ;
if ( type . Properties . Any ( p = > p . Name = = name & &
p . ExplicitInterfaceImpl = = getter . ExplicitInterfaceImpl ) )
return ;
var property = new Property
{
Access = getter . Access = = AccessSpecifier . Public | |
( setter ! = null & & setter . Access = = AccessSpecifier . Public ) ?
AccessSpecifier . Public : AccessSpecifier . Protected ,
Name = name ,
Namespace = type ,
QualifiedType = getter . OriginalReturnType ,
OriginalNamespace = getter . OriginalNamespace
} ;
if ( getter . IsOverride | | ( setter ! = null & & setter . IsOverride ) )
{
var baseVirtualProperty = type . GetBaseProperty ( property , getTopmost : true ) ;
if ( baseVirtualProperty ! = null & & ! baseVirtualProperty . IsVirtual )
{
// the only way the above can happen is if we are generating properties in abstract implementations
// in which case we can have less naming conflicts since the abstract base can also contain non-virtual properties
if ( getter . SynthKind = = FunctionSynthKind . AbstractImplCall )
return ;
throw new Exception ( string . Format (
"Base of property {0} is not virtual while the getter is." ,
getter . QualifiedOriginalName ) ) ;
}
if ( baseVirtualProperty = = null | | baseVirtualProperty . SetMethod = = null )
setter = null ;
}
property . GetMethod = getter ;
property . SetMethod = setter ;
property . ExplicitInterfaceImpl = getter . ExplicitInterfaceImpl ;
if ( property . ExplicitInterfaceImpl = = null & & setter ! = null )
{
property . ExplicitInterfaceImpl = setter . ExplicitInterfaceImpl ;
}
if ( getter . Comment ! = null )
{
property . Comment = CombineComments ( getter , setter ) ;
}
type . Properties . Add ( property ) ;
getter . GenerationKind = GenerationKind . Internal ;
if ( setter ! = null )
setter . GenerationKind = GenerationKind . Internal ;
property . Access = ( AccessSpecifier ) Math . Max (
( int ) ( property . GetMethod ? ? property . SetMethod ) . Access ,
( int ) method . Access ) ;
}
private static RawComment CombineComments ( Declaration getter , Declaration setter )
property . Name = property . OriginalName = name ;
method . GenerationKind = GenerationKind . Internal ;
if ( method . ExplicitInterfaceImpl ! = null )
property . ExplicitInterfaceImpl = method . ExplicitInterfaceImpl ;
return property ;
}
private static void ProcessProperties ( Class @class , HashSet < Property > newProperties )
{
foreach ( var property in newProperties )
{
var comment = new RawComment
if ( property . IsOverride )
{
Kind = getter . Comment . Kind ,
BriefText = getter . Comment . BriefText ,
Text = getter . Comment . Text
} ;
if ( getter . Comment . FullComment ! = null )
{
comment . FullComment = new FullComment ( ) ;
comment . FullComment . Blocks . AddRange ( getter . Comment . FullComment . Blocks ) ;
if ( getter ! = setter & & setter ! = null & & setter . Comment ! = null )
Property baseProperty = @class . GetBaseProperty (
new Property { Name = property . Name } , getTopmost : true ) ;
if ( baseProperty = = null & & property . SetMethod ! = null )
{
comment . BriefText + = Environment . NewLine + setter . Comment . BriefText ;
comment . Text + = Environment . NewLine + setter . Comment . Text ;
comment . FullComment . Blocks . AddRange ( setter . Comment . FullComment . Blocks ) ;
property . SetMethod . GenerationKind = GenerationKind . Generate ;
property . SetMethod = null ;
}
else if ( property . GetMethod = = null & & baseProperty . SetMethod ! = null )
property . GetMethod = baseProperty . GetMethod ;
else if ( property . SetMethod = = null )
property . SetMethod = baseProperty . SetMethod ;
}
return comment ;
}
private static string GetPropertyName ( string name )
{
var firstWord = GetFirstWord ( name ) ;
if ( Match ( firstWord , new [ ] { "get" } ) & & name ! = firstWord & &
! char . IsNumber ( name [ 3 ] ) )
if ( property . GetMethod = = null )
{
if ( char . IsLower ( name [ 0 ] ) )
{
if ( name . Length = = 4 )
{
return char . ToLowerInvariant (
name [ 3 ] ) . ToString ( CultureInfo . InvariantCulture ) ;
}
return char . ToLowerInvariant (
name [ 3 ] ) . ToString ( CultureInfo . InvariantCulture ) +
name . Substring ( 4 ) ;
}
return name . Substring ( 3 ) ;
if ( property . SetMethod ! = null )
property . SetMethod . GenerationKind = GenerationKind . Generate ;
@class . Properties . Remove ( property ) ;
continue ;
}
return name ;
}
private static string GetPropertyNameFromSetter ( Method setter )
{
var name = setter . Name . Substring ( "set" . Length ) ;
if ( string . IsNullOrEmpty ( name ) )
return name ;
if ( char . IsLower ( setter . Name [ 0 ] ) & & ! char . IsLower ( name [ 0 ] ) )
return char . ToLowerInvariant ( name [ 0 ] ) + name . Substring ( 1 ) ;
return name ;
}
private void DistributeMethod ( Method method )
{
Type returnType = method . OriginalReturnType . Type . Desugar ( ) ;
if ( ( returnType . IsPrimitiveType ( PrimitiveType . Void ) | |
returnType . IsPrimitiveType ( PrimitiveType . Bool ) ) & &
method . Parameters . Any ( p = > p . Kind = = ParameterKind . Regular ) )
foreach ( var method in @class . Methods . Where ( m = > m . IsGenerated & & m . Name = = property . Name ) )
{
if ( method . Parameters . Count = = 1 )
setters . Add ( method ) ;
else if ( method . Parameters . Count > 1 )
setMethods . Add ( method ) ;
var oldName = method . Name ;
method . Name = string . Format ( "get{0}{1}" ,
char . ToUpperInvariant ( method . Name [ 0 ] ) , method . Name . Substring ( 1 ) ) ;
Diagnostics . Debug ( "Method {0}::{1} renamed to {2}" , method . Namespace . Name , oldName , method . Name ) ;
}
els e
foreach ( var @event in @class . Events . Where ( e = > e . Name = = property . Name ) )
{
if ( method . ConvertToProperty | | IsGetter ( method ) )
getters . Add ( method ) ;
if ( method . Parameters . All ( p = > p . Kind = = ParameterKind . IndirectReturnType ) )
nonSetters . Add ( method ) ;
var oldName = @event . Name ;
@event . Name = string . Format ( "on{0}{1}" ,
char . ToUpperInvariant ( @event . Name [ 0 ] ) , @event . Name . Substring ( 1 ) ) ;
Diagnostics . Debug ( "Event {0}::{1} renamed to {2}" , @event . Namespace . Name , oldName , @event . Name ) ;
}
CombineComments ( property ) ;
}
}
private bool IsGetter ( Method method )
private static string GetReadWritePropertyName ( INamedDecl getter , string afterSet )
{
string name = GetPropertyName ( getter . Name ) ;
if ( name ! = afterSet & & name . StartsWith ( "is" , StringComparison . Ordinal ) & &
name ! = "is" )
{
if ( method . IsDestructor | |
( method . OriginalReturnType . Type . IsPrimitiveType ( PrimitiveType . Void ) ) | |
method . Parameters . Any ( p = > p . Kind ! = ParameterKind . IndirectReturnType ) )
return false ;
var firstWord = GetFirstWord ( method . Name ) ;
if ( firstWord . Length < method . Name . Length & & Match ( firstWord , new [ ] { "get" , "is" , "has" } ) )
return true ;
name = char . ToLowerInvariant ( name [ 2 ] ) + name . Substring ( 3 ) ;
}
return name ;
}
if ( useHeuristics & & ! Match ( firstWord , new [ ] { "to" , "new" } ) & & ! verbs . Contains ( firstWord ) )
return true ;
private static Type GetUnderlyingType ( QualifiedType type )
{
TagType tagType = type . Type as TagType ;
if ( tagType ! = null )
return type . Type ;
// TODO: we should normally check pointer types for const;
// however, there's some bug, probably in the parser, that returns IsConst = false for "const Type& arg"
// so skip the check for the time being
PointerType pointerType = type . Type as PointerType ;
return pointerType ! = null ? pointerType . Pointee : type . Type ;
}
return false ;
}
private static void CombineComments ( Property property )
{
Method getter = property . GetMethod ;
if ( getter . Comment = = null )
return ;
private static bool Match ( string prefix , IEnumerable < string > prefixes )
var comment = new RawComment
{
return prefixes . Any ( p = > prefix = = p | | prefix = = p + '_' ) ;
Kind = getter . Comment . Kind ,
BriefText = getter . Comment . BriefText ,
Text = getter . Comment . Text
} ;
if ( getter . Comment . FullComment ! = null )
{
comment . FullComment = new FullComment ( ) ;
comment . FullComment . Blocks . AddRange ( getter . Comment . FullComment . Blocks ) ;
Method setter = property . SetMethod ;
if ( getter ! = setter & & setter ? . Comment ! = null )
{
comment . BriefText + = Environment . NewLine + setter . Comment . BriefText ;
comment . Text + = Environment . NewLine + setter . Comment . Text ;
comment . FullComment . Blocks . AddRange ( setter . Comment . FullComment . Blocks ) ;
}
}
property . Comment = comment ;
}
private static string GetFirstWord ( string name )
private static string GetPropertyName ( string name )
{
var firstWord = GetFirstWord ( name ) ;
if ( Match ( firstWord , new [ ] { "get" } ) & & name ! = firstWord & &
! char . IsNumber ( name [ 3 ] ) )
{
var firstWord = new List < char > { char . ToLowerInvariant ( name [ 0 ] ) } ;
for ( int i = 1 ; i < name . Length ; i + + )
if ( char . IsLower ( name [ 0 ] ) )
{
var c = name [ i ] ;
if ( char . IsLower ( c ) )
if ( name . Length = = 4 )
{
firstWord . Add ( c ) ;
continue ;
}
if ( c = = '_' )
{
firstWord . Add ( c ) ;
break ;
return char . ToLowerInvariant (
name [ 3 ] ) . ToString ( CultureInfo . InvariantCulture ) ;
}
if ( char . IsUpper ( c ) )
break ;
return char . ToLowerInvariant (
name [ 3 ] ) . ToString ( CultureInfo . InvariantCulture ) +
name . Substring ( 4 ) ;
}
return new string ( firstWord . ToArray ( ) ) ;
return name . Substring ( 3 ) ;
}
return name ;
}
private static readonly HashSet < string > verbs = new HashSet < string > ( ) ;
static GetterSetterToPropertyPass ( )
private static string GetPropertyNameFromSetter ( string name )
{
LoadVerbs ( ) ;
}
var nameBuilder = new StringBuilder ( name ) ;
string firstWord = GetFirstWord ( name ) ;
if ( firstWord = = "set" | | firstWord = = "set_" )
nameBuilder . Remove ( 0 , firstWord . Length ) ;
if ( nameBuilder . Length = = 0 )
return nameBuilder . ToString ( ) ;
private static void LoadVerbs ( )
{
var assembly = Assembly . GetAssembly ( typeof ( GetterSetterToPropertyPass ) ) ;
using ( var resourceStream = GetResourceStream ( assembly ) )
{
using ( var streamReader = new StreamReader ( resourceStream ) )
while ( ! streamReader . EndOfStream )
verbs . Add ( streamReader . ReadLine ( ) ) ;
}
nameBuilder . TrimUnderscores ( ) ;
if ( char . IsLower ( name [ 0 ] ) & & ! char . IsLower ( nameBuilder [ 0 ] ) )
nameBuilder [ 0 ] = char . ToLowerInvariant ( nameBuilder [ 0 ] ) ;
return nameBuilder . ToString ( ) ;
}
private static Stream GetResourceStream ( Assembly assembly )
private bool IsGetter ( Method method )
{
var resources = assembly . GetManifestResourceNames ( ) ;
if ( method . IsDestructor | |
method . OriginalReturnType . Type . IsPrimitiveType ( PrimitiveType . Void ) | |
method . Parameters . Any ( p = > p . Kind ! = ParameterKind . IndirectReturnType ) )
return false ;
var firstWord = GetFirstWord ( method . Name ) ;
if ( resources . Count ( ) = = 0 )
throw new Exception ( "Cannot find embedded verbs data resource." ) ;
if ( firstWord . Length < method . Name . Length & &
Match ( firstWord , new [ ] { "get" , "is" , "has" } ) )
return true ;
// We are relying on this fact that there is only one resource embedded.
// Before we loaded the resource by name but found out that naming was
// different between different platforms and/or build systems.
return assembly . GetManifestResourceStream ( resources [ 0 ] ) ;
if ( Options . UsePropertyDetectionHeuristics & &
! Match ( firstWord , new [ ] { "to" , "new" } ) & & ! verbs . Contains ( firstWord ) )
return true ;
return false ;
}
public GetterSetterToPropertyPass ( )
private static bool IsSetter ( Method method )
{
VisitOptions . VisitClassBases = false ;
VisitOptions . VisitClassFields = false ;
VisitOptions . VisitClassProperties = false ;
VisitOptions . VisitClassMethods = false ;
VisitOptions . VisitNamespaceEnums = false ;
VisitOptions . VisitNamespaceTemplates = false ;
VisitOptions . VisitNamespaceTypedefs = false ;
VisitOptions . VisitNamespaceEvents = false ;
VisitOptions . VisitNamespaceVariables = false ;
VisitOptions . VisitFunctionParameters = false ;
VisitOptions . VisitTemplateArguments = false ;
Type returnType = method . OriginalReturnType . Type . Desugar ( ) ;
return ( returnType . IsPrimitiveType ( PrimitiveType . Void ) | |
returnType . IsPrimitiveType ( PrimitiveType . Bool ) ) & &
method . Parameters . Count ( p = > p . Kind = = ParameterKind . Regular ) = = 1 ;
}
public override bool VisitClassDecl ( Class @clas s )
private static bool Match ( string prefix , IEnumerable < string > prefixes )
{
if ( base . VisitClassDecl ( @class ) )
new PropertyGenerator ( @class , Options . UsePropertyDetectionHeuristics ) . GenerateProperties ( ) ;
return false ;
return prefixes . Any ( p = > prefix = = p | | prefix = = p + '_' ) ;
}
private static string GetFirstWord ( string name )
{
var firstWord = new List < char > { char . ToLowerInvariant ( name [ 0 ] ) } ;
for ( int i = 1 ; i < name . Length ; i + + )
{
var c = name [ i ] ;
if ( char . IsLower ( c ) )
{
firstWord . Add ( c ) ;
continue ;
}
if ( c = = '_' )
{
firstWord . Add ( c ) ;
break ;
}
if ( char . IsUpper ( c ) )
break ;
}
return new string ( firstWord . ToArray ( ) ) ;
}
private static readonly HashSet < string > verbs = new HashSet < string > ( ) ;
}
}