/*******************************************************************************
 * Copyright (c) 2000, 2006 QNX Software Systems 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:
 *     QNX Software Systems - Initial API and implementation
 *     Matthias Spycher (matthias@coware.com) - bug 124966
 *******************************************************************************/

package org.eclipse.cdt.debug.mi.core;

import java.util.regex.Pattern;

/**
 * GDB Type Parser.
 * The code was lifted from: The C Programming Language
 * B. W. Kernighan and D. Ritchie
 */
public class GDBTypeParser {

	// GDB type parsing from whatis command
	// declarator: type dcl
	// type: (name)+
	// dcl: ('*' | '&')* direct-decl
	// direct-dcl: '(' dcl ')'
	//             direct-dcl '(' ')'
	//             direct-dcl '[' integer ']'
	// name: ([a-zA-z][0-9])+
	// integer ([0-9)+ 

	final static int EOF = -1;
	final static int NAME = 0;
	final static int PARENS = 1;
	final static int BRACKETS = 2;

	String line;
	int index;
	int tokenType;
	String token;
	String dataType;
	String name;
	GDBDerivedType gdbDerivedType;
	GDBType genericType;

	public GDBType getGDBType() {
		if (gdbDerivedType != null) {
			return gdbDerivedType;
		}
		return genericType;
	}

	public String getVariableName() {
		return name;
	}

	public GDBType parse(String s) {
		// Sanity.
		if (s == null) {
			s = new String();
		}
		s = Pattern.compile("\\bconst\\b").matcher(s).replaceAll("");  //$NON-NLS-1$//$NON-NLS-2$
		s = Pattern.compile("\\bvolatile\\b").matcher(s).replaceAll("");  //$NON-NLS-1$//$NON-NLS-2$
		s = s.trim();

		// Initialize.
		line = s;
		index = 0;
		tokenType = -1;
		token = ""; //$NON-NLS-1$
		dataType = ""; //$NON-NLS-1$
		name = ""; //$NON-NLS-1$
		gdbDerivedType = null;
		genericType = null;

		// Fetch the datatype.
		while (getToken() == NAME) {
			dataType += " " + token; //$NON-NLS-1$
		}

		// Hack for GDB, the typename can be something like
		// class A : public B, C { ... } *
		// We are only interested in "class A"
		// Carefull for class A::data or class ns::A<ns::data>
		int column = dataType.indexOf(':');
        while (column > 0) {
            if ((column + 2) < dataType.length() && dataType.charAt(column + 1) == ':') {
                column = dataType.indexOf(':', column+2);
                continue;
            }
            dataType = dataType.substring(0, column);
            break;
        }
		genericType = new GDBType(dataType);

		// Start the recursive parser.
		dcl(tokenType);
		return getGDBType();
	}

	public static String unParse (GDBType gdbType) {

		StringBuffer sb = new StringBuffer();
		// Fetch the datatype.
		while (gdbType != null) {
			GDBDerivedType derived = null;
			int type = gdbType.getType();
			if (gdbType instanceof GDBDerivedType) {
				derived = (GDBDerivedType)gdbType;
				gdbType = derived.getChild();
				// respect the precedence of operators.
				if (type == GDBType.FUNCTION) {
					sb.append("()"); //$NON-NLS-1$
				} else if (type == GDBType.ARRAY) {
					sb.append('[').append(derived.getDimension()).append(']');
				} else if (type == GDBType.POINTER) {
					int childType = (gdbType != null) ? gdbType.getType() : GDBType.GENERIC; 
					if (childType == GDBType.POINTER || childType == GDBType.REFERENCE) {
						sb.append('*');
					} else if (childType == GDBType.GENERIC) {
						sb.insert(0, '*');
					} else {
						sb.insert(0, "(*").append(')'); //$NON-NLS-1$
					}
				} else if (type == GDBType.REFERENCE) {
					int childType = (gdbType != null) ? gdbType.getType() : GDBType.GENERIC; 
					if (childType == GDBType.POINTER || childType == GDBType.REFERENCE) {
						sb.append("&"); //$NON-NLS-1$
					} else if (childType == GDBType.GENERIC) {
						sb.insert(0, '&');
					} else {
						sb.insert(0, "(&").append(')'); //$NON-NLS-1$
					}
				}
			} else {
				sb.insert(0, ' ');
				sb.insert(0, gdbType.nameType);
				gdbType = null;
			}
		}
		return sb.toString().trim();

	}

	public class GDBType {
		public final static int GENERIC = 0;
		public final static int POINTER = 1;
		public final static int REFERENCE = 2;
		public final static int ARRAY = 3;
		public final static int FUNCTION = 4;

		String nameType;
		int type;

		public GDBType(String n) {
			this(n, 0);
		}

		public GDBType(int t) {
			this("", t); //$NON-NLS-1$
		}

