/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004-2008 Robert Grimm
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.lang;
import java.util.HashMap;
import xtc.tree.Annotation;
import xtc.tree.LineMarker;
import xtc.tree.Location;
import xtc.tree.Node;
import xtc.tree.Pragma;
import xtc.tree.SourceIdentity;
import xtc.util.State;
/**
* The global state for parsing C. This class provides a simplified
* symbol table that is organized as a stack of parsing contexts, with
* a new context being pushed onto the stack through {@link #start()}
* and popped again through {@link #commit()} or {@link #abort()}.
*
* @author Robert Grimm
* @version $Revision: 1.16 $
*/
public class CParserState implements State {
/** A parsing context. */
protected static class Context {
/** The next context. */
public Context next;
/**
* The flags for this context. The flags from LSB to MSB are:
* FLAG_TYPEDEF, FLAG_SCOPE, FLAG_TYPE_SPEC, FLAG_PARAMS,
* FLAG_MODIFIED, FLAG_STRUCTURE.
*/
public int flags;
/** The bindings for this context, if any. */
public final HashMap<String,Boolean> bindings;
/** The marked annotation, if any. */
public Annotation mark;
// ------------------------------------------------------------------------
/** Create a new context. */
public Context() {
bindings = new HashMap<String,Boolean>();
}
// ------------------------------------------------------------------------
/** Clear this context. */
public void clear() {
if (isSet(FLAG_MODIFIED)) {
bindings.clear();
}
flags = 0;
mark = null;
}
// ------------------------------------------------------------------------
/**
* Determine whether the specified flag is set.
*
* @param flag The flag.
* @return <code>true</code> if the flag is set.
*/
public boolean isSet(final int flag) {
return (0 != (flag & flags));
}
/**
* Set the specified flag.
*
* @param flag The flag.
*/
public void set(final int flag) {
flags |= flag;
}
/**
* Clear the specified flag.
*
* @param flag The flag.
*/
public void clear(final int flag) {
flags &= ~flag;
}
// ------------------------------------------------------------------------
/** Print a trace of all contexts starting with this context. */
public void trace() {
Context c = this;
do {
System.out.print(" Context@0x");
System.out.print(Integer.toHexString(System.identityHashCode(c)));
System.out.print(": flags=0b");
System.out.println(Integer.toBinaryString(c.flags));
c = c.next;
} while (null != c);
System.out.flush();
}
}
// ==========================================================================
/** The flag for whether to print debug information to the console. */
protected static final boolean DEBUG = false;
/** The initial size of the context pool. */
protected static final int POOL_INIT = 10;
/** The increment of the context pool. */
protected static final int POOL_INCR = 5;
/** The flag for typedefs. */
protected static final int FLAG_TYPEDEF = 0x01;
/** The flag for scopes. */
protected static final int FLAG_SCOPE = 0x02;
/** The flag for having parsed a type specifier. */
protected static final int FLAG_TYPE_SPEC = 0x04;
/** The flag for having parsed a function parameter list. */
protected static final int FLAG_PARAMS = 0x08;
/** The flag for having modified the bindings. */
protected static final int FLAG_MODIFIED = 0x10;
/** The flag for structure/union declaration lists. */
protected static final int FLAG_STRUCTURE = 0x20;
// ==========================================================================
/** The pool of parsing contexts. */
private Context pool;
/**
* The top of the context stack. The implementation assumes that
* this field always references at least one context, which
* corresponds to the global namespace.
*/
protected Context top;
/** The current nesting level. */
protected int nesting;
/** The current annotation, if any. */
protected Annotation annotation;
// ==========================================================================
/** Create a C parser state object. */
public CParserState() {
fillPool(POOL_INIT);
top = new Context();
reset(null);
}
// ==========================================================================
/**
* Add the specified number of fresh contexts to the pool.
*
* @param n The number to add.
*/
protected void fillPool(int n) {
for (int i=0; i<n; i++) {
addToPool(new Context());
}
}
/**
* Take a context from the pool, refilling the pool if necessary.
*
* @return A fresh context.
*/
protected Context takeFromPool() {
if (null == pool) {
fillPool(POOL_INCR);
}
Context c = pool;
pool = c.next;
c.next = null;
return c;
}
/**
* Return the specified context to the pool, clearing it along the
* way.
*
* @param c The context to return.
*/
protected void addToPool(Context c) {
c.next = pool;
c.clear();
pool = c;
}
// ==========================================================================
/**
* Push the specified context onto the context stack.
*
* @param c The context to push.
*/
protected void push(Context c) {
c.next = top;
top = c;
}
/**
* Pop a context from the context stack.
*
* @return The top-most context.
*/
protected Context pop() {
Context c = top;
top = c.next;
c.next = null;
return c;
}
// ==========================================================================
public void reset(String file) {
if (DEBUG) System.out.println("reset(" + file + ")");
// Return all contexts besides the top-level context from the
// stack to the pool.
while (null != top.next) {
addToPool(pop());
}
// Clear the top-level context.
top.clear();
top.set(FLAG_SCOPE);
// Clear the nesting level.
nesting = 0;
// Clear any annotations.
annotation = null;
}
public void start() {
if (DEBUG) {
nesting++;
System.out.println("start(" + nesting + ")");
}
push(takeFromPool());
}
public void commit() {
if (DEBUG) {
if (top.isSet(FLAG_SCOPE)) {
System.out.println("implied exitScope(" + nesting + ")");
}
System.out.println("commit(" + nesting + ")");
nesting--;
}
addToPool(pop());
}
public void abort() {
if (DEBUG) {
if (top.isSet(FLAG_SCOPE)) {
System.out.println("implied exitScope(" + nesting + ")");
}
System.out.println("abort(" + nesting + ")");
nesting--;
}
addToPool(pop());
}
// ==========================================================================
/** Record a typedef storage class specifier. */
public void typedef() {
if (DEBUG) System.out.println("typedef()");
top.set(FLAG_TYPEDEF);
}
/** Record a function parameter list. */
public void parameters() {
if (DEBUG) System.out.println("parameters()");
top.set(FLAG_PARAMS);
}
/** Record a function declarator. */
public void functionDeclarator() {
if (DEBUG) System.out.println("functionDeclarator()");
top.clear(FLAG_PARAMS);
}
/** Record a type specifier. */
public void typeSpecifier() {
if (DEBUG) System.out.println("typeSpecifier()");
top.set(FLAG_TYPE_SPEC);
}
/** Enter a new scope. */
public void pushScope() {
if (DEBUG) System.out.println("pushScope(" + nesting + ")");
top.set(FLAG_SCOPE);
}
/** Exit the last scope. */
public void popScope() {
if (DEBUG) System.out.println("popScope(" + nesting + ")");
top.clear(FLAG_SCOPE);
}
/** Enter a structure declaration list. */
public void enterStructure() {
if (DEBUG) System.out.println("enterStructure(" + nesting + ")");
top.set(FLAG_STRUCTURE);
}
/** Exit a structure declaration list. */
public void exitStructure() {
if (DEBUG) System.out.println("exitStructure(" + nesting + ")");
top.clear(FLAG_STRUCTURE);
}
/**
* Implicitly bind the specified identifier. Depending on the
* current parsing context, the identifier is either bound as a type
* or as an object/function/constant.
*
* @param id The identifier.
*/
public void bind(String id) {
// Ignore the binding if a function parameter list has already
// been parsed or the binding appears inside a structure
// declaration list.
if (top.next.isSet(FLAG_PARAMS) || top.next.isSet(FLAG_STRUCTURE)) {
if (DEBUG) {
System.out.println("ignoring bind(" + id + ", " +
top.isSet(FLAG_TYPEDEF) + ")");
}
return;
} else if (DEBUG) {
System.out.println("bind(" + id + ", " + top.isSet(FLAG_TYPEDEF) + ")");
}
// Get the top-most scope.
Context c = top;
while (! c.isSet(FLAG_SCOPE)) {
c = c.next;
}
// Record the name.
if (c.bindings.containsKey(id)) {
if (DEBUG) {
System.out.println("ignoring rebinding of " + id);
}
} else {
c.bindings.put(id, top.isSet(FLAG_TYPEDEF)? Boolean.TRUE : Boolean.FALSE);
c.set(FLAG_MODIFIED);
}
}
/**
* Explicitly bind the specified identifier.
*
* @param id The identifier.
* @param isType The flag for whether the identifier represents a
* type.
*/
public void bind(String id, boolean isType) {
if (DEBUG) {
System.out.println("bind(" + id + ", " + isType + ')');
}
// Get the top-most scope.
Context c = top;
while (! c.isSet(FLAG_SCOPE)) {
c = c.next;
}
// Record the name.
if (c.bindings.containsKey(id)) {
if (DEBUG) {
System.out.println("ignoring rebinding of " + id);
}
} else {
c.bindings.put(id, isType ? Boolean.TRUE : Boolean.FALSE);
c.set(FLAG_MODIFIED);
}
}
/**
* Determine whether the specified identifier names a type.
*
* @param id The identifier.
* @return <code>true</code> if the specified identifier names a type.
*/
public boolean isType(String id) {
// If we have already parsed a type specifier, the identifier does
// not name a type.
if (top.isSet(FLAG_TYPE_SPEC)) {
if (DEBUG) System.out.println("isType(" + id + ") -> false");
return false;
}
// Otherwise, we consult the symbol table.
Context c = top;
do {
while (! c.isSet(FLAG_SCOPE)) {
c = c.next;
}
Boolean value = c.bindings.get(id);
if (null != value) {
boolean type = value.booleanValue();
if (DEBUG) System.out.println("isType(" + id + ") -> " + type);
return type;
}
c = c.next;
} while (null != c);
if (DEBUG) System.out.println("isType(" + id + ") -> false");
return false;
}
/**
* Determine whether a declaration actually is a declaration. This
* method determines whether the sequence
* <pre>
* DeclarationSpecifiers l:InitializedDeclaratorList?
* </pre>
* can actually represent a declaration. It assumes that any type
* specifier encountered while parsing
* <code>DeclarationSpecifiers</code> has been marked through {@link
* #typeSpecifier()}.
*
* @param idl The result of parsing the optional initialized
* declarator list.
* @return <code>true</code> if the declaration is a declaration.
*/
public boolean isValid(Node idl) {
return top.isSet(FLAG_TYPE_SPEC) || (null != idl);
}
// ==========================================================================
/**
* Record a line marker. Note that string values for the four flags
* are interpreted as follows: Any non-null string counts for
* <code>true</code>, while null counts for <code>false</code>.
*
* @see LineMarker
*
* @param file The file name (without quotes).
* @param line The line number.
* @param isStartFile The start file flag.
* @param isReturnToFile The return to file flag.
* @param isSystemHeader The system header flag.
* @param isExternC The extern C flag.
* @param location The line marker's source location.
*/
public void lineMarker(String file, int line, String isStartFile,
String isReturnToFile, String isSystemHeader,
String isExternC, Location location) {
if (DEBUG) System.out.println("lineMarker(" + file + ": " + line + ")");
int flags = 0;
if (null != isStartFile) flags |= LineMarker.FLAG_START_FILE;
if (null != isReturnToFile) flags |= LineMarker.FLAG_RETURN_TO_FILE;
if (null != isSystemHeader) flags |= LineMarker.FLAG_SYSTEM_HEADER;
if (null != isExternC) flags |= LineMarker.FLAG_EXTERN_C;
LineMarker marker = new LineMarker(line, file, flags, null);
marker.setLocation(location);
if (null == annotation) {
annotation = marker;
} else {
annotation.innerMost().setNode(marker);
}
}
/**
* Record a pragma.
*
* @see Pragma
*
* @param directive The actual directive.
* @param location The pragma's source location.
*/
public void pragma(String directive, Location location) {
if (DEBUG) System.out.println("pragma(" + directive + ")");
Pragma pragma = new Pragma(directive, null);
pragma.setLocation(location);
if (null == annotation) {
annotation = pragma;
} else {
annotation.innerMost().setNode(pragma);
}
}
/**
* Record an ident directive.
*
* @see SourceIdentity
*
* @param ident The actual identity marker.
* @param location The ident directive's source location.
*/
public void ident(String ident, Location location) {
if (DEBUG) System.out.println("ident(" + ident + ")");
SourceIdentity identity = new SourceIdentity(ident, null);
identity.setLocation(location);
if (null == annotation) {
annotation = identity;
} else {
annotation.innerMost().setNode(identity);
}
}
/**
* Mark the current annotation. This method must be called before
* recognizing the nonterminals to be annotated. Furthermore, it
* must be called within the context of a stateful production.
*/
public void mark() {
if (DEBUG) System.out.println("mark()");
if (null == annotation) {
top.mark = null;
} else {
top.mark = annotation.innerMost();
}
}
/**
* Annotate the specified node. If any annotations have been
* recorded and {@link #mark() marked}, the specified node is
* wrapped by those annotations and the outer-most annotation is
* returned. Otherwise, the specified node is returned.
*
* @param node The node.
* @return The annotated node.
*/
public Node annotate(Node node) {
if (DEBUG) System.out.println("annotate(" + node + ")");
if (null != top.mark) {
// Find the mark in the next closest context, if it exists.
Annotation base = null;
Context c = top.next;
while (null != c) {
if (null != c.mark) {
base = c.mark;
break;
}
c = c.next;
}
// Apply the annotations between the base and current mark to
// the specified node, while also preserving any new
// annotations.
Annotation a = (Annotation)top.mark.getNode();
top.mark.setNode(node);
if (null == base) {
node = annotation;
annotation = a;
} else {
node = base.getNode();
base.setNode(a);
}
top.mark = null;
}
// Done.
return node;
}
}