/*******************************************************************************
 * Copyright (c) 2000, 2008 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
 *******************************************************************************/
package org.eclipse.cdt.utils.debug.stabs;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.utils.coff.PE;
import org.eclipse.cdt.utils.coff.Coff.SectionHeader;
import org.eclipse.cdt.utils.coff.PE.Attribute;
import org.eclipse.cdt.utils.debug.DebugArrayType;
import org.eclipse.cdt.utils.debug.DebugBaseType;
import org.eclipse.cdt.utils.debug.DebugCrossRefType;
import org.eclipse.cdt.utils.debug.DebugEnumField;
import org.eclipse.cdt.utils.debug.DebugEnumType;
import org.eclipse.cdt.utils.debug.DebugField;
import org.eclipse.cdt.utils.debug.DebugFunctionType;
import org.eclipse.cdt.utils.debug.DebugParameterKind;
import org.eclipse.cdt.utils.debug.DebugPointerType;
import org.eclipse.cdt.utils.debug.DebugReferenceType;
import org.eclipse.cdt.utils.debug.DebugStructType;
import org.eclipse.cdt.utils.debug.DebugType;
import org.eclipse.cdt.utils.debug.DebugUnknownType;
import org.eclipse.cdt.utils.debug.DebugVariableKind;
import org.eclipse.cdt.utils.debug.IDebugEntryRequestor;
import org.eclipse.cdt.utils.debug.tools.DebugSym;
import org.eclipse.cdt.utils.debug.tools.DebugSymsRequestor;
import org.eclipse.cdt.utils.elf.Elf;
import org.eclipse.cdt.utils.elf.Elf.Section;

public class Stabs {

	final static String LLLOW = "01000000000000000000000"; //$NON-NLS-1$
	final static String LLHIGH = "0777777777777777777777"; //$NON-NLS-1$
	final static String ULLHIGH = "01777777777777777777777"; //$NON-NLS-1$

	byte[] stabData;
	byte[] stabstrData;
	boolean isLe;

	boolean inCompilationUnit;
	boolean inFunction;
	boolean inInclude;

	int bracket;
	String currentFile;

	Map<TypeNumber, DebugType> mapTypes = new HashMap<TypeNumber, DebugType>();
	DebugType voidType = new DebugBaseType("void", 0, false); //$NON-NLS-1$

	public Stabs(String file) throws IOException {

		// we support Elf and PE executable formats.  try Elf
		// and then PE.
		try {
			Elf exe = new Elf(file);
			init(exe);
			exe.dispose();
		} catch (IOException e) {
			PE exe = new PE(file);
			init(exe);
			exe.dispose();
		}
	}

	public Stabs(Elf exe) throws IOException {
		init(exe);
	}

	public Stabs(byte[] stab, byte[] stabstr, boolean le) {
		init(stab, stabstr, le);
	}

	void init(Elf exe) throws IOException {
		byte[] data = null;
		byte[] stabstr = null;
		Elf.Section[] sections = exe.getSections();
		for (Section section : sections) {
			String name = section.toString();
			if (name.equals(".stab")) { //$NON-NLS-1$
				data = section.loadSectionData();
			} else if (name.equals(".stabstr")) { //$NON-NLS-1$
				stabstr = section.loadSectionData();
			}
		}
		Elf.ELFhdr header = exe.getELFhdr();
		boolean isLE = header.e_ident[Elf.ELFhdr.EI_DATA] == Elf.ELFhdr.ELFDATA2LSB;
		if (data != null && stabstr != null) {
			init(data, stabstr, isLE);
		}
	}

	void init(PE exe) throws IOException {
		byte[] data = null;
		byte[] stabstr = null;

		SectionHeader[] sections = exe.getSectionHeaders();
		for (SectionHeader section : sections) {
			String name = new String(section.s_name).trim();
			if (name.equals(".stab")) { //$NON-NLS-1$
				data = section.getRawData();
			} else if (name.equals(".stabstr")) { //$NON-NLS-1$
				stabstr = section.getRawData();
			}
		}
		
		Attribute att = exe.getAttribute();
		if (data != null && stabstr != null) {
			init(data, stabstr, att.isLittleEndian());
		}
	}

	void init(byte[] stab, byte[] stabstr, boolean le) {
		stabData = stab;
		stabstrData = stabstr;
		isLe = le;
	}

	String makeString(long offset) {
		StringBuffer buf = new StringBuffer();
		for (; offset < stabstrData.length; offset++) {
			byte b = stabstrData[(int) offset];
			if (b == 0) {
				break;
			}
			buf.append((char) b);
		}
		return buf.toString();
	}

