/*******************************************************************************
 *  Copyright (c) 2006, 2015 IBM Corporation and others.
 *
 *  This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License 2.0
 *  which accompanies this distribution, and is available at
 *  https://www.eclipse.org/legal/epl-2.0/
 *
 *  SPDX-License-Identifier: EPL-2.0
 *
 *  Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core.dom.lrparser.c99.action.deprecated;

import static org.eclipse.cdt.core.parser.util.CollectionUtils.reverseIterable;
import static org.eclipse.cdt.internal.core.dom.lrparser.symboltable.CNamespace.GOTO_LABEL;
import static org.eclipse.cdt.internal.core.dom.lrparser.symboltable.CNamespace.IDENTIFIER;
import static org.eclipse.cdt.internal.core.dom.lrparser.symboltable.CNamespace.STRUCT_TAG;

import java.util.LinkedList;
import java.util.List;

import org.eclipse.cdt.core.dom.ast.EScopeKind;
import org.eclipse.cdt.core.dom.ast.IASTBinaryExpression;
import org.eclipse.cdt.core.dom.ast.IASTElaboratedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTLiteralExpression;
import org.eclipse.cdt.core.dom.ast.IArrayType;
import org.eclipse.cdt.core.dom.ast.IBasicType;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.ICompositeType;
import org.eclipse.cdt.core.dom.ast.IField;
import org.eclipse.cdt.core.dom.ast.IFunctionType;
import org.eclipse.cdt.core.dom.ast.IParameter;
import org.eclipse.cdt.core.dom.ast.IPointerType;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.ITypedef;
import org.eclipse.cdt.core.dom.lrparser.action.ITokenStream;
import org.eclipse.cdt.core.dom.lrparser.action.ScopedStack;
import org.eclipse.cdt.core.parser.util.DebugUtil;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.C99Parsersym;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99ArrayType;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99BasicType;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Enumeration;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Enumerator;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Field;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Function;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99FunctionScope;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99FunctionType;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Label;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Parameter;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99PointerType;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99ProblemBinding;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Scope;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Structure;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Typedef;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.C99Variable;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.IC99Binding;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.IC99Scope;
import org.eclipse.cdt.internal.core.dom.lrparser.c99.bindings.ITypeable;
import org.eclipse.cdt.internal.core.dom.lrparser.symboltable.C99SymbolTable;
import org.eclipse.cdt.internal.core.dom.parser.ITypeContainer;

import lpg.lpgjavaruntime.IToken;

/**
 * This class was an attempt at doing full binding resolution during the parse
 * as opposed to doing it after the parse as is normally done with the DOM parser.
 *
 *
 * TODO: token mapping so that this will work with UPC
 * TODO: what about function definitions, don't they count as declarations?
 *
 * Try to resolve bindings without using the ASTStack, that way I can resolve bindings
 * without generating an AST. In the future I can remove this as a subclass of C99ParserAction.
 *
 * TODO: if I'm calculating scopes then those scopes need to be linked to AST nodes
 *
 * TODO: some language constructs are not handled yet: typeIds (casts), field designators
 *
 * @author Mike Kucera
 *
 * @deprecated Binding resolution is too hard, replacing with simpler C99TypedefTrackerParserAction
 */
@SuppressWarnings("restriction")
@Deprecated
public class C99ResolveParserAction {

	private static final boolean DEBUG = true;
	private static final String NO_IDENT = ""; //$NON-NLS-1$

	// provides limited access to the token stream
	private final ITokenStream parser;

	// The symbolTable currently in use
	private C99SymbolTable symbolTable = C99SymbolTable.EMPTY_TABLE;

	// A stack that keeps track of scopes in the symbol table, used to "close" scopes and to undo the opening of scopes
	private final LinkedList<C99SymbolTable> symbolTableScopeStack = new LinkedList<>();

	// A stack that keeps track of scopes that are set on bindings
	private final LinkedList<IC99Scope> bindingScopeStack = new LinkedList<>();

	// keeps track of nested declarations
	private final LinkedList<DeclaratorFrame> declarationStack = new LinkedList<>();

	// keeps track of expression types
	private final ScopedStack<IType> exprTypeStack = new ScopedStack<>();

	private TypeQualifiers typeQualifiers; // TODO: can this go in the declaration stack?

	private static class TypeQualifiers {
		boolean isConst, isRestrict, isVolatile;
	}

	// "For every action there is an equal and opposite reaction." - Newton's third law
	private final LinkedList<IUndoAction> undoStack = new LinkedList<>();

	private interface IUndoAction {
		void undo();
	}

	public void undo() {
		undoStack.removeLast().undo();
	}