		public GDBType(String n, int t) {
			nameType = n;
			type = t;
		}

		public String toString() {
			return unParse(this);
		}

		public String verbose() {
			return nameType;
		}

		public int getType() {
			return type;
		}

		public String getTypeName() {
			return nameType;
		}
	}

	public class GDBDerivedType extends GDBType {
		int dimension;
		GDBType child;

		public GDBDerivedType(GDBType c, int i) {
			this(c, i, 0);
		}

		public GDBDerivedType(GDBType c, int t, int dim) {
			super(t);
			setChild(c);
			dimension = dim;
		}

		public int getDimension() {
			return dimension;
		}

		public void setChild(GDBType c) {
			child = c;
		}

		public GDBType getChild() {
			return child;
		}

		public boolean hasChild() {
			return child != null;
		}

		public String verbose() {
			StringBuffer sb = new StringBuffer();
			switch (getType()) {
				case FUNCTION :
					sb.append(" function returning " + (hasChild() ? child.verbose() : ""));  //$NON-NLS-1$//$NON-NLS-2$
					break;
				case ARRAY :
					sb.append(" array[" + dimension + "]" + " of " + (hasChild() ? child.verbose() : ""));  //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
					break;
				case REFERENCE :
					sb.append(" reference to " + (hasChild() ? child.verbose() : ""));  //$NON-NLS-1$//$NON-NLS-2$
					break;
				case POINTER :
					sb.append(" pointer to " + (hasChild() ? child.verbose() : ""));  //$NON-NLS-1$//$NON-NLS-2$
					break;
			}
			return sb.toString();
		}
	}

	int getch() {
		if (index >= line.length() || index < 0) {
			return EOF;
		}
		return line.charAt(index++);
	}

	void ungetch() {
		if (index > 0) {
			index--;
		}
	}