	int read_4_bytes(byte[] bytes, int offset) {
		if (isLe) {
			return (((bytes[offset + 3] & 0xff) << 24)
				| ((bytes[offset + 2] & 0xff) << 16)
				| ((bytes[offset + 1] & 0xff) << 8)
				| (bytes[offset] & 0xff));
		}
		return (((bytes[offset] & 0xff) << 24)
			| ((bytes[offset + 1] & 0xff) << 16)
			| ((bytes[offset + 2] & 0xff) << 8)
			| (bytes[offset + 3] & 0xff));
	}

	short read_2_bytes(byte[] bytes, int offset) {
		if (isLe) {
			return (short) (((bytes[offset + 1] & 0xff) << 8) | (bytes[offset] & 0xff));
		}
		return (short) (((bytes[offset] & 0xff) << 8) | (bytes[offset + 1] & 0xff));
	}

	public void parse(IDebugEntryRequestor requestor) {
		//List list = new ArrayList();
		long nstab = stabData.length / StabConstant.SIZE;
		int i, offset;
		String holder = null;
		long stroff = 0;
		int type = 0;
		int other = 0;
		short desc = 0;
		long value = 0;
		for (bracket = i = offset = 0; i < nstab; i++, offset += StabConstant.SIZE) {

			// get the offset for the string; 4 bytes
			stroff = read_4_bytes(stabData, offset);
			// get the type; 1 byte;
			type = 0xff & stabData[offset + 4];
			// get the other
			other = 0xff & stabData[offset + 5];
			// get the desc
			desc = read_2_bytes(stabData, offset + 6);
			// get the value
			value = read_4_bytes(stabData, offset + 8);

			String field;
			if (stroff > 0) {
				field = makeString(stroff);
			} else {
				field = new String();
			}
			// Check for continuation and if any go to the next stab
			// until we find a string that is not terminated with a
			// continuation line '\\'
			// According to the spec all the other fields are duplicated so we
			// still have the data.
			// From the spec continuation line on AIX is '?'
			if (field.endsWith("\\") || field.endsWith("?")) { //$NON-NLS-1$ //$NON-NLS-2$
				field = field.substring(0, field.length() - 1);
				if (holder == null) {
					holder = field;
				} else {
					holder += field;
				}
				continue;
			} else if (holder != null) {
				field = holder + field;
				holder = null;
			}
			parseStabEntry(requestor, field, type, other, desc, value);
		}
		// Bring closure.
		if (inFunction) {
			requestor.exitFunction(-1);
			inFunction = false;
		}
		if (inInclude) {
			requestor.exitInclude();
			inInclude = false;
		}
		if (inCompilationUnit) {
			requestor.exitCompilationUnit(value);
			inCompilationUnit = false;
			currentFile = null;
		}
	}

	void parseStabEntry(IDebugEntryRequestor requestor, String field, int type, int other, short desc, long value) {
		// Parse the string
		switch (type) {
			case StabConstant.N_GSYM :
			case StabConstant.N_LSYM :
			case StabConstant.N_PSYM :
				//accept a new variable
				parseStabString(requestor, field, value);
				break;

			case StabConstant.N_SLINE :
				// New statement line
				requestor.acceptStatement(desc, value);
				break;

			case StabConstant.N_FUN :
				if (inFunction) {
					requestor.exitFunction(value);
					inFunction = false;
				}
				// Start a new Function
				if (field.length() == 0) {
					field = " anon "; //$NON-NLS-1$
				}
				inFunction = true;
				parseStabString(requestor, field, value);
				break;

			case StabConstant.N_LBRAC :
				if (inFunction) {
					requestor.enterCodeBlock(value);
				}
				bracket++;
				break;

			case StabConstant.N_RBRAC :
				requestor.exitCodeBlock(value);
				bracket--;
				break;

			case StabConstant.N_BINCL :
				// Start of an include file
				requestor.enterInclude(field);
				inInclude = true;
				break;

			case StabConstant.N_EINCL :
				// end of the include
				requestor.exitInclude();
				inInclude = false;
				break;

			case StabConstant.N_SOL :
				// if we had an include it means the end.
				if (inInclude) {
					requestor.exitInclude();
					inInclude = false;
				}
				// Start of an include file
				requestor.enterInclude(field);
				inInclude = true;
				break;

			case StabConstant.N_CATCH :
				parseStabString(requestor, field, value);
				break;

			case StabConstant.N_SO :
				// if whitin a function
				if (inFunction) {
					requestor.exitFunction(-1);
					inFunction = false;
				}
				if (inInclude) {
					requestor.exitInclude();
					inInclude = false;
				}
				if (inCompilationUnit) {
					requestor.exitCompilationUnit(value);
					inCompilationUnit = false;
					currentFile = null;
				}
				if (field != null && field.length() > 0) {
					// if it ends with "/" do not call the entering yet
					// we have to concatenate the next one.
					if (field.endsWith("/")) { //$NON-NLS-1$
						currentFile = field;
					} else {
						if (currentFile != null) {
							currentFile += field;
						} else {
							currentFile = field;
						}
						requestor.enterCompilationUnit(currentFile, value);
						inCompilationUnit = true;
						currentFile = null;
					}
				}
				break;
		}
		//System.out.println(" " + i + "\t" + Stab.type2String(type) + "\t" +
		// other + "\t\t" +
		//	desc + "\t" + Long.toHexString(value) + "\t" + + stroff + "\t\t"
		// +name);
	}

