/*******************************************************************************
* Copyright (c) 2005, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Rational Software - initial implementation
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.core.dom.ast;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import org.eclipse.cdt.core.dom.ast.IBasicType.Kind;
import org.eclipse.cdt.core.dom.ast.c.ICArrayType;
import org.eclipse.cdt.core.dom.ast.c.ICQualifierType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBasicType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameterPackType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPPointerToMemberType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPReferenceType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPSpecialization;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateArgument;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateInstance;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPTemplateParameter;
import org.eclipse.cdt.core.dom.ast.gnu.cpp.IGPPQualifierType;
import org.eclipse.cdt.core.parser.Keywords;
import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.internal.core.dom.parser.ITypeContainer;
import org.eclipse.cdt.internal.core.dom.parser.Value;
import org.eclipse.cdt.internal.core.dom.parser.c.CASTTypeId;
import org.eclipse.cdt.internal.core.dom.parser.c.CVisitor;
import org.eclipse.cdt.internal.core.dom.parser.c.ICInternalBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTypeId;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPDeferredClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPUnknownClassInstance;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil;
/**
* This is a utility class to help convert AST elements to Strings corresponding to the
* AST element's type.
*
* @noextend This class is not intended to be subclassed by clients.
* @noinstantiate This class is not intended to be instantiated by clients.
*/
public class ASTTypeUtil {
private static final String COMMA_SPACE = ", "; //$NON-NLS-1$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private static final String SPACE = " "; //$NON-NLS-1$
private static final int DEAULT_ITYPE_SIZE = 2;
/**
* Returns a string representation for the parameters of the given function type. The
* representation contains the comma-separated list of the normalized parameter type
* representations wrapped in parentheses.
*/
public static String getParameterTypeString(IFunctionType type) {
StringBuilder result = new StringBuilder();
appendParameterTypeString(type, result);
return result.toString();
}
private static void appendParameterTypeString(IFunctionType ft, StringBuilder result) {
IType[] types = ft.getParameterTypes();
result.append(Keywords.cpLPAREN);
boolean needComma= false;
for (IType type : types) {
if (type != null) {
if (needComma)
result.append(COMMA_SPACE);
appendType(type, true, result);
needComma= true;
}
}
if (ft instanceof ICPPFunctionType && ((ICPPFunctionType) ft).takesVarArgs()) {
if (needComma)
result.append(COMMA_SPACE);
result.append(Keywords.cpELLIPSIS);
}
result.append(Keywords.cpRPAREN);
}
/**
* @return Whether the function matching the given function binding takes
* parameters or not.
*
* @since 5.1
*/
public static boolean functionTakesParameters(IFunction function) {
IParameter[] parameters = function.getParameters();
if (parameters.length == 0) {
return false;
}
if (parameters.length == 1 && SemanticUtil.isVoidType(parameters[0].getType())) {
return false;
}
return true;
}
/**
* Returns a string representation for the type array. The representation is
* a comma-separated list of the normalized string representations of the
* provided types.
* @see #getTypeListString(IType[], boolean)
*/
public static String getTypeListString(IType[] types) {
return getTypeListString(types, true);
}
/**
* Returns a String representation of the type array as a
* comma-separated list.
* @param types
* @return representation of the type array as a comma-separated list
* @since 5.1
*/
public static String getTypeListString(IType[] types, boolean normalize) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < types.length; i++) {
if (types[i] != null) {
appendType(types[i], normalize, result);
if (i < types.length - 1)
result.append(COMMA_SPACE);
}
}
return result.toString();
}
/**
* Returns a comma-separated list of the string representations of the arguments, enclosed
* in angle brackets.
* Optionally normalization is performed:
* <br> template parameter names are represented by their parameter position,
* <br> further normalization may be performed in future versions.
* @param normalize indicates whether normalization shall be performed
* @since 5.1
*/
public static String getArgumentListString(ICPPTemplateArgument[] args, boolean normalize) {
StringBuilder result= new StringBuilder();
appendArgumentList(args, normalize, result);
return result.toString();
}
private static void appendArgumentList(ICPPTemplateArgument[] args, boolean normalize, StringBuilder result) {
boolean first= true;
result.append('<');
for (ICPPTemplateArgument arg : args) {
if (!first) {
result.append(',');
}
first= false;
appendArgument(arg, normalize, result);
}
result.append('>');
}
/**
* Returns a string representation for an template argument. Optionally
* normalization is performed:
* <br> template parameter names are represented by their parameter position,
* <br> further normalization may be performed in future versions.
* @param normalize indicates whether normalization shall be performed
* @since 5.1
*/
public static String getArgumentString(ICPPTemplateArgument arg, boolean normalize) {
StringBuilder buf= new StringBuilder();
appendArgument(arg, normalize, buf);
return buf.toString();
}
private static void appendArgument(ICPPTemplateArgument arg, boolean normalize, StringBuilder buf) {
IValue val= arg.getNonTypeValue();
if (val != null) {
buf.append(val.getSignature());
} else {
appendType(arg.getTypeValue(), normalize, buf);
}
}
/**
* Returns an array of normalized string representations for the parameter types of the
* given function type.
* @see #getType(IType, boolean)
*/
public static String[] getParameterTypeStringArray(IFunctionType type) {
IType[] parms = type.getParameterTypes();
String[] result = new String[parms.length];
for (int i = 0; i < parms.length; i++) {
if (parms[i] != null) {
result[i] = getType(parms[i]);
}
}
return result;
}
private static void appendTypeString(IType type, boolean normalize, StringBuilder result) {
boolean needSpace = false;
if (type instanceof IArrayType) {
result.append(Keywords.cpLBRACKET);
if (type instanceof ICArrayType) {
final ICArrayType catype = (ICArrayType) type;
if (catype.isConst()) {
result.append(Keywords.CONST); needSpace = true;
}
if (catype.isRestrict()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.RESTRICT); needSpace = true;
}
if (catype.isStatic()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.STATIC); needSpace = true;
}
if (catype.isVolatile()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.VOLATILE);
}
}
IValue val= ((IArrayType) type).getSize();
if (val != null && val != Value.UNKNOWN) {
if (normalize) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(val.getSignature());
} else {
Long v= val.numericalValue();
if (v != null) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(v.longValue());
}
}
}
result.append(Keywords.cpRBRACKET);
} else if (type instanceof IBasicType) {
IBasicType basicType= (IBasicType) type;
final Kind kind = basicType.getKind();
if (basicType.isSigned()) {
// 3.9.1.2: signed integer types
if (!normalize || kind == Kind.eChar) {
result.append(Keywords.SIGNED); needSpace = true;
}
} else if (basicType.isUnsigned()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.UNSIGNED); needSpace = true;
}
if (basicType.isLong()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.LONG); needSpace = true;
} else if (basicType.isShort()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.SHORT); needSpace = true;
} else if (basicType.isLongLong()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.LONG_LONG); needSpace = true;
}
if (basicType.isComplex()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.c_COMPLEX); needSpace = true;
}
if ((basicType).isImaginary()) {
if (needSpace) {
result.append(SPACE); needSpace = false;
}
result.append(Keywords.c_IMAGINARY); needSpace = true;
}
switch (kind) {
case eChar:
if (needSpace) result.append(SPACE);
result.append(Keywords.CHAR);
break;
case eDouble:
if (needSpace) result.append(SPACE);
result.append(Keywords.DOUBLE);
break;
case eFloat:
if (needSpace) result.append(SPACE);
result.append(Keywords.FLOAT);
break;
case eInt:
if (needSpace) result.append(SPACE);
result.append(Keywords.INT);
break;
case eVoid:
if (needSpace) result.append(SPACE);
result.append(Keywords.VOID);
break;
case eBoolean:
if (needSpace) result.append(SPACE);
if (basicType instanceof ICPPBasicType) {
result.append(Keywords.BOOL);
} else {
result.append(Keywords.c_BOOL);
}
break;
case eWChar:
if (needSpace) result.append(SPACE);
result.append(Keywords.WCHAR_T);
break;
case eChar16:
if (needSpace) result.append(SPACE);
result.append(Keywords.CHAR16_T);
break;
case eChar32:
if (needSpace) result.append(SPACE);
result.append(Keywords.CHAR32_T);
break;
case eUnspecified:
break;
}
} else if (type instanceof ICPPTemplateParameter) {
appendCppName((ICPPTemplateParameter) type, normalize, normalize, result);
} else if (type instanceof ICPPBinding) {
if (type instanceof IEnumeration) {
result.append(Keywords.ENUM);
result.append(SPACE);
}
appendCppName((ICPPBinding) type, normalize, normalize, result);
} else if (type instanceof ICompositeType) {
// 101114 fix, do not display class, and for consistency don't display struct/union as well
appendNameCheckAnonymous((ICompositeType) type, result);
} else if (type instanceof ITypedef) {
result.append(((ITypedef) type).getNameCharArray());
} else if (type instanceof ICPPReferenceType) {
if (((ICPPReferenceType) type).isRValueReference()) {
result.append(Keywords.cpAND);
} else {
result.append(Keywords.cpAMPER);
}
} else if (type instanceof ICPPParameterPackType) {
result.append(Keywords.cpELLIPSIS);
} else if (type instanceof IEnumeration) {
result.append(Keywords.ENUM);
result.append(SPACE);
appendNameCheckAnonymous((IEnumeration) type, result);
} else if (type instanceof IFunctionType) {
appendParameterTypeString((IFunctionType) type, result);
needSpace = false;
if (type instanceof ICPPFunctionType) {
ICPPFunctionType ft= (ICPPFunctionType) type;
needSpace= appendCVQ(result, needSpace, ft.isConst(), ft.isVolatile(), false);
}
} else if (type instanceof IPointerType) {
if (type instanceof ICPPPointerToMemberType) {
appendTypeString(((ICPPPointerToMemberType) type).getMemberOfClass(), normalize, result);
result.append(Keywords.cpCOLONCOLON);
}
result.append(Keywords.cpSTAR); needSpace = true;
IPointerType pt= (IPointerType) type;
needSpace= appendCVQ(result, needSpace, pt.isConst(), pt.isVolatile(), pt.isRestrict());
} else if (type instanceof IQualifierType) {
if (type instanceof ICQualifierType) {
if (((ICQualifierType) type).isRestrict()) {
result.append(Keywords.RESTRICT); needSpace = true;
}
} else if (type instanceof IGPPQualifierType) {
if (((IGPPQualifierType) type).isRestrict()) {
result.append(Keywords.RESTRICT); needSpace = true;
}
}
IQualifierType qt= (IQualifierType) type;
needSpace= appendCVQ(result, needSpace, qt.isConst(), qt.isVolatile(), false);
} else if (type instanceof ISemanticProblem) {
result.append('?');
} else if (type != null) {
result.append('@').append(type.hashCode());
}
}
private static void appendTemplateParameter(ICPPTemplateParameter type, boolean normalize, StringBuilder result) {
if (normalize) {
result.append('#');
result.append(Integer.toString(type.getParameterID(), 16));
} else {
result.append(type.getName());
}
}
private static boolean appendCVQ(StringBuilder target, boolean needSpace, final boolean isConst,
final boolean isVolatile, final boolean isRestrict) {
if (isConst) {
if (needSpace) {
target.append(SPACE);
}
target.append(Keywords.CONST);
needSpace = true;
}
if (isVolatile) {
if (needSpace) {
target.append(SPACE);
}
target.append(Keywords.VOLATILE);
needSpace = true;
}
if (isRestrict) {
if (needSpace) {
target.append(SPACE);
}
target.append(Keywords.RESTRICT);
needSpace = true;
}
return needSpace;
}
/**
* Returns the normalized string representation of the type.
* @see #getType(IType, boolean)
*/
public static String getType(IType type) {
return getType(type, true);
}
/**
* Returns a string representation of a type.
* Optionally the representation is normalized:
* <br> typedefs are resolved
* <br> template parameter names are represented by their parameter position
* <br> further normalization may be performed in the future.
* @param type a type to compute the string representation for.
* @param normalize whether or not normalization should be performed.
* @return the type representation of the IType
*/
public static String getType(IType type, boolean normalize) {
StringBuilder result = new StringBuilder();
appendType(type, normalize, result);
return result.toString();
}
/**
* Appends the the result of {@link #getType(IType, boolean)} to the given buffer.
* @since 5.3
*/
public static void appendType(IType type, boolean normalize, StringBuilder result) {
IType[] types = new IType[DEAULT_ITYPE_SIZE];
// push all of the types onto the stack
int i = 0;
IQualifierType cvq= null;
ICPPReferenceType ref= null;
while (type != null && ++i < 100) {
if (type instanceof ITypedef) {
if (normalize || type instanceof ICPPSpecialization) {
// Skip the typedef and proceed with its target type.
} else {
// Output reference, qualifier and typedef, then stop.
if (ref != null) {
types = (IType[]) ArrayUtil.append(IType.class, types, ref);
ref= null;
}
if (cvq != null) {
types = (IType[]) ArrayUtil.append(IType.class, types, cvq);
cvq= null;
}
types = (IType[]) ArrayUtil.append(IType.class, types, type);
type= null;
}
} else {
if (type instanceof ICPPReferenceType) {
// reference types ignore cv-qualifiers
cvq=null;
// lvalue references win over rvalue references
if (ref == null || ref.isRValueReference()) {
// delay reference to see if there are more
ref= (ICPPReferenceType) type;
}
} else {
if (cvq != null) {
// merge cv qualifiers
if (type instanceof IQualifierType || type instanceof IPointerType) {
type= SemanticUtil.addQualifiers(type, cvq.isConst(), cvq.isVolatile(), false);
cvq= null;
}
}
if (type instanceof IQualifierType) {
// delay cv qualifier to merge it with others
cvq= (IQualifierType) type;
} else {
// no reference, no cv qualifier: output reference and cv-qualifier
if (ref != null) {
types = (IType[]) ArrayUtil.append(IType.class, types, ref);
ref= null;
}
if (cvq != null) {
types = (IType[]) ArrayUtil.append(IType.class, types, cvq);
cvq= null;
}
types = (IType[]) ArrayUtil.append(IType.class, types, type);
}
}
}
if (type instanceof ITypeContainer) {
type = ((ITypeContainer) type).getType();
} else if (type instanceof IFunctionType) {
type= ((IFunctionType) type).getReturnType();
} else {
type= null;
}
}
// pop all of the types off of the stack, and build the string representation while doing so
List<IType> postfix= null;
BitSet parenthesis= null;
boolean needParenthesis= false;
boolean needSpace= false;
for (int j = types.length - 1; j >= 0; j--) {
IType tj = types[j];
if (tj != null) {
if (j > 0 && types[j - 1] instanceof IQualifierType) {
if (needSpace)
result.append(SPACE);
appendTypeString(types[j - 1], normalize, result);
result.append(SPACE);
appendTypeString(tj, normalize, result);
needSpace= true;
--j;
} else {
// handle post-fix
if (tj instanceof IFunctionType || tj instanceof IArrayType) {
if (j == 0) {
if (needSpace)
result.append(SPACE);
appendTypeString(tj, normalize, result);
needSpace= true;
} else {
if (postfix == null) {
postfix= new ArrayList<IType>();
}
postfix.add(tj);
needParenthesis= true;
}
} else {
if (needSpace)
result.append(SPACE);
if (needParenthesis && postfix != null) {
result.append('(');
if (parenthesis == null) {
parenthesis= new BitSet();
}
parenthesis.set(postfix.size()-1);
}
appendTypeString(tj, normalize, result);
needParenthesis= false;
needSpace= true;
}
}
}
}
if (postfix != null) {
for (int j = postfix.size() - 1; j >= 0; j--) {
if (parenthesis != null && parenthesis.get(j)) {
result.append(')');
}
IType tj = postfix.get(j);
appendTypeString(tj, normalize, result);
}
}
}
/**
* For testing purposes, only.
* Returns the normalized string representation of the type defined by the given declarator.
*
* @noreference This method is not intended to be referenced by clients.
*/
public static String getType(IASTDeclarator decltor) {
// get the most nested declarator
while (decltor.getNestedDeclarator() != null)
decltor = decltor.getNestedDeclarator();
IBinding binding = decltor.getName().resolveBinding();
IType type = null;
try {
if (binding instanceof IEnumerator) {
type = ((IEnumerator)binding).getType();
} else if (binding instanceof IFunction) {
type = ((IFunction)binding).getType();
} else if (binding instanceof ITypedef) {
type = ((ITypedef)binding).getType();
} else if (binding instanceof IVariable) {
type = ((IVariable)binding).getType();
}
} catch (DOMException e) {
return EMPTY_STRING;
}
if (type != null) {
return getType(type);
}
return EMPTY_STRING;
}
/**
* For testing purposes, only.
* Return's the String representation of a node's type (if available).
*
* @noreference This method is not intended to be referenced by clients.
*/
public static String getNodeType(IASTNode node) {
if (node instanceof IASTDeclarator)
return getType((IASTDeclarator) node);
if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IVariable)
return getType(((IVariable)((IASTName) node).resolveBinding()).getType());
if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IFunction)
return getType(((IFunction)((IASTName) node).resolveBinding()).getType());
if (node instanceof IASTName && ((IASTName) node).resolveBinding() instanceof IType)
return getType((IType)((IASTName) node).resolveBinding());
if (node instanceof IASTTypeId)
return getType((IASTTypeId) node);
return EMPTY_STRING;
}
/**
* Returns the type representation of the IASTTypeId as a String.
*
* @param typeId
* @return the type representation of the IASTTypeId as a String
*/
public static String getType(IASTTypeId typeId) {
if (typeId instanceof CASTTypeId)
return createCType(typeId.getAbstractDeclarator());
else if (typeId instanceof CPPASTTypeId)
return createCPPType(typeId.getAbstractDeclarator());
return EMPTY_STRING;
}
private static String createCType(IASTDeclarator declarator) {
IType type = CVisitor.createType(declarator);
return getType(type);
}
private static String createCPPType(IASTDeclarator declarator) {
IType type = CPPVisitor.createType(declarator);
return getType(type);
}
/**
* @deprecated don't use it does something strange
*/
@Deprecated
public static boolean isConst(IType type) {
if (type instanceof IQualifierType) {
return ((IQualifierType) type).isConst();
} else if (type instanceof ITypeContainer) {
return isConst(((ITypeContainer) type).getType());
} else if (type instanceof IArrayType) {
return isConst(((IArrayType) type).getType());
} else if (type instanceof ICPPReferenceType) {
return isConst(((ICPPReferenceType) type).getType());
} else if (type instanceof IFunctionType) {
return isConst(((IFunctionType) type).getReturnType());
} else if (type instanceof IPointerType) {
return isConst(((IPointerType) type).getType());
} else if (type instanceof ITypedef) {
return isConst(((ITypedef) type).getType());
} else {
return false;
}
}
/**
* Returns the qualified name for the given binding including template arguments.
* If there are template arguments the arguments are neither normalized nor qualified.
* @since 5.3
*/
public static String getQualifiedName(ICPPBinding binding) {
StringBuilder buf= new StringBuilder();
appendCppName(binding, false, true, buf);
return buf.toString();
}
private static void appendCppName(IBinding binding, boolean normalize, boolean qualify, StringBuilder result) {
ICPPTemplateParameter tpar= getTemplateParameter(binding);
if (tpar != null) {
appendTemplateParameter(tpar, normalize, result);
} else {
if (qualify) {
IBinding owner= binding.getOwner();
if (owner instanceof ICPPNamespace || owner instanceof IType) {
int pos= result.length();
appendCppName(owner, normalize, qualify, result);
if (result.length() > pos)
result.append("::"); //$NON-NLS-1$
}
}
appendNameCheckAnonymous(binding, result);
}
if (binding instanceof ICPPTemplateInstance) {
appendArgumentList(((ICPPTemplateInstance) binding).getTemplateArguments(), normalize, result);
} else if (binding instanceof ICPPUnknownClassInstance) {
appendArgumentList(((ICPPUnknownClassInstance) binding).getArguments(), normalize, result);
}
}
private static ICPPTemplateParameter getTemplateParameter(IBinding binding) {
if (binding instanceof ICPPTemplateParameter)
return (ICPPTemplateParameter) binding;
if (binding instanceof ICPPDeferredClassInstance)
return getTemplateParameter(((ICPPDeferredClassInstance) binding).getTemplateDefinition());
return null;
}
private static void appendNameCheckAnonymous(IBinding binding, StringBuilder result) {
char[] name= binding.getNameCharArray();
if (name != null && name.length > 0) {
result.append(name);
} else if (!(binding instanceof ICPPNamespace)) {
appendNameForAnonymous(binding, result);
}
}
public static char[] createNameForAnonymous(IBinding binding) {
StringBuilder result= new StringBuilder();
appendNameForAnonymous(binding, result);
if (result.length() == 0)
return null;
return extractChars(result);
}
private static char[] extractChars(StringBuilder buf) {
final int length = buf.length();
char[] result= new char[length];
buf.getChars(0, length, result, 0);
return result;
}
private static void appendNameForAnonymous(IBinding binding, StringBuilder buf) {
IASTNode node= null;
if (binding instanceof ICInternalBinding) {
node= ((ICInternalBinding) binding).getPhysicalNode();
} else if (binding instanceof ICPPInternalBinding) {
node= ((ICPPInternalBinding) binding).getDefinition();
}
if (node != null) {
IASTFileLocation loc= node.getFileLocation();
if (loc == null) {
node= node.getParent();
if (node != null) {
loc= node.getFileLocation();
}
}
if (loc != null) {
char[] fname= loc.getFileName().toCharArray();
int fnamestart= findFileNameStart(fname);
buf.append('{');
buf.append(fname, fnamestart, fname.length-fnamestart);
buf.append(':');
buf.append(loc.getNodeOffset());
buf.append('}');
}
}
}
private static int findFileNameStart(char[] fname) {
for (int i= fname.length - 2; i >= 0; i--) {
switch (fname[i]) {
case '/':
case '\\':
return i+1;
}
}
return 0;
}
}