	public void undo(int steps) {
		for (int i = 0; i < steps; i++) {
			undo();
		}
	}

	public IC99Scope getCurrentScope() {
		return bindingScopeStack.getLast();
	}

	public C99ResolveParserAction(ITokenStream parser) {
		this.parser = parser;
		bindingScopeStack.add(new C99Scope(EScopeKind.eGlobal)); // the global scope
		System.out.println();
	}

	private static IType rawType(IType type) {
		while (type instanceof ITypedef) {
			type = ((C99Typedef) type).getType();
		}
		return type;
	}

	/**
	 * Lexer feedback hack, used by the parser to identify typedefname tokens.
	 */
	public boolean isTypedef(String ident) {
		boolean result = symbolTable.lookup(IDENTIFIER, ident) instanceof ITypedef;
		return result;
	}

	/**
	 * Methods used by tests, package local access.
	 */
	C99SymbolTable getSymbolTable() {
		return symbolTable;
	}

	int undoStackSize() {
		return undoStack.size();
	}

	LinkedList<DeclaratorFrame> getDeclarationStack() {
		return declarationStack;
	}

	/**
	 * Called from the grammar file in places where a scope is created.
	 *
	 * Scopes are created by compound statements, however special care
	 * must also be taken with for loops because they may contain
	 * declarations.
	 *
	 * TODO: scope object now need to be handled explicitly
	 */
	public void openSymbolScope() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		symbolTableScopeStack.add(symbolTable);
		bindingScopeStack.add(new C99Scope(EScopeKind.eLocal));

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {

				bindingScopeStack.removeLast();
				symbolTable = symbolTableScopeStack.removeLast();
			}
		});
	}

	public IC99Scope closeSymbolScope() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final C99SymbolTable undoTable = symbolTable;
		symbolTable = symbolTableScopeStack.removeLast(); // close the scope

		final IC99Scope undoScope = bindingScopeStack.removeLast();
		if (!bindingScopeStack.isEmpty())
			undoScope.setParent(bindingScopeStack.getLast());

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				undoScope.setParent(null);
				bindingScopeStack.add(undoScope);

				symbolTableScopeStack.add(symbolTable);
				symbolTable = undoTable;
			}
		});

		return undoScope;
	}

	// TODO, this needs an undo action
	public void openPointerScope() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		frame.openPointerModifierScope();

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				frame.closePointerModifierScope();
			}
		});
	}

	/**
	 * Called from the grammar before a declaration is about to be reduced.
	 */
	public void openDeclarationScope() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		declarationStack.add(new DeclaratorFrame());

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				declarationStack.removeLast();
			}
		});
	}

	public void closeDeclarationScope() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame undoFrame = declarationStack.removeLast();

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				declarationStack.add(undoFrame);
			}
		});
	}

	public void consumeFunctionDefinition() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		//		final IC99Scope undoScope = bindingScope;
		//
		//		C99FunctionScope functionScope = new C99FunctionScope();
		//		functionScope.setBodyScope(undoScope);
		//		undoScope.setParent(functionScope);
		//		bindingScope = bindingScopeStack.removeLast();
		//		functionScope.setParent(bindingScope);

		final IC99Scope undoScope = bindingScopeStack.removeLast();

		C99FunctionScope functionScope = new C99FunctionScope();
		functionScope.setBodyScope(undoScope);
		undoScope.setParent(functionScope);
		functionScope.setParent(bindingScopeStack.getLast());

		final DeclaratorFrame frame = declarationStack.removeLast();

		// the function binding needs to be available outside of the function's scope
		String functionName = frame.getDeclaratorName().toString();
		C99Function functionBinding = (C99Function) symbolTable.lookup(IDENTIFIER, functionName);
		functionBinding.setFunctionScope(functionScope);

		final C99SymbolTable undoTable = symbolTable;
		final C99SymbolTable outerTable = symbolTableScopeStack.removeLast();
		symbolTable = outerTable.insert(IDENTIFIER, functionName, functionBinding);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				symbolTableScopeStack.add(outerTable);
				symbolTable = undoTable;
				declarationStack.add(frame);
				bindingScopeStack.add(undoScope);
			}
		});
	}

	public void consumeAbstractDeclaratorFunctionDeclarator() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		frame.setFunctionDeclarator(true);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				frame.setFunctionDeclarator(false);
			}
		});
	}

	public void consumeDirectDeclaratorFunctionDeclarator() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		frame.setFunctionDeclarator(true);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.setFunctionDeclarator(false);
			}
		});
	}

	public void consumeDeclSpecToken() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		IToken token = parser.getRightIToken();
		final int kind = token.getKind();

		// creates a DeclSpec if there isn't one already
		DeclaratorFrame frame = declarationStack.getLast();
		final DeclSpec declSpec = frame.getDeclSpec();
		declSpec.add(kind);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				declSpec.remove(kind);
			}
		});
	}

	/**
	 * A labeled statement is creates an implicit declaration of the label identifier.
	 *
	 * TODO: a label has function scope, meaning the same label cannot be declared twice
	 * in a function regardless of block scope.
	 * TODO: labels can be implicitly declared, that is a goto can exist above the label
	 */
	public IBinding consumeStatementLabeled() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		IToken token = parser.getLeftIToken();
		String ident = token.toString();

		IC99Binding labelBinding = new C99Label(ident);
		// TODO: strictly speaking the same label cannot be declared twice,
		// but we aren't checking that here
		final C99SymbolTable oldTable = symbolTable;
		symbolTable = symbolTable.insert(GOTO_LABEL, ident, labelBinding);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				symbolTable = oldTable;
			}
		});

		return labelBinding;
	}

	public IBinding consumeStatementGoto() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		List<IToken> ruleTokens = parser.getRuleTokens();
		assert ruleTokens.size() == 3 : "a goto statement must always consist of 3 tokens"; //$NON-NLS-1$
		String ident = ruleTokens.get(1).toString();

		final C99SymbolTable oldTable = symbolTable;

		IC99Binding labelBinding = symbolTable.lookup(GOTO_LABEL, ident);
		if (labelBinding == null) {
			labelBinding = new C99Label(ident);
			symbolTable = symbolTable.insert(GOTO_LABEL, ident, labelBinding);
		}

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				symbolTable = oldTable;
			}
		});

		return labelBinding;
	}

	public IBinding consumeDeclarationSpecifiersTypedefName() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		// find the typedef token
		String typedefName = null;
		for (IToken token : parser.getRuleTokens()) {
			// the token kind will still be TK_identifier, but there can only be one
			if (token.getKind() == C99Parsersym.TK_identifier) {
				typedefName = token.toString();
				break;
			}
		}
		assert typedefName != null : "a typedef token must have been parsed for this action to fire"; //$NON-NLS-1$

		// we know that the binding is a typedef because the lexer feedback hack worked and got us here
		ITypedef binding = (ITypedef) symbolTable.lookup(IDENTIFIER, typedefName);
		// TODO: do I need to clone the typedef in case it is further modified like with const?
		DeclaratorFrame frame = declarationStack.getLast();
		final DeclSpec declSpec = frame.getDeclSpec();
		declSpec.setType(binding);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				declSpec.setType(null);
			}
		});

		return binding;
	}

	public void consumeDirectDeclaratorIdentifier() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		DebugUtil.printMethodTrace();
		frame.setDeclaratorName(parser.getRightIToken());

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				DebugUtil.printMethodTrace();
				frame.setDeclaratorName(null);
			}
		});
	}

	// TODO need to be careful, this is called in a lot of places in the grammar
	public void consumeDeclaratorWithPointer() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		final LinkedList<C99PointerType> scope = frame.closePointerModifierScope();
		final int scopeSize = scope.size();

		for (C99PointerType pt : reverseIterable(scope)) {
			frame.addTypeModifier(pt);
		}

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				for (int i = 0; i < scopeSize; i++) {
					frame.removeLastTypeModifier();
				}
				frame.openPointerModifierScope(scope);
			}
		});
	}

	public void consumeDirectDeclaratorBracketed() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		// Used to tell the difference between function prototype declarations and function pointer declarations
		final DeclaratorFrame frame = declarationStack.getLast();
		frame.setDeclaratorBracketed(true);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.setDeclaratorBracketed(false);
			}
		});
	}

	public IBinding consumeDeclaratorComplete() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();

		IToken token = frame.getDeclaratorName();
		IType type = frame.getDeclaratorType();
		DeclSpec declSpec = frame.getDeclSpec();
		boolean isFunction = frame.isFunctionDeclarator();
		List<IBinding> nestedDeclarators = frame.getNestedDeclarations();

		if (isFunction)
			type = createFunctionType(type, nestedDeclarators);

		String ident = (token == null) ? null : token.toString();

		// compute the binding
		IC99Binding binding;
		if (declSpec.isTypedef())
			binding = createTypedefBinding(ident, type);
		else if (isFunction && !frame.isDeclaratorBracketed())
			binding = createFunctionBinding(ident, (IFunctionType) type, declSpec, nestedDeclarators);
		else
			binding = createVariableBinding(ident, type, declSpec);

		binding.setScope(bindingScopeStack.getLast());

		// insert into symbol table
		final C99SymbolTable oldTable = symbolTable;
		if (ident != null)
			symbolTable = symbolTable.insert(IDENTIFIER, ident, binding);

		declarationStack.removeLast();
		declarationStack.add(new DeclaratorFrame(frame.getDeclSpec())); // reset the declarator
		exprTypeStack.push(type);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				exprTypeStack.pop();
				declarationStack.removeLast();
				declarationStack.add(frame);
				symbolTable = oldTable;
			}
		});

		return binding;
	}

	/**
	 * Just gets rid of the type that was on the type stack.
	 */
	public void consumeInitDeclarator() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final IType type = exprTypeStack.pop();

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				exprTypeStack.push(type);
			}
		});
	}

	public IBinding consumeFunctionDefinitionHeader() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		DeclaratorFrame frame = declarationStack.getLast();
		DeclSpec declSpec = frame.getDeclSpec();
		String functionName = frame.getDeclaratorName().toString();

		final C99SymbolTable oldTable = symbolTable;

		// there may have been a function prototype, hence there may already be a binding for the function
		IC99Binding binding = symbolTable.lookup(IDENTIFIER, functionName);
		if (binding == null) {
			IType type = frame.getDeclaratorType();
			List<IBinding> nestedDeclarators = frame.getNestedDeclarations();

			IFunctionType functionType = createFunctionType(type, nestedDeclarators);
			binding = createFunctionBinding(functionName, functionType, declSpec, nestedDeclarators);

			// a scope has already been opened for the function body, use the outer scope
			IC99Scope topScope = bindingScopeStack.removeLast();
			binding.setScope(bindingScopeStack.getLast());
			bindingScopeStack.add(topScope);

			symbolTable = symbolTable.insert(IDENTIFIER, functionName, binding);
		}

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				symbolTable = oldTable;
			}
		});

		return binding;
	}

	public IBinding consumeDeclaratorCompleteParameter() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.removeLast();
		IToken token = frame.getDeclaratorName();
		IType type = frame.getDeclaratorType();
		DeclSpec declSpec = frame.getDeclSpec();
		boolean isFunction = frame.isFunctionDeclarator();

		// its a function pointer
		if (isFunction)
			type = createFunctionType(type, frame.getNestedDeclarations());

		String ident = (token == null) ? NO_IDENT : token.toString();

		IC99Binding parameterBinding = createParameterBinding(ident, type, declSpec);
		parameterBinding.setScope(bindingScopeStack.getLast());

		// check to see if there is already a parameter binding
		String functionName = declarationStack.getLast().getDeclaratorName().toString();
		C99Function function = (C99Function) symbolTable.lookup(IDENTIFIER, functionName);

		if (function != null) {
			// there is already a function binding for this function, that means there
			// is a function prototype and there is already a binding for this parameter
			int position = declarationStack.getLast().getNestedDeclarations().size();
			IParameter[] parameters = function.getParameters();
			if (parameters != null && position < parameters.length) {
				parameterBinding = (IC99Binding) parameters[position];
			}
		}

		// even if the binding is reused it still might be under a different name
		final C99SymbolTable oldTable = symbolTable;
		if (ident != null) {
			symbolTable = symbolTable.insert(IDENTIFIER, ident, parameterBinding);
		}

		declarationStack.getLast().addNestedDeclaration(parameterBinding);

		// parameter declarations can only have one declarator, so don't reset
		//declarationStack.add(new DeclaratorFrame()); // reset

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				//declarationStack.removeLast();
				declarationStack.getLast().removeLastNestedDeclaration();
				declarationStack.add(frame);
				symbolTable = oldTable;
			}
		});

		return parameterBinding;
	}

	/**
	 * This is a special case for the rule:
	 *     parameter_declaration ::= declaration_specifiers
	 *
	 * In this case there is no declarator at all
	 *
	 * TODO: creating bindings that have no identifier seems really dumb,
	 * why does it need to be done? Why not just have a null binding or
	 * for that matter don't even have a name node
	 *
	 */
	public IBinding consumeParameterDeclarationWithoutDeclarator() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.removeLast();
		DeclSpec declSpec = frame.getDeclSpec();
		C99Parameter param = createParameterBinding(null, declSpec.getType(), declSpec);
		declarationStack.getLast().addNestedDeclaration(param);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				declarationStack.getLast().removeLastNestedDeclaration();
				declarationStack.add(frame);
			}
		});

		return param;
	}

	public IBinding consumeDeclaratorCompleteField() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		IToken token = frame.getDeclaratorName();
		IType type = frame.getDeclaratorType();

		// its a function pointer
		if (frame.isFunctionDeclarator())
			type = createFunctionType(type, frame.getNestedDeclarations());

		IBinding binding = createFieldBinding(token.toString(), type, frame.getDeclSpec());

		declarationStack.removeLast();
		declarationStack.getLast().addNestedDeclaration(binding);
		declarationStack.add(new DeclaratorFrame(frame.getDeclSpec())); // reset the declarator
		exprTypeStack.push(type);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
				declarationStack.removeLast();
				declarationStack.getLast().removeLastNestedDeclaration();
				declarationStack.add(frame);
			}
		});

		return binding;
	}

	/**
	 * An abstract declarator used as part of an expression, eg) a cast.
	 * Only need the type.
	 *
	 * TODO: this isn't enough, I need a binding for the abstract declarator
	 * what I really need is a consumeDeclaratorCompleteTypeId similar to above
	 */
	public void consumeTypeId() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.removeLast();
		IType type = frame.getDeclaratorType();
		if (frame.isFunctionDeclarator()) // its a function pointer
			type = createFunctionType(type, frame.getNestedDeclarations());

		exprTypeStack.push(type);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
				declarationStack.add(frame);
			}
		});
	}

	public void consumeDirectDeclaratorArrayModifier() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		frame.addTypeModifier(new C99ArrayType());

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.removeLastTypeModifier();
			}
		});
	}

	public void consumeAbstractDeclaratorArrayModifier() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		frame.addTypeModifier(new C99ArrayType());

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.removeLastTypeModifier();
			}
		});
	}

	public void consumeDirectDeclaratorModifiedArrayModifier(boolean isStatic, boolean isVarSized,
			boolean hasTypeQualifierList) {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		C99ArrayType arrayType = new C99ArrayType();
		arrayType.setStatic(isStatic);
		arrayType.setVariableLength(isVarSized);

		if (hasTypeQualifierList) {
			arrayType.setConst(typeQualifiers.isConst);
			arrayType.setRestrict(typeQualifiers.isRestrict);
			arrayType.setVolatile(typeQualifiers.isVolatile);
		}

		final DeclaratorFrame frame = declarationStack.getLast();
		frame.addTypeModifier(arrayType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.removeLastTypeModifier();
			}
		});
	}

	public void consumePointer() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final DeclaratorFrame frame = declarationStack.getLast();
		frame.addPointerModifier(new C99PointerType());

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.removeLastPointerModifier();
			}
		});
	}

	public void consumePointerTypeQualifierList() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		C99PointerType pointerType = new C99PointerType();
		pointerType.setConst(typeQualifiers.isConst);
		pointerType.setRestrict(typeQualifiers.isRestrict);
		pointerType.setVolatile(typeQualifiers.isVolatile);

		final DeclaratorFrame frame = declarationStack.getLast();
		frame.addPointerModifier(pointerType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.removeLastPointerModifier();
			}
		});
	}

	public void consumeTypeQualifiers() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		typeQualifiers = new TypeQualifiers();

		for (IToken token : parser.getRuleTokens()) {
			switch (token.getKind()) {
			case C99Parsersym.TK_const:
				typeQualifiers.isConst = true;
				break;
			case C99Parsersym.TK_restrict:
				typeQualifiers.isRestrict = true;
				break;
			case C99Parsersym.TK_volatile:
				typeQualifiers.isVolatile = true;
				break;
			}
		}

		// I don't think this is really necessary but I need an undo action anyway
		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				typeQualifiers = null;
			}
		});
	}

	/**
	 * Works for structs, unions and enums.
	 * If the struct tag is not in the symbol table then it is treated
	 * as a declaration.
	 */
	public IBinding consumeTypeSpecifierElaborated(int kind) {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		String tag = parser.getRightIToken().toString();

		IC99Binding structBinding = symbolTable.lookup(STRUCT_TAG, tag);

		final C99SymbolTable undoTable;

		final boolean isDeclaration = (structBinding == null);

		if (isDeclaration) { // declaration of an incomplete type
			if (kind == IASTElaboratedTypeSpecifier.k_enum)
				structBinding = new C99Enumeration();
			else
				structBinding = new C99Structure(kind);

			undoTable = symbolTable;
			symbolTable = symbolTable.insert(STRUCT_TAG, tag, structBinding);
		} else {
			undoTable = null; // final variable must be initialized
		}

		final DeclSpec declSpec = declarationStack.getLast().getDeclSpec();
		declSpec.setType((IType) structBinding); // upcast

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				declSpec.setType(null);

				if (isDeclaration) {
					assert undoTable != null;
					symbolTable = undoTable;
				}
			}
		});

		return structBinding;
	}

	/**
	 * @param key A field in IASTCompositeTypeSpecifier.
	 */
	public IBinding consumeTypeSpecifierComposite(final boolean hasName, int key) {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		// If the symbol table isn't updated then its still ok to undo
		// because setting symbolTable to oldTable will effectively be a no-op.
		final C99SymbolTable oldTable = symbolTable;

		C99Structure struct;
		if (hasName) {
			String ident = parser.getRuleTokens().get(1).toString();
			struct = (C99Structure) symbolTable.lookup(STRUCT_TAG, ident); // structure may have already been declared
			if (struct == null) {
				struct = new C99Structure(ident, key);
				symbolTable = symbolTable.insert(STRUCT_TAG, ident, struct);
			}
		} else {
			struct = new C99Structure(key);
		}

		final DeclaratorFrame frame = declarationStack.getLast();
		for (IBinding binding : frame.getNestedDeclarations()) {
			// the parser may allow invalid declarations like typedefs inside of structures, ignore those
			if (binding instanceof C99Field) {
				C99Field field = (C99Field) binding;
				struct.addField(field);
				field.setCompositeTypeOwner(struct);
			}
		}

		frame.getDeclSpec().setType(struct);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.getDeclSpec().setType(null);
				symbolTable = oldTable;
			}
		});

		return struct;
	}

	public IBinding consumeTypeSpecifierEnumeration(final boolean hasName) {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		C99Enumeration enumeration = new C99Enumeration();

		final C99SymbolTable oldTable = symbolTable;
		if (hasName) {
			String ident = parser.getRuleTokens().get(1).toString();
			enumeration.setName(ident);
			symbolTable = symbolTable.insert(STRUCT_TAG, ident, enumeration);
		}

		final DeclaratorFrame frame = declarationStack.getLast();
		for (IBinding binding : frame.getNestedDeclarations()) {
			C99Enumerator enumerator = (C99Enumerator) binding;
			enumeration.addEnumerator(enumerator);
			enumerator.setType(enumeration);
		}

		frame.getDeclSpec().setType(enumeration);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				frame.getDeclSpec().setType(null);
				if (hasName)
					symbolTable = oldTable;
			}
		});

		return enumeration;
	}

	public IBinding consumeEnumerator() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		IToken token = parser.getLeftIToken();
		String ident = token.toString();
		C99Enumerator enumerator = new C99Enumerator(ident);

		final C99SymbolTable oldTable = symbolTable;
		symbolTable = symbolTable.insert(IDENTIFIER, ident, enumerator);

		// enumerators are not declarations in the standard sense, so a scope won't be opened for them
		declarationStack.getLast().addNestedDeclaration(enumerator);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				declarationStack.getLast().removeLastNestedDeclaration();
				symbolTable = oldTable;
			}
		});

		return enumerator;
	}

	public IField consumeDesignatorBaseField() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		IType baseType = getInitializerType();
		String fieldName = parser.getRightIToken().toString();

		C99Field fieldBinding = computeFieldBinding(baseType, fieldName, false);
		IType type = fieldBinding == null ? C99ProblemBinding.badType() : fieldBinding.getType();

		exprTypeStack.push(type);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
			}
		});

		return fieldBinding;
	}

	private IType getInitializerType() {
		List<IType> outerScope = exprTypeStack.outerScope();
		return outerScope.get(outerScope.size() - 1);
	}

	public void consumeDesignatorBaseArray() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		IType baseType = getInitializerType();

		IType type = C99ProblemBinding.badType();
		if (baseType instanceof C99ArrayType) {
			type = ((C99ArrayType) baseType).getType();
		}

		exprTypeStack.push(type);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
			}
		});
	}

	public void consumeInitializerStartPositional() {
		if (DEBUG)
			DebugUtil.printMethodTrace();
		DebugUtil.printMethodTrace();

		IType type = getInitializerType();
		type = rawType(type);

		IType positionType;
		if (type instanceof C99Structure) {
			int position = exprTypeStack.topScope().size();
			C99Field field = (C99Field) ((C99Structure) type).getFields()[position];
			positionType = field.getType();
		} else if (type instanceof IArrayType) {
			positionType = ((C99ArrayType) type).getType();
		} else {
			positionType = C99ProblemBinding.badType();
		}

		exprTypeStack.push(positionType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
			}
		});
	}

	private static C99FunctionType createFunctionType(IType returnType, List<IBinding> parameterDeclarations) {
		C99FunctionType functionType = new C99FunctionType();
		functionType.setReturnType(returnType);
		for (IBinding b : parameterDeclarations) {
			C99Variable parameter = (C99Variable) b;
			functionType.addParameterType(parameter.getType());
		}
		return functionType;
	}

	// helper functions for creating binding objects

	private static C99Function createFunctionBinding(String ident, IFunctionType type, DeclSpec declSpec) {
		C99Function func = new C99Function(ident == null ? NO_IDENT : ident, type);
		declSpec.modifyBinding(func);
		return func;
	}

	private static C99Function createFunctionBinding(String ident, IFunctionType type, DeclSpec declSpec,
			List<IBinding> params) {
		C99Function func = createFunctionBinding(ident, type, declSpec);
		for (IBinding b : params) {
			func.addParameter((IParameter) b);
		}
		return func;
	}

	private static C99Field createFieldBinding(String ident, IType type, DeclSpec declSpec) {
		C99Field var = new C99Field(ident == null ? NO_IDENT : ident);
		var.setType(type);
		declSpec.modifyBinding(var);
		return var;
	}

	private static C99Parameter createParameterBinding(String ident, IType type, DeclSpec declSpec) {
		C99Parameter param = new C99Parameter(ident == null ? NO_IDENT : ident);
		param.setType(type);
		declSpec.modifyBinding(param);
		return param;
	}

	private static C99Variable createVariableBinding(String ident, IType type, DeclSpec declSpec) {
		C99Variable var = new C99Variable(ident == null ? NO_IDENT : ident);
		var.setType(type);
		declSpec.modifyBinding(var);
		return var;
	}

	private static C99Typedef createTypedefBinding(String ident, IType type) {
		return new C99Typedef(type, ident == null ? NO_IDENT : ident);
	}

	public void openTypeScope() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		exprTypeStack.openScope();

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.closeScope();
			}
		});
	}

	@SuppressWarnings("nls")
	public void consumeExpressionConstant(int kind) {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		//super.consumeExpressionConstant(kind);
		// TODO: this is incomplete, what about double constants, int constants with long suffix etc
		String constant = parser.getRightIToken().toString();

		IType type = null;
		switch (kind) {
		case IASTLiteralExpression.lk_char_constant:
			if (constant.startsWith("L")) {//unsigned short int
				C99BasicType charType = new C99BasicType(IBasicType.t_int);
				charType.setShort(true);
				charType.setUnsigned(true);
				type = charType;
			} else
				type = new C99BasicType(IBasicType.t_char);
			break;

		case IASTLiteralExpression.lk_float_constant:
			C99BasicType floatType;
			if (constant.contains("f") || constant.contains("F"))
				floatType = new C99BasicType(IBasicType.t_float);
			else
				floatType = new C99BasicType(IBasicType.t_double);

			if (constant.contains("l") || constant.contains("L"))
				floatType.setLong(true);

			type = floatType;
			break;

		case IASTLiteralExpression.lk_integer_constant:
			C99BasicType intType = new C99BasicType(IBasicType.t_int);
			if (constant.contains("l") || constant.contains("L"))
				intType.setLong(true);

			if (constant.contains("ll") || constant.contains("LL")) {
				intType.setLongLong(true);
				intType.setLong(false);
			}
			if (constant.contains("u") || constant.contains("U"))
				intType.setUnsigned(true);

			type = intType;
			break;

		case IASTLiteralExpression.lk_string_literal:
			type = new C99PointerType(new C99BasicType(IBasicType.t_char));
			break;

		default:
			assert false : "can't get here"; //$NON-NLS-1$
		}

		exprTypeStack.push(type);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
			}
		});
	}

	public IBinding consumeExpressionID() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		IToken token = parser.getRightIToken();
		String ident = token.toString();
		IBinding binding = symbolTable.lookup(IDENTIFIER, ident);

		IType type = C99ProblemBinding.badType();
		if (binding instanceof ITypeable)
			type = ((ITypeable) binding).getType();

		exprTypeStack.push(type);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
			}
		});

		return binding;
	}

	public IField consumeExpressionFieldReference(boolean isPointerDereference) {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		String memberName = parser.getRightIToken().toString();
		final IType identType = exprTypeStack.pop();

		C99Field field = computeFieldBinding(identType, memberName, isPointerDereference);
		IType resultType = field == null ? C99ProblemBinding.badType() : field.getType();

		exprTypeStack.push(resultType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
				exprTypeStack.push(identType);
			}
		});

		return field;
	}

	/**
	 * Computes the type of a field member access in an expression.
	 * eg) x.a; computes the type of a
	 */
	private C99Field computeFieldBinding(IType identType, String memberName, boolean isPointerDereference) {
		IType type = identType;
		if (isPointerDereference) {
			if (type instanceof IPointerType)
				type = ((ITypeContainer) type).getType(); // do the dereference
			else
				return null;
		}

		type = rawType(type);
		if (type instanceof ICompositeType) {
			ICompositeType struct = (ICompositeType) type;
			return (C99Field) struct.findField(memberName);
		}

		return null;
	}

	// TODO In C a function name can be used without parenthesis, the result is the address of
	// the function (which is an int I think). This means an identifier that happens to be
	// a function name should probably evaluate to an int, and then if it subsequently gets parsed
	// as a function call we can look up its function type from the symbol table.
	public void consumeExpressionFunctionCall(final boolean hasArgs) {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final List<IType> scope = hasArgs ? exprTypeStack.closeScope() : null;
		final IType identifierType = exprTypeStack.pop();

		IType resultType = C99ProblemBinding.badType();

		if (identifierType instanceof IFunctionType) {
			// TODO: check the parameter types
			IFunctionType functionType = (IFunctionType) identifierType;
			resultType = functionType.getReturnType();

		}

		exprTypeStack.push(resultType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
				exprTypeStack.push(identifierType);
				if (hasArgs)
					exprTypeStack.openScope(scope);
			}
		});
	}

	public void consumeExpressionArraySubscript() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		// Not doing type checking so it doesn't matter that this is integral type
		final IType subscriptType = exprTypeStack.pop();
		final IType exprType = exprTypeStack.pop();

		IType resultType = C99ProblemBinding.badType();
		if (exprType instanceof IArrayType) {
			IArrayType arrType = (IArrayType) exprType;
			resultType = arrType.getType(); // strip off the array type

		}

		exprTypeStack.push(resultType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
				exprTypeStack.push(exprType);
				exprTypeStack.push(subscriptType);
			}
		});
	}

	public void consumeExpressionUnaryOperator() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		// TODO: this is lazy, need to check the actual rules for types and operators
		final IType expressionType = exprTypeStack.pop();

		IType resultType = new C99BasicType(IBasicType.t_int);
		exprTypeStack.push(resultType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
				exprTypeStack.push(expressionType);
			}
		});
	}

	public void consumeExpressionUnarySizeofTypeName() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final IType typeNameType = exprTypeStack.pop();

		IType resultType = new C99BasicType(IBasicType.t_int);
		exprTypeStack.push(resultType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
				exprTypeStack.push(typeNameType);
			}
		});
	}

	public void consumeExpressionCast() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final IType exprType = exprTypeStack.pop();

		// pop then push is no-op
		//IType castType = exprTypeStack.pop();
		//exprTypeStack.push(castType);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.push(exprType);
			}
		});
	}

	public void consumeExpressionTypeIdInitializer() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		// Throw away the types of the initializer list
		final List<IType> scope = exprTypeStack.closeScope();

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.openScope(scope);
			}
		});
	}

	public void consumeExpressionInitializer() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final IType type = exprTypeStack.pop();

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				exprTypeStack.push(type);
			}
		});
	}

	public void consumeExpressionBinaryOperator(int op) {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final IType op2type = exprTypeStack.pop();
		final IType op1type = exprTypeStack.pop();

		switch (op) {
		case IASTBinaryExpression.op_assign:
		case IASTBinaryExpression.op_multiplyAssign:
		case IASTBinaryExpression.op_divideAssign:
		case IASTBinaryExpression.op_moduloAssign:
		case IASTBinaryExpression.op_plusAssign:
		case IASTBinaryExpression.op_minusAssign:
		case IASTBinaryExpression.op_shiftLeftAssign:
		case IASTBinaryExpression.op_shiftRightAssign:
		case IASTBinaryExpression.op_binaryAndAssign:
		case IASTBinaryExpression.op_binaryXorAssign:
		case IASTBinaryExpression.op_binaryOrAssign:
			exprTypeStack.push(op1type);
			break;

		default:
			IType resultType = new C99BasicType(IBasicType.t_int);
			exprTypeStack.push(resultType);
		}

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();

				exprTypeStack.pop();
				exprTypeStack.push(op1type);
				exprTypeStack.push(op2type);
			}
		});
	}

	public void consumeExpressionConditional() {
		if (DEBUG)
			DebugUtil.printMethodTrace();

		final IType expr2Type = exprTypeStack.pop();
		final IType expr1Type = exprTypeStack.pop();
		final IType condType = exprTypeStack.pop();
		exprTypeStack.push(expr1Type);

		undoStack.add(new IUndoAction() {
			@Override
			public void undo() {
				if (DEBUG)
					DebugUtil.printMethodTrace();
				exprTypeStack.pop();
				exprTypeStack.push(condType);
				exprTypeStack.push(expr1Type);
				exprTypeStack.push(expr2Type);
			}
		});
	}

	//	// TODO: expression lists are changing
	//	public void consumeExpressionList(boolean baseCase) {
	//		// This is a hack, the type of an expression
	//		// list will be the first expression in the list.
	//		if(!baseCase) {
	//			exprTypeStack.pop();
	//		}
	//	}

}