	void parseStabString(IDebugEntryRequestor requestor, String field, long value) {

		StringField sf = new StringField(field);

		switch (sf.getSymbolDescriptor()) {
			// C++ nested symbol.
			case ':' :
				break;

				// Parameter pass by reference in register.
			case 'a' :
				{
					String information = sf.getTypeInformation();
					String paramName = sf.getName();
					DebugParameterKind paramKind = DebugParameterKind.REGISTER_REFERENCE;
					DebugType paramType = parseStabType("", information); //$NON-NLS-1$
					requestor.acceptParameter(paramName, paramType, paramKind, value);
				}
				break;

				// Sun Based variable
			case 'b' :
				break;

				// symbol descriptor indicates that this stab represents a
				// constant.
			case 'c' :
				{
					String name = sf.getName();
					String information = sf.getTypeInformation();
					parseStabConstant(requestor, name, information, value);
				}
				break;

				//  Conformant array bound(Pascal).
				// Nave of a caught exception GNU C++
			case 'C' :
				{
					String excName = sf.getName();
					String information = sf.getTypeInformation();
					DebugType excType = parseStabType("", information); //$NON-NLS-1$
					requestor.acceptCaughtException(excName, excType, value);
				}
				break;

				// File scope function.
			case 'f' :
				// Global function.
			case 'F' :
				{
					String funcName = sf.getName();
					String funcInfo = sf.getTypeInformation();
					DebugType funcType = parseStabType("", funcInfo); //$NON-NLS-1$
					boolean funcGlobal = sf.getSymbolDescriptor() == 'F';
					requestor.enterFunction(funcName, funcType, funcGlobal, value);
				}
				break;
				// Global variable
			case 'G' :
				{
					String varName = sf.getName();
					String varInfo = sf.getTypeInformation();
					DebugVariableKind varKind = DebugVariableKind.GLOBAL;
					DebugType varType = parseStabType("", varInfo); //$NON-NLS-1$
					requestor.acceptVariable(varName, varType, varKind, value);
				}
				break;

				// ???
			case 'i' :
				break;
				// Internal(nested) procedure.
			case 'I' :
				break;
				// Internal/nested function.
			case 'J' :
				break;
				// Label name
			case 'L' :
				break;
				// Module
			case 'm' :
				break;

				// Argument list parameter
			case 'p' :
				{
					String paramName = sf.getName();
					String paramInfo = sf.getTypeInformation();
					DebugParameterKind paramKind = DebugParameterKind.STACK;
					DebugType paramType = parseStabType("", paramInfo); //$NON-NLS-1$
					requestor.acceptParameter(paramName, paramType, paramKind, value);
				}
				break;

				// Paramater in floating point register.
			case 'D' :
				// register parameter or prototype f function referenced by the
				// file.
			case 'P' :
				// Register Parameter
			case 'R' :
				{
					String paramName = sf.getName();
					String paramInfo = sf.getTypeInformation();
					DebugParameterKind paramKind = DebugParameterKind.REGISTER;
					DebugType paramType = parseStabType("", paramInfo); //$NON-NLS-1$
					requestor.acceptParameter(paramName, paramType, paramKind, value);
				}
				break;

				// static procedure
			case 'Q' :
				break;

				// Floating point register variable
			case 'd' :
				// Never use, according to the
				// Register variable
			case 'r' :
				{
					String varName = sf.getName();
					String varInfo = sf.getTypeInformation();
					DebugVariableKind varKind = DebugVariableKind.REGISTER;
					DebugType varType = parseStabType("", varInfo); //$NON-NLS-1$
					requestor.acceptVariable(varName, varType, varKind, value);
				}
				break;

				// File scope variable
			case 'S' :
				{
					String varName = sf.getName();
					String varInfo = sf.getTypeInformation();
					DebugVariableKind varKind = DebugVariableKind.STATIC;
					DebugType varType = parseStabType("", varInfo); //$NON-NLS-1$
					requestor.acceptVariable(varName, varType, varKind, value);
				}
				break;

				// Type name
			case 't' :
				{
					String name = sf.getName();
					String infoField = sf.getTypeInformation();
					DebugType type = parseStabType(name, infoField);
					requestor.acceptTypeDef(name, type);
				}
				break;

				// Enumeration, structure or union
			case 'T' :
				{
					String infoField = sf.getTypeInformation();
					// According to the doc 't' can follow the 'T'.  If so just
					// strip the T and go again.
					if (infoField.length() > 0 && infoField.charAt(0) == 't') {
						String s = field.replaceFirst(":T", ":"); //$NON-NLS-1$ //$NON-NLS-2$
						parseStabString(requestor, s, value);
					} else {
						// Just register the type.
						String name = sf.getName();
						parseStabType(name, infoField);
					}
				}
				break;

				// Parameter passed by reference.
			case 'v' :
				{
					String paramName = sf.getName();
					String paramInfo = sf.getTypeInformation();
					DebugParameterKind paramKind = DebugParameterKind.REFERENCE;
					DebugType paramType = parseStabType("", paramInfo); //$NON-NLS-1$
					requestor.acceptParameter(paramName, paramType, paramKind, value);
				}
				break;

				// Procedure scope static variable
			case 'V' :
				{
					String varName = sf.getName();
					String varInfo = sf.getTypeInformation();
					DebugVariableKind varKind = DebugVariableKind.LOCAL_STATIC;
					DebugType varType = parseStabType("", varInfo); //$NON-NLS-1$
					requestor.acceptVariable(varName, varType, varKind, value);
				}
				break;

				// Conformant array
			case 'x' :
				break;

				// Function return variable
			case 'X' :
				// local variable
			case 's' :
				// Variable on the stack
			case '-' :
			default :
				{
					String varName = sf.getName();
					String varInfo = sf.getTypeInformation();
					DebugVariableKind varKind = DebugVariableKind.LOCAL;
					DebugType varType = parseStabType("", varInfo); //$NON-NLS-1$
					requestor.acceptVariable(varName, varType, varKind, value);
				}
				break;
		}
	}