	// check if the character is an alphabet
	boolean isCIdentifierStart(int c) {
		if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == ':' || c == ',') {
			return true;
		}
		return false;
	}

	// check is the character is alpha numeric
	// [a-zA-Z0-9]
	// GDB hack accept ':' ',' part of the GDB hacks
	// when doing ptype gdb returns "class A : public C { ..}"
	boolean isCIdentifierPart(int c) {
		if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == ':') {
			return true;
		}
		return false;
	}

	boolean isCSpace(int c) {
		if (c == ' ' || c == '\t' || c == '\f' || c == '\n') {
			return true;
		}
		return false;
	}

	void insertingChild(int kind) {
		insertingChild(kind, 0);
	}

	void insertingChild(int kind, int d) {
		if (gdbDerivedType == null) {
			gdbDerivedType = new GDBDerivedType(genericType, kind, d);
		} else {
			GDBDerivedType dType = gdbDerivedType;
			GDBType gdbType = gdbDerivedType.getChild();
			while (gdbType instanceof GDBDerivedType) {
				dType = (GDBDerivedType)gdbType;
				gdbType = dType.getChild();
			}				
			gdbType = new GDBDerivedType(gdbType, kind, d);
			dType.setChild(gdbType);
		}
	}

	// method returns the next token
	int getToken() {
		token = ""; //$NON-NLS-1$

		int c = getch();

		// Skip over any space
		while (isCSpace(c)) {
			c = getch();
		}

		//char character = (char) c;

		if (c == '(') {
			c = getch();
			if (c == ')') {
				token = "()"; //$NON-NLS-1$
				tokenType = PARENS;
			} else if (isCIdentifierStart(c)) {
				int i = 0;
				token += (char)c;
				while (i == 0 && c != ')') {
					if (c == EOF) {
						// Unbalanced parantheses.
						break;
					}
					c = getch();
					token += (char)c;
					if (c == '(') {
						++i;
					} else if (c == ')') {
						--i;
					}
				}
				tokenType = PARENS;
			} else {
				ungetch();
				tokenType = '(';
			}
			
			

		} else if (c == '[') {
			while ((c = getch()) != ']' && c != EOF) {
				token += (char) c;
			}
			tokenType = BRACKETS;
		} else if (isCIdentifierStart(c)) {
            StringBuffer sb = new StringBuffer();
            sb.append((char) c);
			while (isCIdentifierPart((c = getch())) && c != EOF) {
                sb.append((char) c);
			}
            if (c == '<') {
                // Swallow template args in types like "class foobar<A,B> : public C {..} *"
                // FIXME: if the bracket is not terminate do we throw exception?
                sb.append((char) c);
                int count = 1;
                do {
                    c = getch();
                    if (c == '<') {
                        count++;
                    } else if (c == '>') {
                        count--;
                    }
                    if (c != ' ') {
                    	sb.append((char)c);
                    }
                } while (count > 0 && c != EOF);
            } else if (c != EOF) {
				ungetch();
			}
            token = sb.toString();
			tokenType = NAME;
		} else if (c == '{') {
			// Swallow gdb sends things like "struct foobar {..} *"
			// FIXME: if the bracket is not terminate do we throw exception?
			int count = 1;
			do {
				c = getch();
				if (c == '{') {
					count++;
				} else if (c == '}') {
					count--;
				}
			} while (count > 0 && c != EOF);
		} else {
			tokenType = c;
		}
		return tokenType;
	}

	void dcl() {
		dcl(getToken());
	}

	// parse a declarator
	void dcl(int c) {
		int nstar = 0;
		int namp = 0;
		if (c == '*') {
			nstar++;
			for (; getToken() == '*'; nstar++) {
			}
		} else if (c == '&') {
			namp++;
			for (; getToken() == '&'; namp++) {
			}
		}
		dirdcl();
		while (nstar-- > 0) {
			insertingChild(GDBType.POINTER);
		}
		while (namp-- > 0) {
			insertingChild(GDBType.REFERENCE);
		}
	}

	// parse a direct declarator
	void dirdcl() {
		int type;

		if (tokenType == '(') {
			dcl();
			if (tokenType != ')' /*&& name.length() > 0*/) {
				// Do we throw an exception on unterminated parentheses
				// It should have been handle by getToken()
				return;
			}
		} else if (tokenType == NAME) {
			// Useless we do not need the name of the variable
			name = " " + token; //$NON-NLS-1$
		} else if (tokenType == PARENS) {
			insertingChild(GDBType.FUNCTION);
		} else if (tokenType == BRACKETS) {			
			int len = 0;
			if (token.length() > 0) {
				try {
					len = Integer.parseInt(token);
				} catch (NumberFormatException e) {
				}
			}
			insertingChild(GDBType.ARRAY, len);
		} else if (tokenType == '&') {
			insertingChild(GDBType.REFERENCE);
		} else {
			// oops bad declaration ?
			return;
		}

		while ((type = getToken()) == PARENS || type == BRACKETS) {
			if (type == PARENS) {
				insertingChild(GDBType.FUNCTION);
			} else { /* BRACKETS */
				int len = 0;
				if (token.length() > 0) {
					try {
						len = Integer.parseInt(token);
					} catch (NumberFormatException e) {
					}
				}
				insertingChild(GDBType.ARRAY, len);
			}
		}
	}

	public static void main(String[] args) {

		GDBTypeParser parser = new GDBTypeParser();

		System.out.println("int *&"); //$NON-NLS-1$
		parser.parse("int *&"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int (&rg)(int)"); //$NON-NLS-1$
		parser.parse("int (&rg)(int)"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int (&ra)[3]"); //$NON-NLS-1$
		parser.parse("int (&rg)[3]"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("struct link { int i; int j; struct link * next;} *"); //$NON-NLS-1$
		parser.parse("struct link { int i; int j; struct link * next} *"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

        System.out.println("class ns::link<8, ns::A> : public ns::B { int i; int j; struct link * next;} *"); //$NON-NLS-1$
        parser.parse("class ns::link<8, ns::A> : public ns::B { int i; int j; struct link * next;} *"); //$NON-NLS-1$
        System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
        System.out.println(parser.getGDBType().verbose());
        System.out.println();

        System.out.println("char **argv"); //$NON-NLS-1$
		parser.parse("char **argv"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int (*daytab)[13]"); //$NON-NLS-1$
		parser.parse("int (*daytab)[13]"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int *daytab[13]"); //$NON-NLS-1$
		parser.parse("int *daytab[13]"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("void *comp()"); //$NON-NLS-1$
		parser.parse("void *comp()"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("void (*comp)()"); //$NON-NLS-1$
		parser.parse("void (*comp)()"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int (*func[15])()"); //$NON-NLS-1$
		parser.parse("int (*func[15])()"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("char (*(*x())[])()"); //$NON-NLS-1$
		parser.parse("char (*(*x())[])()"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("char (*(*x[3])())[5]"); //$NON-NLS-1$
		parser.parse("char (*(*x[3])())[5]"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("char *[5]"); //$NON-NLS-1$
		parser.parse("char *[5]"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int [2][3]"); //$NON-NLS-1$
		parser.parse("int [2][3]"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int (int, char **)"); //$NON-NLS-1$
		parser.parse("int (int, char **)"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int (int)"); //$NON-NLS-1$
		parser.parse("int (int)"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int (void)"); //$NON-NLS-1$
		parser.parse("int (void)"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

		System.out.println("int ()"); //$NON-NLS-1$
		parser.parse("int ()"); //$NON-NLS-1$
		System.out.println(GDBTypeParser.unParse(parser.getGDBType()));
		System.out.println(parser.getGDBType().verbose());
		System.out.println();

	}
}