	DebugType parseStabType(String name, String typeInformation) {
		try {
			Reader reader = new StringReader(typeInformation);
			return parseStabType(name, reader);
		} catch (IOException e) {
		}
		return new DebugUnknownType(name);
	}

	DebugType parseStabType(String name, Reader reader) throws IOException {
		return parseStabType(name, null, reader);
	}

	DebugType parseStabType(String name, TypeInformation oldType, Reader reader) throws IOException {

		TypeInformation typeInfo = new TypeInformation(reader);
		DebugType type = null;
		switch (typeInfo.getTypeDescriptor()) {

			// Method (C++)
			case '#' :
				break;

				// Reference (C++)
			case '&' :
				{
					DebugType subType = parseStabType("", reader); //$NON-NLS-1$
					type = new DebugReferenceType(subType);
				}
				break;

				// Member (C++) class and variable
			case '@' :
				break;

				// pointer type.
			case '*' :
				{
					DebugType subType = parseStabType("", reader); //$NON-NLS-1$
					type = new DebugPointerType(subType);
				}
				break;

				// Builtin type define byt Sun stabs
				// Pascal Type
			case 'b' :
				// Builtin floating type
			case 'R' :
				// Wide character
			case 'w' :
				// Builtin flaoting point type.
			case 'g' :
				// Complex buitin type.
			case 'c' :
				{
					char desc = typeInfo.getTypeDescriptor();
					type = parseStabBuiltinType(name, desc, reader);
				}
				break;

				// Array.
			case 'a' :
			case 'A' :
				type = parseStabArrayType(name, reader);
				break;

				// Volatile-qualified type
			case 'B' :
				break;

				// Cobol
			case 'C' :
				break;

				// File type
			case 'd' :
				break;

				// N-dimensional dynamic array.
			case 'D' :
				break;

				// Enumeration type
			case 'e' :
				type = parseStabEnumType(name, reader);
				break;

				// N-dimensional subarray
			case 'E' :
				break;

				// Function type
			case 'f' :
				{
					DebugType subType = parseStabType("", reader); //$NON-NLS-1$
					type = new DebugFunctionType(subType);
				}
				break;

				// Pascal Function parameter
			case 'F' :
				break;

				// COBOL Group
			case 'G' :
				break;

				// Imported type
			case 'i' :
				break;

				// Const-qualified type
			case 'k' :
				break;

				// Cobol file desc.
			case 'K' :
				break;

				// Multiple instance type
			case 'M' :
				break;

				// string type:
			case 'n' :
				break;

				// Stringpt:
			case 'N' :
				break;

				// Opaque type
			case 'o' :
				break;

				// Procedure
			case 'p' :
				break;

				// Packed array
			case 'P' :
				break;

				// Range. example:
			case 'r' :
				type = parseStabRangeType(name, typeInfo.getTypeNumber(), reader);
				break;

				// Structure type
			case 's' :
				type = parseStabStructType(name, typeInfo.getTypeNumber(), false, reader);
				break;

				// Union
			case 'u' :
				type = parseStabStructType(name, typeInfo.getTypeNumber(), true, reader);
				break;

				// Set type
			case 'S' :
				break;

				// variant record.
			case 'v' :
				break;

				// Cross-reference
			case 'x' :
				type = parseStabCrossRefType(name, reader);
				break;

				// ???
			case 'Y' :
				break;

				// gstring
			case 'z' :
				break;

				// Reference to a previously define type.
			case '(' :
			case '-' :
			default :
				if (typeInfo.isTypeDefinition()) {
					type = parseStabType(name, typeInfo, reader);
				} else {
					// check for void
					if (oldType != null && oldType.getTypeNumber().equals(typeInfo.getTypeNumber())) {
						type = voidType;
					} else {
						type = getDebugType(typeInfo.getTypeNumber());
					}
				}
		}

		// register the type.
		if (type != null && typeInfo.isTypeDefinition()) {
			mapTypes.put(typeInfo.getTypeNumber(), type);
		}
		if (type == null) {
			type = new DebugUnknownType(name);
		}
		return type;
	}

	/**
	 * @param name
	 * @param reader
	 * @return
	 */
	DebugType parseStabCrossRefType(String name, Reader reader) throws IOException {
		StringBuffer sb = new StringBuffer();
		int c = reader.read();
		if (c == 's') {
			sb.append("struct "); //$NON-NLS-1$
		} else if (c == 'u') {
			sb.append("union "); //$NON-NLS-1$
		} else if (c == 'e') {
			sb.append("enum "); //$NON-NLS-1$
		} else {
			sb.append((char) c);
		}
		while ((c = reader.read()) != -1) {
			if (c == ':') {
				break;
			}
			sb.append((char) c);
		}
		return new DebugCrossRefType(null, name, sb.toString());
	}

	/**
	 * @param name
	 * @param desc
	 * @param reader
	 * @return
	 */
	private DebugType parseStabBuiltinType(String name, char desc, Reader reader) throws IOException {
		DebugType builtinType = null;
		switch (desc) {
			case 'b' :
				{
					// get the signed
					int signed = reader.read();
					reader.mark(1);
					// get the flag
					int charFlag = reader.read();
					if (charFlag != 'c') {
						reader.reset();
					}
					int c;
					StringBuffer sb = new StringBuffer();

					// get the width
					//int width = 0;
					while ((c = reader.read()) != -1) {
						if (c == ';') {
							break;
						}
						sb.append((char) c);
					}
					//try {
					//	String token = sb.toString();
					//	width = Integer.parseInt(token);
					//} catch (NumberFormatException e) {
					//}

					sb.setLength(0);

					// get the offset
					//int offset = 0;
					while ((c = reader.read()) != -1) {
						if (c == ';') {
							break;
						}
						sb.append((char) c);
					}
					//try {
						//String token = sb.toString();
						//offset = Integer.parseInt(token);
					//} catch (NumberFormatException e) {
					//}

					sb.setLength(0);

					// get the nbits
					int nbits = 0;
					while ((c = reader.read()) != -1) {
						if (c == ';') {
							break;
						}
						sb.append((char) c);
					}
					try {
						String token = sb.toString();
						nbits = Integer.parseInt(token);
					} catch (NumberFormatException e) {
					}
					builtinType = new DebugBaseType(name, nbits / 8, signed == 'u');
				}
				break;

			case 'w' :
				{
					builtinType = new DebugBaseType(name, 8, false);
				}
				break;

			case 'R' :
				{
					int c;
					StringBuffer sb = new StringBuffer();

					// get the fp-Type
					//int fpType = 0;
					while ((c = reader.read()) != -1) {
						if (c == ';') {
							break;
						}
						sb.append((char) c);
					}
					//try {
					//	String token = sb.toString();
						//fpType = Integer.parseInt(token);
					//} catch (NumberFormatException e) {
					//}

					sb.setLength(0);

					// get the bytes
					int bytes = 0;
					while ((c = reader.read()) != -1) {
						if (c == ';') {
							break;
						}
						sb.append((char) c);
					}
					try {
						String token = sb.toString();
						bytes = Integer.parseInt(token);
					} catch (NumberFormatException e) {
					}
					builtinType = new DebugBaseType(name, bytes, false);
				}
				break;

			case 'c' :
			case 'g' :
				{
					//DebugType type = parseStabType(name, reader);
					parseStabType(name, reader);
					int c = reader.read(); // semicolon
					StringBuffer sb = new StringBuffer();
					int nbits = 0;
					while ((c = reader.read()) != -1) {
						sb.append((char) c);
					}
					try {
						String token = sb.toString();
						nbits = Integer.parseInt(token);
					} catch (NumberFormatException e) {
					}
					builtinType = new DebugBaseType(name, nbits / 8, false);
				}
				break;

		}
		return builtinType;
	}

	DebugType parseStabArrayType(String name, Reader reader) throws IOException {
		// Format of an array type:
		// "ar<index type>;lower;upper;<array_contents_type>".

		// we only understand range for an array.
		int c = reader.read();
		if (c == 'r') {
			//DebugType index_type = parseStabType("", reader); //$NON-NLS-1$
			parseStabType("", reader); //$NON-NLS-1$

			c = reader.read();
			// Check ';'
			if (c != ';') {
				// bad array type
				return null;
			}

			StringBuffer sb = new StringBuffer();
			while ((c = reader.read()) != -1) {
				if (c == ';') {
					break;
				}
				sb.append((char) c);
			}
			// Check ';'
			if (c != ';') {
				// bad array type
				return null;
			}

			// lower index
			int lower = 0; // should always be zero for C/C++
			try {
				String token = sb.toString();
				lower = Integer.parseInt(token);
			} catch (NumberFormatException e) {
			}

			sb.setLength(0);

			while ((c = reader.read()) != -1) {
				if (c == ';') {
					break;
				}
				sb.append((char) c);
			}
			// Check ';'
			if (c != ';') {
				// bad array type
				return null;
			}

			int upper = 0;
			// upper index
			try {
				String token = sb.toString();
				upper = Integer.parseInt(token);
			} catch (NumberFormatException e) {
			}

			// Check ';'
			if (c != ';') {
				// bad array type
				return null;
			}

			// The array_content_type
			DebugType subType = parseStabType("", reader); //$NON-NLS-1$

			return new DebugArrayType(subType, upper - lower + 1);
		}
		return new DebugArrayType(new DebugUnknownType(name), 0);
	}

	/**
	 * _Bool:t(0,20)=eFalse:0,True:1,; fruit:T(1,4)=eapple:0,orange:1,;
	 * 
	 * @param name
	 * @param attributes
	 * @return
	 */
	DebugType parseStabEnumType(String name, Reader reader) throws IOException {
		List<DebugEnumField> list = new ArrayList<DebugEnumField>();
		String fieldName = null;
		StringBuffer sb = new StringBuffer();
		int c;
		while ((c = reader.read()) != -1) {
			if (c == ':') {
				fieldName = sb.toString();
				sb.setLength(0);
			} else if (c == ',') {
				if (fieldName != null && fieldName.length() > 0) {
					String value = sb.toString();
					int fieldValue = 0;
					try {
						fieldValue = Integer.decode(value).intValue();
					} catch (NumberFormatException e) {
					}
					list.add(new DebugEnumField(fieldName, fieldValue));
				}
				fieldName = null;
				sb.setLength(0);
			} else if (c == ';') {
				break;
			} else {
				sb.append((char) c);
			}
		}
		DebugEnumField[] fields = new DebugEnumField[list.size()];
		list.toArray(fields);
		return new DebugEnumType(name, fields);
	}

	/**
	 * For C lang -- node:T(1,5)=s12i:(0,1),0,32;j:(0,1),32,32;next:(1,6)=*(1,5),64,32;;
	 * 
	 * @param name
	 * @param typeNumber
	 * @param union
	 * @param reader
	 * @return
	 */
	DebugType parseStabStructType(String name, TypeNumber typeNumber, boolean union, Reader reader) throws IOException {
		int c;
		StringBuffer sb = new StringBuffer();
		while ((c = reader.read()) != -1) {
			if (!Character.isDigit((char) c)) {
				reader.reset();
				break;
			}
			reader.mark(1);
			sb.append((char) c);
		}
		String number = sb.toString();
		int size = 0;
		try {
			size = Integer.decode(number).intValue();
		} catch (NumberFormatException e) {
		}
		DebugStructType structType = new DebugStructType(name, size, union);
		// We have to register it right away, some field may
		// need the tag if they self reference via a pointer.
		mapTypes.put(typeNumber, structType);

		// parse the fields.
		parseStabStructField(structType, reader);
		return structType;
	}

	void parseStabStructField(DebugStructType structType, Reader reader) throws IOException {
		// get the field name.
		StringBuffer sb = new StringBuffer();
		int c;
		while ((c = reader.read()) != -1) {
			if (c != ':') {
				sb.append((char) c);
			} else {
				break;
			}
		}
		// Sanity check: We should have ':' if no bailout
		if (c != ':') {
			return;
		}
		String name = sb.toString();

		// get the type of the field
		DebugType fieldType = parseStabType("", reader); //$NON-NLS-1$

		c = reader.read();
		// Sanity check: we should have ',' here.
		if (c != ',') {
			return;
		}

		// the offset of the struct of the field.
		sb.setLength(0);
		while ((c = reader.read()) != -1 && c != ',') {
			sb.append((char) c);
		}
		// Sanity check: we should have ','
		if (c != ',') {
			return;
		}
		int offset = 0;
		try {
			offset = Integer.decode(sb.toString()).intValue();
		} catch (NumberFormatException e) {
		}

		// the number of bits of the struct of the field.
		sb.setLength(0);
		while ((c = reader.read()) != -1 && c != ';') {
			sb.append((char) c);
		}
		// Check we need ';'
		if (c != ';') {
			return;
		}
		int bits = 0;
		try {
			bits = Integer.decode(sb.toString()).intValue();
		} catch (NumberFormatException e) {
		}

		// add the new field.
		structType.addField(new DebugField(name, fieldType, offset, bits));

		// absorb the trailing ';'
		//reader.read();
		// continue the parsing recursively.
		parseStabStructField(structType, reader);
	}

	DebugType parseStabRangeType(String name, TypeNumber number, Reader reader) throws IOException {
		//  int:t(0,1)=r(0,1);-2147483648;2147483647;
		//              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

		DebugType rangeType = null;

		// get the index_type
		TypeNumber typeNumber = new TypeNumber(reader);

		int c = reader.read();
		// Sanity we should have a semicolon
		if (c != ';') {
			// bad range type;
			return new DebugUnknownType(name);
		}

		StringBuffer sb = new StringBuffer();

		// read the lowerBound.
		while ((c = reader.read()) != -1) {
			if (c == ';') {
				break;
			}
			sb.append((char) c);
		}
		// Sanity we should have a semicolon
		if (c != ';') {
			// bad range type;
			return new DebugUnknownType(name);
		}

		boolean overflowLowerBound = false;
		String lowerBoundString = sb.toString();
		long lowerBound = 0;
		try {
			lowerBound = Long.decode(lowerBoundString).longValue();
		} catch (NumberFormatException e) {
			overflowLowerBound = true;
		}

		sb.setLength(0);

		// read the upperBound.
		while ((c = reader.read()) != -1) {
			if (c == ';') {
				break;
			}
			sb.append((char) c);
		}
		// Sanity we should have a semicolon
		if (c != ';') {
			// bad range type;
			return new DebugUnknownType(name);
		}

		boolean overflowUpperBound = false;
		long upperBound = 0;
		String upperBoundString = sb.toString();
		try {
			upperBound = Long.decode(upperBoundString).longValue();
		} catch (NumberFormatException e) {
			overflowUpperBound = true;
		}

		boolean self = typeNumber.equals(number);

		// Probably trying 64 bits range like "long long"
		if (overflowLowerBound || overflowUpperBound) {

			if (lowerBoundString.equals(LLLOW) && upperBoundString.equals(LLHIGH))
				rangeType = new DebugBaseType(name, 8, false);
			if (!overflowLowerBound && lowerBound == 0 && upperBoundString.equals(ULLHIGH))
				rangeType = new DebugBaseType(name, 8, true);
		} else {

			if (lowerBound == 0 && upperBound == -1) {
				// if the lower bound is 0 and the upper bound is -1,
				// it means unsigned int
				if (name.equals("long long int")) { //$NON-NLS-1$
					rangeType = new DebugBaseType(name, 8, true);
				} else if (name.equals("long long unsigned int")) { //$NON-NLS-1$
					rangeType = new DebugBaseType(name, 8, true);
				} else {
					rangeType = new DebugBaseType(name, 4, true);
				}
			} else if (upperBound == 0 && lowerBound > 0) {
				// if The upper bound is 0 and the lower bound is positive
				// it is a floating point and the lower bound is the number of bytes
				rangeType = new DebugBaseType(name, (int) lowerBound, true);
			} else if (lowerBound == -128 && upperBound == 127) {
				// signed char;
				rangeType = new DebugBaseType(name, 1, false);
			} else if (self && lowerBound == 0 && upperBound == 127) {
				// C/C++ specific
				rangeType = new DebugBaseType(name, 1, false);
			} else if (self && lowerBound == 0 && upperBound == 255) {
				// unsigned char
				rangeType = new DebugBaseType(name, 1, true);
			} else if (lowerBound == -32768 && upperBound == 32767) {
				// signed short
				rangeType = new DebugBaseType(name, 2, false);
			} else if (self && lowerBound == 0 && upperBound == 65535) {
				// unsigned short
				rangeType = new DebugBaseType(name, 2, true);
			} else if (lowerBound == -2147483648 && upperBound == 2147483647) {
				// int
				rangeType = new DebugBaseType(name, 4, false);
			}
		}
		return rangeType;
	}

	void parseStabConstant(IDebugEntryRequestor requestor, String name, String field, long value) {
		try {
			parseStabConstant(requestor, name, new StringReader(field), value);
		} catch (IOException e) {
			//
		}
	}

	void parseStabConstant(IDebugEntryRequestor requestor, String name, Reader reader, long value) throws IOException {
		int c = reader.read();
		if (c == '=') {
			c = reader.read();
			switch (c) {
				// Boolean constant.
				// c=bvalue or c=bvalue
				// value is a numeric value: 0 fo false and 1 for true.
				// Not supported by GDB.
				case 'b' :
					break;

					// Character constant.
					// c=cvalue
					// value is the numeric value of the constant.
					// Not supported by GDB.
				case 'c' :
					break;

					// Constant whose value van be represented as integral.
					// c=e type-information, value
					// type-information is the type of the constant.
					// value is the numeric value of the constant.
					// This is usually use for enumeration constants.
				case 'e' :
					{
						int val = 0;
						DebugType type = parseStabType("", reader); //$NON-NLS-1$
						c = reader.read();
						if (c == ',') {
							StringBuffer sb = new StringBuffer();
							while ((c = reader.read()) != -1) {
								sb.append((char) c);
							}
							try {
								String s = sb.toString();
								val = Integer.decode(s).intValue();
							} catch (NumberFormatException e) {
							}
						}
						requestor.acceptTypeConst(name, type, val);
					}
					break;

					// Integer constant.
					// c=ivalue
					// value is the numeric value;
				case 'i' :
					{
						int val = 0;
						StringBuffer sb = new StringBuffer();
						while ((c = reader.read()) != -1) {
							sb.append((char) c);
						}
						try {
							String s = sb.toString();
							val = Integer.decode(s).intValue();
						} catch (NumberFormatException e) {
						} catch (IndexOutOfBoundsException e) {
						}
						requestor.acceptIntegerConst(name, val);
					}
					break;

					// Real constant.
					// c=rvalue
					// value is the real value, which can be INF or QNAN or SNAN
					// preceded
					// by a sign.
				case 'r' :
					{
						double val = 0;
						StringBuffer sb = new StringBuffer();
						while ((c = reader.read()) != -1) {
							sb.append((char) c);
						}
						try {
							String s = sb.toString();
							if (s.equals("-INF")) { //$NON-NLS-1$
								val = Double.NEGATIVE_INFINITY;
							} else if (s.equals("INF")) { //$NON-NLS-1$
								val = Double.POSITIVE_INFINITY;
							} else if (s.equals("QNAN")) { //$NON-NLS-1$
								val = Double.NaN;
							} else if (s.equals("SNAN")) { //$NON-NLS-1$
								val = Double.NaN;
							} else {
								val = Double.parseDouble(s);
							}
						} catch (NumberFormatException e) {
						} catch (IndexOutOfBoundsException e) {
						}
						requestor.acceptFloatConst(name, val);
					}
					break;

					// String constant.
					// c=svalue
					// value is a strinc encosed in either ' in which case ' are
					// escaped or
					// " in which case " are escaped.
					// Not supported by GDB.
				case 's' :
					break;

					// Set constant.
					// C/C++ does not have set
					// Not supported by GDB.
				case 'S' :
					break;
			}
		}
	}

	DebugType getDebugType(TypeNumber tn) {
		return mapTypes.get(tn);
	}

	public static void main(String[] args) {
		try {
			DebugSymsRequestor symreq = new DebugSymsRequestor();				
			Stabs stabs = new Stabs(args[0]);
			stabs.parse(symreq);
			DebugSym[] entries = symreq.getEntries();
			for (DebugSym entry : entries) {
				System.out.println(entry);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
